From 75197c9aaaeb68a116fc07961362a3630d4ab1a9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 6 Jan 2025 19:14:00 -0500 Subject: [PATCH 1/4] simpler fix --- .../src/compiler/phases/3-transform/utils.js | 16 ++++++++++++++++ .../pre-first-node-backslash-n/_config.js | 3 +++ .../pre-first-node-backslash-n/_expected.html | 2 ++ .../pre-first-node-backslash-n/main.svelte | 7 +++++++ 4 files changed, 28 insertions(+) create mode 100644 packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_config.js create mode 100644 packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_expected.html create mode 100644 packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/main.svelte diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index b53a3110bd22..9360b5122fcd 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -270,6 +270,22 @@ export function clean_nodes( var first = trimmed[0]; + // initial newline inside a `
` is disregarded
+	if (
+		parent.type === 'RegularElement' &&
+		parent.name === 'pre' &&
+		first.type === 'Text' &&
+		first.data[0] === '\n'
+	) {
+		first.data = first.data.slice(1);
+		first.raw = first.raw.slice(1);
+
+		if (first.data === '') {
+			trimmed.shift();
+			first = trimmed[0];
+		}
+	}
+
 	// Special case: Add a comment if this is a lone script tag. This ensures that our run_scripts logic in template.js
 	// will always be able to call node.replaceWith() on the script tag in order to make it run. If we don't add this
 	// and would still call node.replaceWith() on the script tag, it would be a no-op because the script tag has no parent.
diff --git a/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_config.js b/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_config.js
new file mode 100644
index 000000000000..f47bee71df87
--- /dev/null
+++ b/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_config.js
@@ -0,0 +1,3 @@
+import { test } from '../../test';
+
+export default test({});
diff --git a/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_expected.html b/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_expected.html
new file mode 100644
index 000000000000..b2e80650314e
--- /dev/null
+++ b/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_expected.html
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file diff --git a/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/main.svelte b/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/main.svelte new file mode 100644 index 000000000000..abd29eed72d9 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/main.svelte @@ -0,0 +1,7 @@ + + +
+
{name}
+
\ No newline at end of file From b40f677e69fd331bbe1d3ea03a6ddf20ad5d7e06 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 6 Jan 2025 19:20:23 -0500 Subject: [PATCH 2/4] fix test --- .../svelte/tests/runtime-legacy/samples/pre-tag/_config.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js b/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js index f1a42e996a77..194499bd5be8 100644 --- a/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js @@ -31,9 +31,7 @@ function get_html(ssr) { E F -
leading newline
  leading newline and spaces
-leading newlines
without spaces
  with spaces  
${' '}
+  
leading newline
  leading newline and spaces
leading newlines
without spaces
  with spaces  
${' '}
 newline after leading space
-
 multiple leading newlines
${ssr ? '' : ''}`; } From 2ea4c02942cc14781327371bed67828c6631cb05 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 6 Jan 2025 19:20:56 -0500 Subject: [PATCH 3/4] changeset --- .changeset/tricky-radios-vanish.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tricky-radios-vanish.md diff --git a/.changeset/tricky-radios-vanish.md b/.changeset/tricky-radios-vanish.md new file mode 100644 index 000000000000..0935148fba6f --- /dev/null +++ b/.changeset/tricky-radios-vanish.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: remove leading newline from `
` contents

From af3d03eac37f15aa030258fb8ab1ad370dbb3f05 Mon Sep 17 00:00:00 2001
From: Simon Holthausen 
Date: Tue, 7 Jan 2025 10:57:52 +0100
Subject: [PATCH 4/4] proper fix

---
 .../src/compiler/phases/3-transform/utils.js  | 27 ++++++++++---------
 .../pre-first-node-backslash-n/_config.js     |  3 ---
 .../pre-first-node-backslash-n/_expected.html |  2 --
 .../pre-first-node-backslash-n/main.svelte    |  7 -----
 .../samples/pre-first-node-newline/_config.js |  5 ++++
 .../pre-first-node-newline/_expected.html     |  7 +++++
 .../pre-first-node-newline/main.svelte        | 23 ++++++++++++++++
 .../runtime-legacy/samples/pre-tag/_config.js | 22 ++++++---------
 8 files changed, 57 insertions(+), 39 deletions(-)
 delete mode 100644 packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_config.js
 delete mode 100644 packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_expected.html
 delete mode 100644 packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/main.svelte
 create mode 100644 packages/svelte/tests/hydration/samples/pre-first-node-newline/_config.js
 create mode 100644 packages/svelte/tests/hydration/samples/pre-first-node-newline/_expected.html
 create mode 100644 packages/svelte/tests/hydration/samples/pre-first-node-newline/main.svelte

diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js
index 9360b5122fcd..62a635de3536 100644
--- a/packages/svelte/src/compiler/phases/3-transform/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/utils.js
@@ -5,6 +5,7 @@
 import {
 	regex_ends_with_whitespaces,
 	regex_not_whitespace,
+	regex_starts_with_newline,
 	regex_starts_with_whitespaces
 } from '../patterns.js';
 import * as b from '../../utils/builders.js';
@@ -270,19 +271,19 @@ export function clean_nodes(
 
 	var first = trimmed[0];
 
-	// initial newline inside a `
` is disregarded
-	if (
-		parent.type === 'RegularElement' &&
-		parent.name === 'pre' &&
-		first.type === 'Text' &&
-		first.data[0] === '\n'
-	) {
-		first.data = first.data.slice(1);
-		first.raw = first.raw.slice(1);
-
-		if (first.data === '') {
-			trimmed.shift();
-			first = trimmed[0];
+	// initial newline inside a `
` is disregarded, if not followed by another newline
+	if (parent.type === 'RegularElement' && parent.name === 'pre' && first.type === 'Text') {
+		const text = first.data.replace(regex_starts_with_newline, '');
+		if (text !== first.data) {
+			const tmp = text.replace(regex_starts_with_newline, '');
+			if (text === tmp) {
+				first.data = text;
+				first.raw = first.raw.replace(regex_starts_with_newline, '');
+				if (first.data === '') {
+					trimmed.shift();
+					first = trimmed[0];
+				}
+			}
 		}
 	}
 
diff --git a/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_config.js b/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_config.js
deleted file mode 100644
index f47bee71df87..000000000000
--- a/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import { test } from '../../test';
-
-export default test({});
diff --git a/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_expected.html b/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_expected.html
deleted file mode 100644
index b2e80650314e..000000000000
--- a/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/_expected.html
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file diff --git a/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/main.svelte b/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/main.svelte deleted file mode 100644 index abd29eed72d9..000000000000 --- a/packages/svelte/tests/hydration/samples/pre-first-node-backslash-n/main.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - -
-
{name}
-
\ No newline at end of file diff --git a/packages/svelte/tests/hydration/samples/pre-first-node-newline/_config.js b/packages/svelte/tests/hydration/samples/pre-first-node-newline/_config.js new file mode 100644 index 000000000000..c838aab749cd --- /dev/null +++ b/packages/svelte/tests/hydration/samples/pre-first-node-newline/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +// A note about _expected.html: It is different from body.html because we're +// testing against target.innerHTML which already removed the redundant first newline +export default test({}); diff --git a/packages/svelte/tests/hydration/samples/pre-first-node-newline/_expected.html b/packages/svelte/tests/hydration/samples/pre-first-node-newline/_expected.html new file mode 100644 index 000000000000..26de34d1ed27 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/pre-first-node-newline/_expected.html @@ -0,0 +1,7 @@ +
static content no line
	static content ignored line
+
+	static content relevant line
+
+
+
+
diff --git a/packages/svelte/tests/hydration/samples/pre-first-node-newline/main.svelte b/packages/svelte/tests/hydration/samples/pre-first-node-newline/main.svelte new file mode 100644 index 000000000000..9764b49e99d1 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/pre-first-node-newline/main.svelte @@ -0,0 +1,23 @@ + + +
static content no line
+ +
+	static content ignored line
+
+ +
+
+	static content relevant line
+
+ +
+
{name}
+
+ +
+
+
{name}
+
diff --git a/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js b/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js index 194499bd5be8..d7e1fe9d43a3 100644 --- a/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js @@ -2,17 +2,9 @@ import { test } from '../../test'; export default test({ mode: ['client', 'server'], // output is correct, but test suite chokes on the extra ssr comment which is harmless - withoutNormalizeHtml: true, - html: get_html(false), - ssrHtml: get_html(true) -}); - -/** @param {boolean} ssr */ -function get_html(ssr) { - // ssr rendered HTML has an extra newline prefixed within `
` tag,
-	// if the 
 tag starts with `\n`
-	// because when browser parses the SSR rendered HTML, it will ignore the 1st '\n' character
-	return `${ssr ? '' : ''}
  A
+	withoutNormalizeHtml: 'only-strip-comments', // because whitespace inside pre tags is significant
+	// Note how we're testing against target.innerHTML which already removed the redundant first newline
+	html: `
  A
   B
   
     C
@@ -31,7 +23,9 @@ function get_html(ssr) {
     
     E
     F
-  
leading newline
  leading newline and spaces
leading newlines
without spaces
  with spaces  
${' '}
+  
leading newline
  leading newline and spaces
+leading newlines
without spaces
  with spaces  
${' '}
 newline after leading space
-multiple leading newlines
${ssr ? '' : ''}`; -} + +multiple leading newlines
` +});