diff --git a/src/compiler/compile/nodes/Text.ts b/src/compiler/compile/nodes/Text.ts index 6ea6405dda..347beb9c23 100644 --- a/src/compiler/compile/nodes/Text.ts +++ b/src/compiler/compile/nodes/Text.ts @@ -43,4 +43,21 @@ export default class Text extends Node { return parent_element.namespace || elements_without_text.has(parent_element.name); } + + keep_space(): boolean { + if (this.component.component_options.preserveWhitespace) return true; + return this.within_pre(); + } + + within_pre(): boolean { + let node = this.parent; + while (node) { + if (node.type === 'Element' && node.name === 'pre') { + return true; + } + node = node.parent; + } + + return false; + } } diff --git a/src/compiler/compile/render_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts index 98805b9639..87ce775ca9 100644 --- a/src/compiler/compile/render_dom/wrappers/Fragment.ts +++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts @@ -95,7 +95,7 @@ export default class FragmentWrapper { next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock') ); - if (should_trim) { + if (should_trim && !child.keep_space()) { data = trim_end(data); if (!data) continue; } @@ -127,7 +127,7 @@ export default class FragmentWrapper { if (strip_whitespace) { const first = this.nodes[0] as Text; - if (first && first.node.type === 'Text') { + if (first && first.node.type === 'Text' && !first.node.keep_space()) { first.data = trim_start(first.data); if (!first.data) { first.var = null; diff --git a/src/compiler/compile/render_dom/wrappers/Text.ts b/src/compiler/compile/render_dom/wrappers/Text.ts index edff44d1fd..2a342b5678 100644 --- a/src/compiler/compile/render_dom/wrappers/Text.ts +++ b/src/compiler/compile/render_dom/wrappers/Text.ts @@ -29,15 +29,7 @@ export default class TextWrapper extends Wrapper { if (this.renderer.component.component_options.preserveWhitespace) return false; if (/[\S\u00A0]/.test(this.data)) return false; - let node = this.parent && this.parent.node; - while (node) { - if (node.type === 'Element' && node.name === 'pre') { - return false; - } - node = node.parent; - } - - return true; + return !this.node.within_pre(); } render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { diff --git a/src/compiler/compile/render_ssr/handlers/utils/remove_whitespace_children.ts b/src/compiler/compile/render_ssr/handlers/utils/remove_whitespace_children.ts index b6c1bf493a..7733c89cb8 100644 --- a/src/compiler/compile/render_ssr/handlers/utils/remove_whitespace_children.ts +++ b/src/compiler/compile/render_ssr/handlers/utils/remove_whitespace_children.ts @@ -26,7 +26,7 @@ export default function remove_whitespace_children(children: INode[], next?: INo trimmable_at(child, next) : !child.has_ancestor('EachBlock'); - if (should_trim) { + if (should_trim && !child.keep_space()) { data = trim_end(data); if (!data) continue; } @@ -47,7 +47,7 @@ export default function remove_whitespace_children(children: INode[], next?: INo } const first = nodes[0]; - if (first && first.type === 'Text') { + if (first && first.type === 'Text' && !first.keep_space()) { first.data = trim_start(first.data); if (!first.data) { first.var = null; diff --git a/test/runtime/samples/pre-tag/_config.js b/test/runtime/samples/pre-tag/_config.js new file mode 100644 index 0000000000..a2e8feb118 --- /dev/null +++ b/test/runtime/samples/pre-tag/_config.js @@ -0,0 +1,57 @@ +export default { + test({ assert, target }) { + // Test for
tag
+ const elementPre = target.querySelector('#pre');
+ // Test for non tag
+ const elementDiv = target.querySelector('#div');
+ // Test for tag in non tag
+ const elementDivWithPre = target.querySelector('#div-with-pre');
+
+ // There is a slight difference in innerHTML because there is a difference in HTML optimization (in jsdom)
+ // depending on how the innerHTML is set.
+ // (There is no difference in the display.)
+ // Reassign innerHTML to add the same optimizations to innerHTML.
+
+ // eslint-disable-next-line no-self-assign
+ elementPre.innerHTML = elementPre.innerHTML;
+ // eslint-disable-next-line no-self-assign
+ elementDiv.innerHTML = elementDiv.innerHTML;
+ // eslint-disable-next-line no-self-assign
+ elementDivWithPre.innerHTML = elementDivWithPre.innerHTML;
+
+ assert.equal(
+ elementPre.innerHTML,
+ `
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+`
+ );
+ assert.equal(
+ elementDiv.innerHTML,
+ `A
+ B
+ C
+ D
+ E
+ F`
+ );
+ assert.equal(
+ elementDivWithPre.innerHTML,
+ ` A
+ B
+
+ C
+ D
+
+ E
+ F
+ `
+ );
+ }
+};
diff --git a/test/runtime/samples/pre-tag/main.svelte b/test/runtime/samples/pre-tag/main.svelte
new file mode 100644
index 0000000000..ef603b9883
--- /dev/null
+++ b/test/runtime/samples/pre-tag/main.svelte
@@ -0,0 +1,34 @@
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
+
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
diff --git a/test/runtime/samples/preserve-whitespaces/_config.js b/test/runtime/samples/preserve-whitespaces/_config.js
new file mode 100644
index 0000000000..e0c621a18b
--- /dev/null
+++ b/test/runtime/samples/preserve-whitespaces/_config.js
@@ -0,0 +1,66 @@
+export default {
+ compileOptions: {
+ preserveWhitespace: true
+ },
+ test({ assert, target }) {
+ // Test for tag
+ const elementPre = target.querySelector('#pre');
+ // Test for non tag
+ const elementDiv = target.querySelector('#div');
+ // Test for tag in non tag
+ const elementDivWithPre = target.querySelector('#div-with-pre');
+
+ // There is a slight difference in innerHTML because there is a difference in HTML optimization (in jsdom)
+ // depending on how the innerHTML is set.
+ // (There is no difference in the display.)
+ // Reassign innerHTML to add the same optimizations to innerHTML.
+
+ // eslint-disable-next-line no-self-assign
+ elementPre.innerHTML = elementPre.innerHTML;
+ // eslint-disable-next-line no-self-assign
+ elementDiv.innerHTML = elementDiv.innerHTML;
+ // eslint-disable-next-line no-self-assign
+ elementDivWithPre.innerHTML = elementDivWithPre.innerHTML;
+
+ assert.equal(
+ elementPre.innerHTML,
+ `
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+`
+ );
+ assert.equal(
+ elementDiv.innerHTML,
+ `
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+`
+ );
+ assert.equal(
+ elementDivWithPre.innerHTML,
+ `
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+`
+ );
+ }
+};
diff --git a/test/runtime/samples/preserve-whitespaces/main.svelte b/test/runtime/samples/preserve-whitespaces/main.svelte
new file mode 100644
index 0000000000..ef603b9883
--- /dev/null
+++ b/test/runtime/samples/preserve-whitespaces/main.svelte
@@ -0,0 +1,34 @@
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
+
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
diff --git a/test/server-side-rendering/index.ts b/test/server-side-rendering/index.ts
index 98d19a6006..f5773e37b6 100644
--- a/test/server-side-rendering/index.ts
+++ b/test/server-side-rendering/index.ts
@@ -83,9 +83,13 @@ describe('ssr', () => {
if (css.code) fs.writeFileSync(`${dir}/_actual.css`, css.code);
try {
- (compileOptions.preserveComments
- ? assert.htmlEqualWithComments
- : assert.htmlEqual)(html, expectedHtml);
+ if (config.withoutNormalizeHtml) {
+ assert.strictEqual(html.trim(), expectedHtml.trim().replace(/\r\n/g, '\n'));
+ } else {
+ (compileOptions.preserveComments
+ ? assert.htmlEqualWithComments
+ : assert.htmlEqual)(html, expectedHtml);
+ }
} catch (error) {
if (shouldUpdateExpected()) {
fs.writeFileSync(`${dir}/_expected.html`, html);
diff --git a/test/server-side-rendering/samples/pre-tag/_config.js b/test/server-side-rendering/samples/pre-tag/_config.js
new file mode 100644
index 0000000000..39b31839f5
--- /dev/null
+++ b/test/server-side-rendering/samples/pre-tag/_config.js
@@ -0,0 +1,3 @@
+export default {
+ withoutNormalizeHtml: true
+};
diff --git a/test/server-side-rendering/samples/pre-tag/_expected.html b/test/server-side-rendering/samples/pre-tag/_expected.html
new file mode 100644
index 0000000000..7f88acdff4
--- /dev/null
+++ b/test/server-side-rendering/samples/pre-tag/_expected.html
@@ -0,0 +1,30 @@
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
+A
+ B
+ C
+ D
+
+ E
+ F
+
+
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
diff --git a/test/server-side-rendering/samples/pre-tag/main.svelte b/test/server-side-rendering/samples/pre-tag/main.svelte
new file mode 100644
index 0000000000..fb240817f7
--- /dev/null
+++ b/test/server-side-rendering/samples/pre-tag/main.svelte
@@ -0,0 +1,34 @@
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
+
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
diff --git a/test/server-side-rendering/samples/preserve-whitespaces/_config.js b/test/server-side-rendering/samples/preserve-whitespaces/_config.js
new file mode 100644
index 0000000000..41eb78446c
--- /dev/null
+++ b/test/server-side-rendering/samples/preserve-whitespaces/_config.js
@@ -0,0 +1,6 @@
+export default {
+ withoutNormalizeHtml: true,
+ compileOptions: {
+ preserveWhitespace: true
+ }
+};
diff --git a/test/server-side-rendering/samples/preserve-whitespaces/_expected.html b/test/server-side-rendering/samples/preserve-whitespaces/_expected.html
new file mode 100644
index 0000000000..fb240817f7
--- /dev/null
+++ b/test/server-side-rendering/samples/preserve-whitespaces/_expected.html
@@ -0,0 +1,34 @@
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
+
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
diff --git a/test/server-side-rendering/samples/preserve-whitespaces/main.svelte b/test/server-side-rendering/samples/preserve-whitespaces/main.svelte
new file mode 100644
index 0000000000..fb240817f7
--- /dev/null
+++ b/test/server-side-rendering/samples/preserve-whitespaces/main.svelte
@@ -0,0 +1,34 @@
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+
+
+
+ A
+ B
+
+ C
+ D
+
+ E
+ F
+
+