Skip to content

Commit 7463d51

Browse files
authored
[fix]: keep space in <pre> or when preserveWhitespace: true (#6990)
Fixes #6437 Fixes #4731 Closes #4737 Whitespace is now untouched inside <pre> tag and other tags if preserveWhitespace is true
1 parent 587f94e commit 7463d51

File tree

15 files changed

+361
-16
lines changed

15 files changed

+361
-16
lines changed

src/compiler/compile/nodes/Text.ts

+17
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,21 @@ export default class Text extends Node {
4343

4444
return parent_element.namespace || elements_without_text.has(parent_element.name);
4545
}
46+
47+
keep_space(): boolean {
48+
if (this.component.component_options.preserveWhitespace) return true;
49+
return this.within_pre();
50+
}
51+
52+
within_pre(): boolean {
53+
let node = this.parent;
54+
while (node) {
55+
if (node.type === 'Element' && node.name === 'pre') {
56+
return true;
57+
}
58+
node = node.parent;
59+
}
60+
61+
return false;
62+
}
4663
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export default class FragmentWrapper {
9595
next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock')
9696
);
9797

98-
if (should_trim) {
98+
if (should_trim && !child.keep_space()) {
9999
data = trim_end(data);
100100
if (!data) continue;
101101
}
@@ -127,7 +127,7 @@ export default class FragmentWrapper {
127127
if (strip_whitespace) {
128128
const first = this.nodes[0] as Text;
129129

130-
if (first && first.node.type === 'Text') {
130+
if (first && first.node.type === 'Text' && !first.node.keep_space()) {
131131
first.data = trim_start(first.data);
132132
if (!first.data) {
133133
first.var = null;

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

+1-9
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,7 @@ export default class TextWrapper extends Wrapper {
2929
if (this.renderer.component.component_options.preserveWhitespace) return false;
3030
if (/[\S\u00A0]/.test(this.data)) return false;
3131

32-
let node = this.parent && this.parent.node;
33-
while (node) {
34-
if (node.type === 'Element' && node.name === 'pre') {
35-
return false;
36-
}
37-
node = node.parent;
38-
}
39-
40-
return true;
32+
return !this.node.within_pre();
4133
}
4234

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

src/compiler/compile/render_ssr/handlers/utils/remove_whitespace_children.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default function remove_whitespace_children(children: INode[], next?: INo
2626
trimmable_at(child, next)
2727
: !child.has_ancestor('EachBlock');
2828

29-
if (should_trim) {
29+
if (should_trim && !child.keep_space()) {
3030
data = trim_end(data);
3131
if (!data) continue;
3232
}
@@ -47,7 +47,7 @@ export default function remove_whitespace_children(children: INode[], next?: INo
4747
}
4848

4949
const first = nodes[0];
50-
if (first && first.type === 'Text') {
50+
if (first && first.type === 'Text' && !first.keep_space()) {
5151
first.data = trim_start(first.data);
5252
if (!first.data) {
5353
first.var = null;
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
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+
10+
// There is a slight difference in innerHTML because there is a difference in HTML optimization (in jsdom)
11+
// depending on how the innerHTML is set.
12+
// (There is no difference in the display.)
13+
// Reassign innerHTML to add the same optimizations to innerHTML.
14+
15+
// eslint-disable-next-line no-self-assign
16+
elementPre.innerHTML = elementPre.innerHTML;
17+
// eslint-disable-next-line no-self-assign
18+
elementDiv.innerHTML = elementDiv.innerHTML;
19+
// eslint-disable-next-line no-self-assign
20+
elementDivWithPre.innerHTML = elementDivWithPre.innerHTML;
21+
22+
assert.equal(
23+
elementPre.innerHTML,
24+
`
25+
A
26+
B
27+
<span>
28+
C
29+
D
30+
</span>
31+
E
32+
F
33+
`
34+
);
35+
assert.equal(
36+
elementDiv.innerHTML,
37+
`A
38+
B
39+
<span>C
40+
D</span>
41+
E
42+
F`
43+
);
44+
assert.equal(
45+
elementDivWithPre.innerHTML,
46+
`<pre> A
47+
B
48+
<span>
49+
C
50+
D
51+
</span>
52+
E
53+
F
54+
</pre>`
55+
);
56+
}
57+
};
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<pre id="pre">
2+
A
3+
B
4+
<span>
5+
C
6+
D
7+
</span>
8+
E
9+
F
10+
</pre>
11+
12+
<div id="div">
13+
A
14+
B
15+
<span>
16+
C
17+
D
18+
</span>
19+
E
20+
F
21+
</div>
22+
23+
<div id="div-with-pre">
24+
<pre>
25+
A
26+
B
27+
<span>
28+
C
29+
D
30+
</span>
31+
E
32+
F
33+
</pre>
34+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
export default {
2+
compileOptions: {
3+
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');
12+
13+
// There is a slight difference in innerHTML because there is a difference in HTML optimization (in jsdom)
14+
// depending on how the innerHTML is set.
15+
// (There is no difference in the display.)
16+
// Reassign innerHTML to add the same optimizations to innerHTML.
17+
18+
// eslint-disable-next-line no-self-assign
19+
elementPre.innerHTML = elementPre.innerHTML;
20+
// eslint-disable-next-line no-self-assign
21+
elementDiv.innerHTML = elementDiv.innerHTML;
22+
// eslint-disable-next-line no-self-assign
23+
elementDivWithPre.innerHTML = elementDivWithPre.innerHTML;
24+
25+
assert.equal(
26+
elementPre.innerHTML,
27+
`
28+
A
29+
B
30+
<span>
31+
C
32+
D
33+
</span>
34+
E
35+
F
36+
`
37+
);
38+
assert.equal(
39+
elementDiv.innerHTML,
40+
`
41+
A
42+
B
43+
<span>
44+
C
45+
D
46+
</span>
47+
E
48+
F
49+
`
50+
);
51+
assert.equal(
52+
elementDivWithPre.innerHTML,
53+
`
54+
<pre> A
55+
B
56+
<span>
57+
C
58+
D
59+
</span>
60+
E
61+
F
62+
</pre>
63+
`
64+
);
65+
}
66+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<pre id="pre">
2+
A
3+
B
4+
<span>
5+
C
6+
D
7+
</span>
8+
E
9+
F
10+
</pre>
11+
12+
<div id="div">
13+
A
14+
B
15+
<span>
16+
C
17+
D
18+
</span>
19+
E
20+
F
21+
</div>
22+
23+
<div id="div-with-pre">
24+
<pre>
25+
A
26+
B
27+
<span>
28+
C
29+
D
30+
</span>
31+
E
32+
F
33+
</pre>
34+
</div>

test/server-side-rendering/index.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,13 @@ describe('ssr', () => {
8383
if (css.code) fs.writeFileSync(`${dir}/_actual.css`, css.code);
8484

8585
try {
86-
(compileOptions.preserveComments
87-
? assert.htmlEqualWithComments
88-
: assert.htmlEqual)(html, expectedHtml);
86+
if (config.withoutNormalizeHtml) {
87+
assert.strictEqual(html.trim(), expectedHtml.trim().replace(/\r\n/g, '\n'));
88+
} else {
89+
(compileOptions.preserveComments
90+
? assert.htmlEqualWithComments
91+
: assert.htmlEqual)(html, expectedHtml);
92+
}
8993
} catch (error) {
9094
if (shouldUpdateExpected()) {
9195
fs.writeFileSync(`${dir}/_expected.html`, html);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
withoutNormalizeHtml: true
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<pre>
2+
A
3+
B
4+
<span>
5+
C
6+
D
7+
</span>
8+
E
9+
F
10+
</pre>
11+
12+
<div>A
13+
B
14+
<span>C
15+
D
16+
</span>
17+
E
18+
F
19+
</div>
20+
21+
<div><pre>
22+
A
23+
B
24+
<span>
25+
C
26+
D
27+
</span>
28+
E
29+
F
30+
</pre></div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<pre>
2+
A
3+
B
4+
<span>
5+
C
6+
D
7+
</span>
8+
E
9+
F
10+
</pre>
11+
12+
<div>
13+
A
14+
B
15+
<span>
16+
C
17+
D
18+
</span>
19+
E
20+
F
21+
</div>
22+
23+
<div>
24+
<pre>
25+
A
26+
B
27+
<span>
28+
C
29+
D
30+
</span>
31+
E
32+
F
33+
</pre>
34+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default {
2+
withoutNormalizeHtml: true,
3+
compileOptions: {
4+
preserveWhitespace: true
5+
}
6+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<pre>
2+
A
3+
B
4+
<span>
5+
C
6+
D
7+
</span>
8+
E
9+
F
10+
</pre>
11+
12+
<div>
13+
A
14+
B
15+
<span>
16+
C
17+
D
18+
</span>
19+
E
20+
F
21+
</div>
22+
23+
<div>
24+
<pre>
25+
A
26+
B
27+
<span>
28+
C
29+
D
30+
</span>
31+
E
32+
F
33+
</pre>
34+
</div>

0 commit comments

Comments
 (0)