diff --git a/src/compiler/compile/nodes/Text.ts b/src/compiler/compile/nodes/Text.ts index 347beb9c23..1c5a6a7584 100644 --- a/src/compiler/compile/nodes/Text.ts +++ b/src/compiler/compile/nodes/Text.ts @@ -49,6 +49,15 @@ export default class Text extends Node { return this.within_pre(); } + /** + * @returns If true, the leading newline character should be stripped. + * @see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element + */ + should_strip_leading_newline(): boolean { + const parent = this.parent; + return parent.type === 'Element' && parent.name === 'pre' && parent.children[0] === this; + } + within_pre(): boolean { let node = this.parent; while (node) { diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index eb57233ed0..49797314fe 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -12,7 +12,7 @@ import { namespaces } from '../../../../utils/namespaces'; import AttributeWrapper from './Attribute'; import StyleAttributeWrapper from './StyleAttribute'; import SpreadAttributeWrapper from './SpreadAttribute'; -import { dimensions } from '../../../../utils/patterns'; +import { dimensions, start_newline } from '../../../../utils/patterns'; import Binding from './Binding'; import add_to_set from '../../../utils/add_to_set'; import { add_event_handler } from '../shared/add_event_handlers'; @@ -939,7 +939,7 @@ export default class ElementWrapper extends Wrapper { if (should_cache) { block.chunks.update.push(b` if (${block.renderer.dirty(dependencies)} && (${cached_snippet} !== (${cached_snippet} = ${snippet}))) { - ${updater} + ${updater} } `); } else { @@ -1015,6 +1015,15 @@ function to_html(wrappers: Array`. + // see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element + const first = wrapper.fragment.nodes[0]; + if (first && first.node.type === 'Text' && start_newline.test(first.node.data)) { + state.quasi.value.raw += '\n'; + } + } + to_html(wrapper.fragment.nodes as Array, block, literal, state); state.quasi.value.raw += ``; diff --git a/src/compiler/compile/render_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts index 87ce775ca9..7986e43c83 100644 --- a/src/compiler/compile/render_dom/wrappers/Fragment.ts +++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts @@ -21,6 +21,7 @@ import Block from '../Block'; import { trim_start, trim_end } from '../../../utils/trim'; import { link } from '../../../utils/link'; import { Identifier } from 'estree'; +import { start_newline } from '../../../utils/patterns'; const wrappers = { AwaitBlock, @@ -127,8 +128,12 @@ export default class FragmentWrapper { if (strip_whitespace) { const first = this.nodes[0] as Text; - if (first && first.node.type === 'Text' && !first.node.keep_space()) { - first.data = trim_start(first.data); + if (first && first.node.type === 'Text') { + if (!first.node.keep_space()) { + first.data = trim_start(first.data); + } else if (first.node.should_strip_leading_newline()) { + first.data = first.data.replace(start_newline, ''); + } if (!first.data) { first.var = null; this.nodes.shift(); diff --git a/src/compiler/utils/patterns.ts b/src/compiler/utils/patterns.ts index 0728035a48..071abd791f 100644 --- a/src/compiler/utils/patterns.ts +++ b/src/compiler/utils/patterns.ts @@ -1,5 +1,6 @@ export const whitespace = /[ \t\r\n]/; export const start_whitespace = /^[ \t\r\n]*/; export const end_whitespace = /[ \t\r\n]*$/; +export const start_newline = /^\r?\n/; export const dimensions = /^(?:offset|client)(?:Width|Height)$/; diff --git a/test/runtime/samples/pre-tag/.editorconfig b/test/runtime/samples/pre-tag/.editorconfig new file mode 100644 index 0000000000..fa948941d5 --- /dev/null +++ b/test/runtime/samples/pre-tag/.editorconfig @@ -0,0 +1,2 @@ +[main.svelte] +trim_trailing_whitespace = unset diff --git a/test/runtime/samples/pre-tag/_config.js b/test/runtime/samples/pre-tag/_config.js index a2e8feb118..c65b9df524 100644 --- a/test/runtime/samples/pre-tag/_config.js +++ b/test/runtime/samples/pre-tag/_config.js @@ -6,23 +6,14 @@ export default { 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;
+		// Test for 
 tag with leading newline
+		const elementPreWithLeadingNewline = target.querySelector('#pre-with-leading-newline');
+		const elementPreWithoutLeadingNewline = target.querySelector('#pre-without-leading-newline');
+		const elementPreWithMultipleLeadingNewline = target.querySelector('#pre-with-multiple-leading-newlines');
 
 		assert.equal(
 			elementPre.innerHTML,
-			`
-  A
+			`  A
   B
   
     C
@@ -53,5 +44,12 @@ export default {
     F
   
` ); + assert.equal(elementPreWithLeadingNewline.children[0].innerHTML, 'leading newline'); + assert.equal(elementPreWithLeadingNewline.children[1].innerHTML, ' leading newline and spaces'); + assert.equal(elementPreWithLeadingNewline.children[2].innerHTML, '\nleading newlines'); + assert.equal(elementPreWithoutLeadingNewline.children[0].innerHTML, 'without spaces'); + assert.equal(elementPreWithoutLeadingNewline.children[1].innerHTML, ' with spaces '); + assert.equal(elementPreWithoutLeadingNewline.children[2].innerHTML, ' \nnewline after leading space'); + assert.equal(elementPreWithMultipleLeadingNewline.innerHTML, '\n\nmultiple leading newlines'); } }; diff --git a/test/runtime/samples/pre-tag/main.svelte b/test/runtime/samples/pre-tag/main.svelte index ef603b9883..6ab8b161de 100644 --- a/test/runtime/samples/pre-tag/main.svelte +++ b/test/runtime/samples/pre-tag/main.svelte @@ -32,3 +32,25 @@ F
+ +
+
+leading newline
+
+  leading newline and spaces
+
+
+leading newlines
+
+ +
+
without spaces
+
  with spaces  
+
 
+newline after leading space
+
+ +
+
+
+multiple leading newlines
diff --git a/test/runtime/samples/preserve-whitespaces/_config.js b/test/runtime/samples/preserve-whitespaces/_config.js index e0c621a18b..ffd6423911 100644 --- a/test/runtime/samples/preserve-whitespaces/_config.js +++ b/test/runtime/samples/preserve-whitespaces/_config.js @@ -10,22 +10,9 @@ export default { // 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
+			`  A
   B
   
     C
diff --git a/test/server-side-rendering/samples/pre-tag/.editorconfig b/test/server-side-rendering/samples/pre-tag/.editorconfig
new file mode 100644
index 0000000000..d53d6a3faf
--- /dev/null
+++ b/test/server-side-rendering/samples/pre-tag/.editorconfig
@@ -0,0 +1,2 @@
+[{main.svelte,_expected.html}]
+trim_trailing_whitespace = unset
diff --git a/test/server-side-rendering/samples/pre-tag/_expected.html b/test/server-side-rendering/samples/pre-tag/_expected.html
index 7f88acdff4..8d8ad5497d 100644
--- a/test/server-side-rendering/samples/pre-tag/_expected.html
+++ b/test/server-side-rendering/samples/pre-tag/_expected.html
@@ -28,3 +28,16 @@
     E
     F
   
+ +
+leading newline
+
+  leading newline and spaces
+
+
+leading newlines
+ +
without spaces
+
  with spaces  
+
 
+newline after leading space
diff --git a/test/server-side-rendering/samples/pre-tag/main.svelte b/test/server-side-rendering/samples/pre-tag/main.svelte index fb240817f7..be3bf9e7ff 100644 --- a/test/server-side-rendering/samples/pre-tag/main.svelte +++ b/test/server-side-rendering/samples/pre-tag/main.svelte @@ -32,3 +32,20 @@ F
+ +
+
+leading newline
+
+  leading newline and spaces
+
+
+leading newlines
+
+ +
+
without spaces
+
  with spaces  
+
 
+newline after leading space
+