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/js/samples/pre-tag/expected.js b/test/js/samples/pre-tag/expected.js new file mode 100644 index 0000000000..c037693c30 --- /dev/null +++ b/test/js/samples/pre-tag/expected.js @@ -0,0 +1,86 @@ +/* generated by Svelte vX.Y.Z */ +import { + SvelteComponent, + detach, + element, + init, + insert, + noop, + safe_not_equal, + space +} from "svelte/internal"; + +function create_fragment(ctx) { + let pre0; + let t3; + let div0; + let t7; + let div1; + + return { + c() { + pre0 = element("pre"); + + pre0.innerHTML = ` + A + B + + C + D + + E + F +`; + + t3 = space(); + div0 = element("div"); + + div0.innerHTML = `A + B + C + D + E + F`; + + t7 = space(); + div1 = element("div"); + + div1.innerHTML = `
+    A
+    B
+    
+      C
+      D
+    
+    E
+    F
+  
`; + }, + m(target, anchor) { + insert(target, pre0, anchor); + insert(target, t3, anchor); + insert(target, div0, anchor); + insert(target, t7, anchor); + insert(target, div1, anchor); + }, + p: noop, + i: noop, + o: noop, + d(detaching) { + if (detaching) detach(pre0); + if (detaching) detach(t3); + if (detaching) detach(div0); + if (detaching) detach(t7); + if (detaching) detach(div1); + } + }; +} + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, null, create_fragment, safe_not_equal, {}); + } +} + +export default Component; diff --git a/test/js/samples/pre-tag/input.svelte b/test/js/samples/pre-tag/input.svelte new file mode 100644 index 0000000000..fb240817f7 --- /dev/null +++ b/test/js/samples/pre-tag/input.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/js/samples/preserve-whitespaces/_config.js b/test/js/samples/preserve-whitespaces/_config.js new file mode 100644 index 0000000000..65ae5c9c93 --- /dev/null +++ b/test/js/samples/preserve-whitespaces/_config.js @@ -0,0 +1,5 @@ +export default { + options: { + preserveWhitespace: true + } +}; diff --git a/test/js/samples/preserve-whitespaces/expected.js b/test/js/samples/preserve-whitespaces/expected.js new file mode 100644 index 0000000000..55e54995cc --- /dev/null +++ b/test/js/samples/preserve-whitespaces/expected.js @@ -0,0 +1,92 @@ +/* generated by Svelte vX.Y.Z */ +import { + SvelteComponent, + detach, + element, + init, + insert, + noop, + safe_not_equal, + text +} from "svelte/internal"; + +function create_fragment(ctx) { + let pre0; + let t3; + let div0; + let t7; + let div1; + + return { + c() { + pre0 = element("pre"); + + pre0.innerHTML = ` + A + B + + C + D + + E + F +`; + + t3 = text("\n\n"); + div0 = element("div"); + + div0.innerHTML = ` + A + B + + C + D + + E + F +`; + + t7 = text("\n\n"); + div1 = element("div"); + + div1.innerHTML = ` +
+    A
+    B
+    
+      C
+      D
+    
+    E
+    F
+  
+`; + }, + m(target, anchor) { + insert(target, pre0, anchor); + insert(target, t3, anchor); + insert(target, div0, anchor); + insert(target, t7, anchor); + insert(target, div1, anchor); + }, + p: noop, + i: noop, + o: noop, + d(detaching) { + if (detaching) detach(pre0); + if (detaching) detach(t3); + if (detaching) detach(div0); + if (detaching) detach(t7); + if (detaching) detach(div1); + } + }; +} + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, null, create_fragment, safe_not_equal, {}); + } +} + +export default Component; diff --git a/test/js/samples/preserve-whitespaces/input.svelte b/test/js/samples/preserve-whitespaces/input.svelte new file mode 100644 index 0000000000..fb240817f7 --- /dev/null +++ b/test/js/samples/preserve-whitespaces/input.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..1027c495a0 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()); + } 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
+  
+