From 69b70e6421de34bf5163b0a6e200ebf57779f38a Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Thu, 17 Feb 2022 19:32:09 +0900 Subject: [PATCH 01/11] [fix]: strip leading newline after `
`

---
 src/compiler/compile/nodes/Text.ts            |  9 +++++++
 .../render_dom/wrappers/Element/index.ts      | 13 ++++++++--
 .../compile/render_dom/wrappers/Fragment.ts   |  9 +++++--
 src/compiler/utils/patterns.ts                |  1 +
 test/runtime/samples/pre-tag/.editorconfig    |  2 ++
 test/runtime/samples/pre-tag/_config.js       | 26 +++++++++----------
 test/runtime/samples/pre-tag/main.svelte      | 22 ++++++++++++++++
 .../samples/preserve-whitespaces/_config.js   | 15 +----------
 .../samples/pre-tag/.editorconfig             |  2 ++
 .../samples/pre-tag/_expected.html            | 13 ++++++++++
 .../samples/pre-tag/main.svelte               | 17 ++++++++++++
 11 files changed, 97 insertions(+), 32 deletions(-)
 create mode 100644 test/runtime/samples/pre-tag/.editorconfig
 create mode 100644 test/server-side-rendering/samples/pre-tag/.editorconfig

diff --git a/src/compiler/compile/nodes/Text.ts b/src/compiler/compile/nodes/Text.ts
index 347beb9c230..1c5a6a7584f 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 eb57233ed0a..49797314fea 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 87ce775ca93..7986e43c834 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 0728035a48e..071abd791f1 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 00000000000..fa948941d5f
--- /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 a2e8feb118c..c65b9df5247 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 ef603b98838..6ab8b161de4 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 e0c621a18b3..ffd6423911d 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 00000000000..d53d6a3faf3
--- /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 7f88acdff41..8d8ad5497de 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 fb240817f7a..be3bf9e7ffc 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
+
From ece59b4d1183de02133a3eac40e51e93531f76d6 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Fri, 8 Apr 2022 11:49:40 +0900 Subject: [PATCH 02/11] Fix for ` + +
+ +
+ +
+ + + +
+ +
+ + + +
+ + + +
+ +
diff --git a/test/server-side-rendering/samples/textarea-content/.editorconfig b/test/server-side-rendering/samples/textarea-content/.editorconfig new file mode 100644 index 00000000000..d53d6a3faf3 --- /dev/null +++ b/test/server-side-rendering/samples/textarea-content/.editorconfig @@ -0,0 +1,2 @@ +[{main.svelte,_expected.html}] +trim_trailing_whitespace = unset diff --git a/test/server-side-rendering/samples/textarea-content/_config.js b/test/server-side-rendering/samples/textarea-content/_config.js new file mode 100644 index 00000000000..39b31839f50 --- /dev/null +++ b/test/server-side-rendering/samples/textarea-content/_config.js @@ -0,0 +1,3 @@ +export default { + withoutNormalizeHtml: true +}; diff --git a/test/server-side-rendering/samples/textarea-content/_expected.html b/test/server-side-rendering/samples/textarea-content/_expected.html new file mode 100644 index 00000000000..7be30b00370 --- /dev/null +++ b/test/server-side-rendering/samples/textarea-content/_expected.html @@ -0,0 +1,35 @@ + + +
+ +
+ +
+ +
+ +
+ + + +
diff --git a/test/server-side-rendering/samples/textarea-content/main.svelte b/test/server-side-rendering/samples/textarea-content/main.svelte new file mode 100644 index 00000000000..e2e052ca7a3 --- /dev/null +++ b/test/server-side-rendering/samples/textarea-content/main.svelte @@ -0,0 +1,40 @@ + + +
+ +
+ +
+ + + +
+ +
+ + + +
+ + + +
+ +
From 1464caf1708f88546f46ad3b96987db1cacf9a00 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Fri, 8 Apr 2022 12:20:00 +0900 Subject: [PATCH 03/11] fix test for CRLF --- test/server-side-rendering/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/server-side-rendering/index.ts b/test/server-side-rendering/index.ts index f5773e37b60..2a4e0596e75 100644 --- a/test/server-side-rendering/index.ts +++ b/test/server-side-rendering/index.ts @@ -84,7 +84,7 @@ describe('ssr', () => { try { if (config.withoutNormalizeHtml) { - assert.strictEqual(html.trim(), expectedHtml.trim().replace(/\r\n/g, '\n')); + assert.strictEqual(html.trim().replace(/\r\n/g, '\n'), expectedHtml.trim().replace(/\r\n/g, '\n')); } else { (compileOptions.preserveComments ? assert.htmlEqualWithComments From 7d4990c5d47eec078548fa806d61cac5687edc07 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Mon, 11 Apr 2022 09:50:56 +0900 Subject: [PATCH 04/11] Strip newline logic to src/compiler/compile/nodes/Eleement.ts --- src/compiler/compile/nodes/Element.ts | 16 ++++++++++------ src/compiler/compile/nodes/Text.ts | 9 --------- .../compile/render_dom/wrappers/Fragment.ts | 3 --- .../compile/render_ssr/handlers/Element.ts | 10 ++++++++++ .../samples/pre-tag/_expected.html | 12 ++++-------- 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 4ca28ca7360..55b7fbfb9dc 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -198,6 +198,16 @@ export default class Element extends Node { this.namespace = get_namespace(parent as Element, this, component.namespace); if (this.namespace !== namespaces.foreign) { + if (this.name === 'pre' || this.name === 'textarea') { + const first = info.children[0]; + if (first && first.type === 'Text') { + // The leading newline character should be stripped. + // see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions + // see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element + first.data = first.data.replace(start_newline, ''); + } + } + if (this.name === 'textarea') { if (info.children.length > 0) { const value_attribute = info.attributes.find(node => node.name === 'value'); @@ -208,12 +218,6 @@ export default class Element extends Node { // this is an egregious hack, but it's the easiest way to get -
-
- +
+
-
- - + +