Skip to content

Commit 5466925

Browse files
committed
improve whitespace handling
1 parent fbfe910 commit 5466925

File tree

23 files changed

+102
-330
lines changed

23 files changed

+102
-330
lines changed

src/compiler/compile/nodes/Text.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,11 @@ export default class Text extends Node {
6060

6161
return false;
6262
}
63+
64+
use_space(): boolean {
65+
if (this.component.compile_options.preserveWhitespace) return false;
66+
if (/[\S\u00A0]/.test(this.data)) return false;
67+
68+
return !this.within_pre();
69+
}
6370
}

src/compiler/compile/render_dom/wrappers/Element/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,11 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
11181118
// Don't add the <pre>/<textare> newline logic here because pre/textarea.innerHTML
11191119
// would keep the leading newline, too, only someParent.innerHTML = '..<pre/textarea>..' won't
11201120

1121-
if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' ';
1121+
if (wrapper.use_space()) {
1122+
// use space instead of the text content
1123+
state.quasi.value.raw += ' ';
1124+
return;
1125+
}
11221126

11231127
const parent = wrapper.node.parent as Element;
11241128

src/compiler/compile/render_dom/wrappers/Text.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Identifier } from 'estree';
77

88
export default class TextWrapper extends Wrapper {
99
node: Text;
10-
data: string;
10+
_data: string;
1111
skip: boolean;
1212
var: Identifier;
1313

@@ -21,15 +21,22 @@ export default class TextWrapper extends Wrapper {
2121
super(renderer, block, parent, node);
2222

2323
this.skip = this.node.should_skip();
24-
this.data = data;
24+
this._data = data;
2525
this.var = (this.skip ? null : x`t`) as unknown as Identifier;
2626
}
2727

2828
use_space() {
29-
if (this.renderer.component.component_options.preserveWhitespace) return false;
30-
if (/[\S\u00A0]/.test(this.data)) return false;
29+
return this.node.use_space();
30+
}
3131

32-
return !this.node.within_pre();
32+
set data(value: string) {
33+
// when updating `this.data` during optimisation
34+
// propagate the changes over to the underlying node
35+
// so that the node.use_space reflects on the latest `data` value
36+
this.node.data = this._data = value;
37+
}
38+
get data() {
39+
return this._data;
3340
}
3441

3542
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {

src/compiler/compile/render_ssr/handlers/Text.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import Element from '../../nodes/Element';
55

66
export default function(node: Text, renderer: Renderer, _options: RenderOptions) {
77
let text = node.data;
8-
if (
8+
if (node.use_space()) {
9+
text = ' ';
10+
} else if (
911
!node.parent ||
1012
node.parent.type !== 'Element' ||
1113
((node.parent as Element).name !== 'script' && (node.parent as Element).name !== 'style')

test/helpers.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import glob from 'tiny-glob/sync';
44
import * as path from 'path';
55
import * as fs from 'fs';
66
import * as colors from 'kleur';
7-
export const assert = (assert$1 as unknown) as typeof assert$1 & { htmlEqual: (actual, expected, message?) => void, htmlEqualWithComments: (actual, expected, message?) => void };
7+
export const assert = (assert$1 as unknown) as typeof assert$1 & { htmlEqual: (actual, expected, message?) => void, htmlEqualWithOptions: (actual, expected, options, message?) => void };
88

99
// for coverage purposes, we need to test source files,
1010
// but for sanity purposes, we need to test dist files
@@ -140,7 +140,7 @@ function cleanChildren(node) {
140140
}
141141
}
142142

143-
export function normalizeHtml(window, html, { removeDataSvelte = false, preserveComments = false }: { removeDataSvelte?: boolean, preserveComments?: boolean} = {}) {
143+
export function normalizeHtml(window, html, { removeDataSvelte = false, preserveComments = false }: { removeDataSvelte?: boolean, preserveComments?: boolean }) {
144144
try {
145145
const node = window.document.createElement('div');
146146
node.innerHTML = html
@@ -155,7 +155,12 @@ export function normalizeHtml(window, html, { removeDataSvelte = false, preserve
155155
}
156156
}
157157

158-
export function setupHtmlEqual(options?: { removeDataSvelte?: boolean }) {
158+
export function normalizeNewline(html: string) {
159+
// return html.trim().replace(/\r\n/g, '\n')
160+
return html.replace(/\r\n/g, '\n');
161+
}
162+
163+
export function setupHtmlEqual(options: { removeDataSvelte?: boolean } = {}) {
159164
const window = env();
160165

161166
// eslint-disable-next-line no-import-assign
@@ -167,10 +172,14 @@ export function setupHtmlEqual(options?: { removeDataSvelte?: boolean }) {
167172
);
168173
};
169174
// eslint-disable-next-line no-import-assign
170-
assert.htmlEqualWithComments = (actual, expected, message) => {
175+
assert.htmlEqualWithOptions = (actual, expected, { preserveComments, withoutNormalizeHtml }, message) => {
171176
assert.deepEqual(
172-
normalizeHtml(window, actual, { ...options, preserveComments: true }),
173-
normalizeHtml(window, expected, { ...options, preserveComments: true }),
177+
withoutNormalizeHtml
178+
? normalizeNewline(actual).replace(/(\sdata-svelte="[^"]+")/g, options.removeDataSvelte ? '' : '$1')
179+
: normalizeHtml(window, actual, { ...options, preserveComments }),
180+
withoutNormalizeHtml
181+
? normalizeNewline(expected).replace(/(\sdata-svelte="[^"]+")/g, options.removeDataSvelte ? '' : '$1')
182+
: normalizeHtml(window, expected, { ...options, preserveComments }),
174183
message
175184
);
176185
};

test/js/samples/debug-ssr-foo/expected.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@ const Component = create_ssr_component(($$result, $$props, $$bindings, slots) =>
88
if ($$props.foo === void 0 && $$bindings.foo && foo !== void 0) $$bindings.foo(foo);
99

1010
return `${each(things, thing => {
11-
return `<span>${escape(thing.name)}</span>
12-
${debug(null, 7, 2, { foo })}`;
13-
})}
14-
15-
<p>foo: ${escape(foo)}</p>`;
11+
return `<span>${escape(thing.name)}</span> ${debug(null, 7, 2, { foo })}`;
12+
})} <p>foo: ${escape(foo)}</p>`;
1613
});
1714

18-
export default Component;
15+
export default Component;

test/js/samples/ssr-preserve-comments/expected.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
import { create_ssr_component } from "svelte/internal";
33

44
const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => {
5-
return `<div>content</div>
6-
<!-- comment -->
7-
<div>more content</div>`;
5+
return `<div>content</div> <!-- comment --> <div>more content</div>`;
86
});
97

10-
export default Component;
8+
export default Component;

test/runtime/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ describe('runtime', () => {
201201
}
202202

203203
if (config.html) {
204-
assert.htmlEqual(target.innerHTML, config.html);
204+
assert.htmlEqualWithOptions(target.innerHTML, config.html, {
205+
withoutNormalizeHtml: config.withoutNormalizeHtml
206+
});
205207
}
206208

207209
if (config.test) {
Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,37 @@
11
export default {
2-
test({ assert, target }) {
3-
// Test for <pre> tag
4-
const elementPre = target.querySelector('#pre');
5-
// Test for non <pre> tag
6-
const elementDiv = target.querySelector('#div');
7-
// Test for <pre> tag in non <pre> tag
8-
const elementDivWithPre = target.querySelector('#div-with-pre');
9-
// Test for <pre> tag with leading newline
10-
const elementPreWithLeadingNewline = target.querySelector('#pre-with-leading-newline');
11-
const elementPreWithoutLeadingNewline = target.querySelector('#pre-without-leading-newline');
12-
const elementPreWithMultipleLeadingNewline = target.querySelector('#pre-with-multiple-leading-newlines');
2+
withoutNormalizeHtml: true,
3+
html: get_html(false),
4+
ssrHtml: get_html(true)
5+
};
136

14-
assert.equal(
15-
elementPre.innerHTML,
16-
` A
7+
function get_html(ssr) {
8+
// ssr rendered HTML has an extra newline prefixed within `<pre>` tag,
9+
// if the <pre> tag starts with `\n`
10+
// because when browser parses the SSR rendered HTML, it will ignore the 1st '\n' character
11+
return `<pre id="pre"> A
1712
B
1813
<span>
1914
C
2015
D
2116
</span>
2217
E
2318
F
24-
`
25-
);
26-
assert.equal(
27-
elementDiv.innerHTML,
28-
`A
19+
</pre> <div id="div">A
2920
B
3021
<span>C
3122
D</span>
3223
E
33-
F`
34-
);
35-
assert.equal(
36-
elementDivWithPre.innerHTML,
37-
`<pre> A
24+
F</div> <div id="div-with-pre"><pre> A
3825
B
3926
<span>
4027
C
4128
D
4229
</span>
4330
E
4431
F
45-
</pre>`
46-
);
47-
assert.equal(elementPreWithLeadingNewline.children[0].innerHTML, 'leading newline');
48-
assert.equal(elementPreWithLeadingNewline.children[1].innerHTML, ' leading newline and spaces');
49-
assert.equal(elementPreWithLeadingNewline.children[2].innerHTML, '\nleading newlines');
50-
assert.equal(elementPreWithoutLeadingNewline.children[0].innerHTML, 'without spaces');
51-
assert.equal(elementPreWithoutLeadingNewline.children[1].innerHTML, ' with spaces ');
52-
assert.equal(elementPreWithoutLeadingNewline.children[2].innerHTML, ' \nnewline after leading space');
53-
assert.equal(elementPreWithMultipleLeadingNewline.innerHTML, '\n\nmultiple leading newlines');
54-
}
55-
};
32+
</pre></div> <div id="pre-with-leading-newline"><pre>leading newline</pre> <pre> leading newline and spaces</pre> <pre>${ssr ? '\n' : ''}
33+
leading newlines</pre></div> <div id="pre-without-leading-newline"><pre>without spaces</pre> <pre> with spaces </pre> <pre>
34+
newline after leading space</pre></div> <pre id="pre-with-multiple-leading-newlines">${ssr ? '\n' : ''}
35+
36+
multiple leading newlines</pre>`;
37+
}
Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,19 @@
11
export default {
22
compileOptions: {
33
preserveWhitespace: true
4-
},
5-
test({ assert, target }) {
6-
// Test for <pre> tag
7-
const elementPre = target.querySelector('#pre');
8-
// Test for non <pre> tag
9-
const elementDiv = target.querySelector('#div');
10-
// Test for <pre> tag in non <pre> tag
11-
const elementDivWithPre = target.querySelector('#div-with-pre');
4+
},
125

13-
assert.equal(
14-
elementPre.innerHTML,
15-
` A
6+
html: `<pre id="pre"> A
167
B
178
<span>
189
C
1910
D
2011
</span>
2112
E
2213
F
23-
`
24-
);
25-
assert.equal(
26-
elementDiv.innerHTML,
27-
`
14+
</pre>
15+
16+
<div id="div">
2817
A
2918
B
3019
<span>
@@ -33,11 +22,9 @@ export default {
3322
</span>
3423
E
3524
F
36-
`
37-
);
38-
assert.equal(
39-
elementDivWithPre.innerHTML,
40-
`
25+
</div>
26+
27+
<div id="div-with-pre">
4128
<pre> A
4229
B
4330
<span>
@@ -47,7 +34,5 @@ export default {
4734
E
4835
F
4936
</pre>
50-
`
51-
);
52-
}
37+
</div>`
5338
};

0 commit comments

Comments
 (0)