From c66a43940aee4c24f4c003a50b433e64d316e40a Mon Sep 17 00:00:00 2001 From: 7nik Date: Tue, 20 May 2025 17:50:54 +0300 Subject: [PATCH 01/11] feat: warn on implicitly closed tags (#15932) * warn on implicitly closed tags * lint * changeset * separate tests for parent/sibling case * account for attributes in opening tag * tweak * update message --------- Co-authored-by: 7nik Co-authored-by: Rich Harris --- .changeset/blue-experts-tell.md | 5 ++++ .../.generated/compile-warnings.md | 19 ++++++++++++++ .../messages/compile-warnings/template.md | 17 ++++++++++++ .../compiler/phases/1-parse/state/element.js | 13 +++++++++- packages/svelte/src/compiler/warnings.js | 11 ++++++++ .../implicitly-closed-by-parent/input.svelte | 6 +++++ .../implicitly-closed-by-parent/warnings.json | 26 +++++++++++++++++++ .../implicitly-closed-by-sibling/input.svelte | 9 +++++++ .../warnings.json | 26 +++++++++++++++++++ 9 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 .changeset/blue-experts-tell.md create mode 100644 packages/svelte/tests/validator/samples/implicitly-closed-by-parent/input.svelte create mode 100644 packages/svelte/tests/validator/samples/implicitly-closed-by-parent/warnings.json create mode 100644 packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/input.svelte create mode 100644 packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/warnings.json diff --git a/.changeset/blue-experts-tell.md b/.changeset/blue-experts-tell.md new file mode 100644 index 0000000000..a12fb126d1 --- /dev/null +++ b/.changeset/blue-experts-tell.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: warn on implicitly closed tags diff --git a/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md index 7069f90206..b579d38602 100644 --- a/documentation/docs/98-reference/.generated/compile-warnings.md +++ b/documentation/docs/98-reference/.generated/compile-warnings.md @@ -632,6 +632,25 @@ In some situations a selector may target an element that is not 'visible' to the ``` +### element_implicitly_closed + +``` +This element is implicitly closed by the following `%tag%`, which can cause an unexpected DOM structure. Add an explicit `%closing%` to avoid surprises. +``` + +In HTML, some elements are implicitly closed by another element. For example, you cannot nest a `

` inside another `

`: + +```html + +

hello

+ + +

+

hello

+``` + +Similarly, a parent element's closing tag will implicitly close all child elements, even if the ` `<%name%>` will be treated as an HTML element unless it begins with a capital letter +## element_implicitly_closed + +> This element is implicitly closed by the following `%tag%`, which can cause an unexpected DOM structure. Add an explicit `%closing%` to avoid surprises. + +In HTML, some elements are implicitly closed by another element. For example, you cannot nest a `

` inside another `

`: + +```html + +

hello

+ + +

+

hello

+``` + +Similarly, a parent element's closing tag will implicitly close all child elements, even if the ` Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...>` rather than `<%name% ... />` diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index d20369b0d9..6b6c9160d8 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -93,7 +93,16 @@ export default function element(parser) { } } - if (parent.type !== 'RegularElement' && !parser.loose) { + if (parent.type === 'RegularElement') { + if (!parser.last_auto_closed_tag || parser.last_auto_closed_tag.tag !== name) { + const end = parent.fragment.nodes[0]?.start ?? start; + w.element_implicitly_closed( + { start: parent.start, end }, + ``, + `` + ); + } + } else if (!parser.loose) { if (parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name) { e.element_invalid_closing_tag_autoclosed(start, name, parser.last_auto_closed_tag.reason); } else { @@ -186,6 +195,8 @@ export default function element(parser) { parser.allow_whitespace(); if (parent.type === 'RegularElement' && closing_tag_omitted(parent.name, name)) { + const end = parent.fragment.nodes[0]?.start ?? start; + w.element_implicitly_closed({ start: parent.start, end }, `<${name}>`, ``); parent.end = start; parser.pop(); parser.last_auto_closed_tag = { diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index c281433213..2a8316308c 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -114,6 +114,7 @@ export const codes = [ 'bind_invalid_each_rest', 'block_empty', 'component_name_lowercase', + 'element_implicitly_closed', 'element_invalid_self_closing_tag', 'event_directive_deprecated', 'node_invalid_placement_ssr', @@ -746,6 +747,16 @@ export function component_name_lowercase(node, name) { w(node, 'component_name_lowercase', `\`<${name}>\` will be treated as an HTML element unless it begins with a capital letter\nhttps://svelte.dev/e/component_name_lowercase`); } +/** + * This element is implicitly closed by the following `%tag%`, which can cause an unexpected DOM structure. Add an explicit `%closing%` to avoid surprises. + * @param {null | NodeLike} node + * @param {string} tag + * @param {string} closing + */ +export function element_implicitly_closed(node, tag, closing) { + w(node, 'element_implicitly_closed', `This element is implicitly closed by the following \`${tag}\`, which can cause an unexpected DOM structure. Add an explicit \`${closing}\` to avoid surprises.\nhttps://svelte.dev/e/element_implicitly_closed`); +} + /** * Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...>` rather than `<%name% ... />` * @param {null | NodeLike} node diff --git a/packages/svelte/tests/validator/samples/implicitly-closed-by-parent/input.svelte b/packages/svelte/tests/validator/samples/implicitly-closed-by-parent/input.svelte new file mode 100644 index 0000000000..f67eba18b8 --- /dev/null +++ b/packages/svelte/tests/validator/samples/implicitly-closed-by-parent/input.svelte @@ -0,0 +1,6 @@ +
+ +
+
+

hello

+
diff --git a/packages/svelte/tests/validator/samples/implicitly-closed-by-parent/warnings.json b/packages/svelte/tests/validator/samples/implicitly-closed-by-parent/warnings.json new file mode 100644 index 0000000000..1316a2b65b --- /dev/null +++ b/packages/svelte/tests/validator/samples/implicitly-closed-by-parent/warnings.json @@ -0,0 +1,26 @@ +[ + { + "code": "element_implicitly_closed", + "message": "This element is implicitly closed by the following ``, which can cause an unexpected DOM structure. Add an explicit `` to avoid surprises.", + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 25 + } + }, + { + "code": "element_implicitly_closed", + "message": "This element is implicitly closed by the following ``, which can cause an unexpected DOM structure. Add an explicit `` to avoid surprises.", + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 20 + } + } +] diff --git a/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/input.svelte b/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/input.svelte new file mode 100644 index 0000000000..7721f2f380 --- /dev/null +++ b/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/input.svelte @@ -0,0 +1,9 @@ +
+

+ +

+
+ +
+

+
diff --git a/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/warnings.json b/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/warnings.json new file mode 100644 index 0000000000..6ea36c5a50 --- /dev/null +++ b/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/warnings.json @@ -0,0 +1,26 @@ +[ + { + "code": "element_implicitly_closed", + "message": "This element is implicitly closed by the following `

`, which can cause an unexpected DOM structure. Add an explicit `

` to avoid surprises.", + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 18 + } + }, + { + "code": "element_implicitly_closed", + "message": "This element is implicitly closed by the following `

`, which can cause an unexpected DOM structure. Add an explicit `

` to avoid surprises.", + "start": { + "line": 8, + "column": 1 + }, + "end": { + "line": 8, + "column": 18 + } + } +] From 93443f93160792d49a85b6b8ac7571abd3fccede Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 20 May 2025 11:13:55 -0400 Subject: [PATCH 02/11] fix: only re-run directly applied attachment if it changed (#15962) * fix: only re-run directly applied attachment if it changed * add note * one down * fix * Revert "one down" This reverts commit 9f6c4cf84838de8d8cb93e7217de37871839ac8b. --- .changeset/slimy-drinks-divide.md | 5 ++++ .../client/dom/elements/attachments.js | 30 +++++++++++++++---- .../attachment-in-mutated-state/_config.js | 16 ++++++++++ .../attachment-in-mutated-state/main.svelte | 15 ++++++++++ 4 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 .changeset/slimy-drinks-divide.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/main.svelte diff --git a/.changeset/slimy-drinks-divide.md b/.changeset/slimy-drinks-divide.md new file mode 100644 index 0000000000..420d0bed56 --- /dev/null +++ b/.changeset/slimy-drinks-divide.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: only re-run directly applied attachment if it changed diff --git a/packages/svelte/src/internal/client/dom/elements/attachments.js b/packages/svelte/src/internal/client/dom/elements/attachments.js index 6e3089a384..4fc1280138 100644 --- a/packages/svelte/src/internal/client/dom/elements/attachments.js +++ b/packages/svelte/src/internal/client/dom/elements/attachments.js @@ -1,15 +1,33 @@ -import { effect } from '../../reactivity/effects.js'; +/** @import { Effect } from '#client' */ +import { block, branch, effect, destroy_effect } from '../../reactivity/effects.js'; + +// TODO in 6.0 or 7.0, when we remove legacy mode, we can simplify this by +// getting rid of the block/branch stuff and just letting the effect rip. +// see https://github.com/sveltejs/svelte/pull/15962 /** * @param {Element} node * @param {() => (node: Element) => void} get_fn */ export function attach(node, get_fn) { - effect(() => { - const fn = get_fn(); + /** @type {false | undefined | ((node: Element) => void)} */ + var fn = undefined; + + /** @type {Effect | null} */ + var e; + + block(() => { + if (fn !== (fn = get_fn())) { + if (e) { + destroy_effect(e); + e = null; + } - // we use `&&` rather than `?.` so that things like - // `{@attach DEV && something_dev_only()}` work - return fn && fn(node); + if (fn) { + e = branch(() => { + effect(() => /** @type {(node: Element) => void} */ (fn)(node)); + }); + } + } }); } diff --git a/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/_config.js b/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/_config.js new file mode 100644 index 0000000000..5d37252358 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target, logs }) { + assert.deepEqual(logs, ['up']); + + const button = target.querySelector('button'); + + flushSync(() => button?.click()); + assert.deepEqual(logs, ['up']); + + flushSync(() => button?.click()); + assert.deepEqual(logs, ['up', 'down']); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/main.svelte b/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/main.svelte new file mode 100644 index 0000000000..fbec108c7a --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/attachment-in-mutated-state/main.svelte @@ -0,0 +1,15 @@ + + + + +{#if state.count < 2} +
+{/if} From 21d5448ab72c1e0cf5dfd6ec989489d4f57516f7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 11:28:43 -0400 Subject: [PATCH 03/11] Version Packages (#15959) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/blue-experts-tell.md | 5 ----- .changeset/slimy-drinks-divide.md | 5 ----- .changeset/wise-tigers-happen.md | 5 ----- packages/svelte/CHANGELOG.md | 12 ++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 6 files changed, 14 insertions(+), 17 deletions(-) delete mode 100644 .changeset/blue-experts-tell.md delete mode 100644 .changeset/slimy-drinks-divide.md delete mode 100644 .changeset/wise-tigers-happen.md diff --git a/.changeset/blue-experts-tell.md b/.changeset/blue-experts-tell.md deleted file mode 100644 index a12fb126d1..0000000000 --- a/.changeset/blue-experts-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': minor ---- - -feat: warn on implicitly closed tags diff --git a/.changeset/slimy-drinks-divide.md b/.changeset/slimy-drinks-divide.md deleted file mode 100644 index 420d0bed56..0000000000 --- a/.changeset/slimy-drinks-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: only re-run directly applied attachment if it changed diff --git a/.changeset/wise-tigers-happen.md b/.changeset/wise-tigers-happen.md deleted file mode 100644 index 53cc671859..0000000000 --- a/.changeset/wise-tigers-happen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': minor ---- - -feat: attachments `fromAction` utility diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 4508a9b054..270460e5ed 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.32.0 + +### Minor Changes + +- feat: warn on implicitly closed tags ([#15932](https://github.com/sveltejs/svelte/pull/15932)) + +- feat: attachments `fromAction` utility ([#15933](https://github.com/sveltejs/svelte/pull/15933)) + +### Patch Changes + +- fix: only re-run directly applied attachment if it changed ([#15962](https://github.com/sveltejs/svelte/pull/15962)) + ## 5.31.1 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 45a875d19b..d374880887 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.31.1", + "version": "5.32.0", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index e42e6741bd..9a198d097d 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.31.1'; +export const VERSION = '5.32.0'; export const PUBLIC_VERSION = '5'; From de8094910de7e2d491c2f26b891844c2f311f78f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gautier=20Ben=20A=C3=AFm?= <48261497+GauBen@users.noreply.github.com> Date: Tue, 20 May 2025 19:32:47 +0200 Subject: [PATCH 04/11] fix(14815): warn when an invalid value is given * Create nervous-stingrays-travel.md --- .changeset/nervous-stingrays-travel.md | 5 +++++ .../.generated/client-warnings.md | 13 +++++++++++++ .../messages/client-warnings/warnings.md | 11 +++++++++++ .../client/dom/elements/bindings/select.js | 19 +++++++++++++++---- .../svelte/src/internal/client/warnings.js | 11 +++++++++++ .../select-multiple-invalid-value/_config.js | 7 +++++++ .../select-multiple-invalid-value/main.svelte | 9 +++++++++ 7 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 .changeset/nervous-stingrays-travel.md create mode 100644 packages/svelte/tests/runtime-runes/samples/select-multiple-invalid-value/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/select-multiple-invalid-value/main.svelte diff --git a/.changeset/nervous-stingrays-travel.md b/.changeset/nervous-stingrays-travel.md new file mode 100644 index 0000000000..9ccc3e6c65 --- /dev/null +++ b/.changeset/nervous-stingrays-travel.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +Warn when an invalid `` element should be an array, but it received a non-array value. The selection will be kept as is. +``` + +When using `` element should be an array, but it received a non-array value. The selection will be kept as is. + +When using `` element should be an array, but it received a non-array value. The selection will be kept as is. + */ +export function select_multiple_invalid_value() { + if (DEV) { + console.warn(`%c[svelte] select_multiple_invalid_value\n%cThe \`value\` property of a \`` element should be an array, but it received a non-array value. The selection will be kept as is.' + ] +}); diff --git a/packages/svelte/tests/runtime-runes/samples/select-multiple-invalid-value/main.svelte b/packages/svelte/tests/runtime-runes/samples/select-multiple-invalid-value/main.svelte new file mode 100644 index 0000000000..f3dfedf0e9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/select-multiple-invalid-value/main.svelte @@ -0,0 +1,9 @@ + + + From ef48d35a15fc104efd6ee5cf13eceea3ace4ae0d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 14:13:31 -0400 Subject: [PATCH 05/11] Version Packages (#15967) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/nervous-stingrays-travel.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/nervous-stingrays-travel.md diff --git a/.changeset/nervous-stingrays-travel.md b/.changeset/nervous-stingrays-travel.md deleted file mode 100644 index 9ccc3e6c65..0000000000 --- a/.changeset/nervous-stingrays-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"svelte": patch ---- - -Warn when an invalid `` value is given ([#14816](https://github.com/sveltejs/svelte/pull/14816)) + ## 5.32.0 ### Minor Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index d374880887..64fa308000 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.32.0", + "version": "5.32.1", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 9a198d097d..c6748487fb 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.32.0'; +export const VERSION = '5.32.1'; export const PUBLIC_VERSION = '5'; From 4e72b7d28b5d8f469ee7ffaee02f94a111823bd4 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Wed, 21 May 2025 15:18:33 +0200 Subject: [PATCH 06/11] fix: import `untrack` directly from client in `svelte/attachments` (#15974) --- .changeset/short-ads-rhyme.md | 5 +++++ packages/svelte/src/attachments/index.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/short-ads-rhyme.md diff --git a/.changeset/short-ads-rhyme.md b/.changeset/short-ads-rhyme.md new file mode 100644 index 0000000000..31434530ab --- /dev/null +++ b/.changeset/short-ads-rhyme.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: import untrack directly from client in `svelte/attachments` diff --git a/packages/svelte/src/attachments/index.js b/packages/svelte/src/attachments/index.js index dc2f3e93d7..b9fde9b6d9 100644 --- a/packages/svelte/src/attachments/index.js +++ b/packages/svelte/src/attachments/index.js @@ -2,7 +2,7 @@ /** @import { Attachment } from './public' */ import { noop, render_effect } from 'svelte/internal/client'; import { ATTACHMENT_KEY } from '../constants.js'; -import { untrack } from 'svelte'; +import { untrack } from '../index-client.js'; import { teardown } from '../internal/client/reactivity/effects.js'; /** From c03ea47e4ef88782a0f43b3157d7712a03c44698 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 21 May 2025 15:09:21 -0400 Subject: [PATCH 07/11] chore: simplify `
` cleaning (#15980)

---
 .changeset/fifty-llamas-know.md                 |  5 +++++
 .../src/compiler/phases/3-transform/utils.js    | 17 +++++------------
 2 files changed, 10 insertions(+), 12 deletions(-)
 create mode 100644 .changeset/fifty-llamas-know.md

diff --git a/.changeset/fifty-llamas-know.md b/.changeset/fifty-llamas-know.md
new file mode 100644
index 0000000000..f11c975f3d
--- /dev/null
+++ b/.changeset/fifty-llamas-know.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+chore: simplify `
` cleaning
diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js
index 5aa40c8abb..5803c04947 100644
--- a/packages/svelte/src/compiler/phases/3-transform/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/utils.js
@@ -271,19 +271,12 @@ export function clean_nodes(
 
 	var first = trimmed[0];
 
-	// initial newline inside a `
` is disregarded, if not followed by another newline
+	// if first text node inside a 
 is a single newline, discard it, because otherwise
+	// the browser will do it for us which could break hydration
 	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];
-				}
-			}
+		if (first.data === '\n' || first.data === '\r\n') {
+			trimmed.shift();
+			first = trimmed[0];
 		}
 	}
 

From 50de8c53178d61f675c5fa1d2d1fc88ae94bb0cd Mon Sep 17 00:00:00 2001
From: Rich Harris 
Date: Wed, 21 May 2025 19:45:07 -0400
Subject: [PATCH 08/11] fix: attach __svelte_meta correctly to elements
 following a CSS wrapper (#15982)

* fix: attach __svelte_meta correctly to elements following a CSS wrapper

* Update .changeset/nervous-hotels-clean.md
---
 .changeset/nervous-hotels-clean.md            |  5 +++
 .../client/visitors/shared/component.js       |  9 +++-
 .../svelte-meta-css-wrapper/Component.svelte  |  7 ++++
 .../svelte-meta-css-wrapper/_config.js        | 42 +++++++++++++++++++
 .../svelte-meta-css-wrapper/main.svelte       |  7 ++++
 5 files changed, 69 insertions(+), 1 deletion(-)
 create mode 100644 .changeset/nervous-hotels-clean.md
 create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/Component.svelte
 create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/_config.js
 create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/main.svelte

diff --git a/.changeset/nervous-hotels-clean.md b/.changeset/nervous-hotels-clean.md
new file mode 100644
index 0000000000..77d5c186c7
--- /dev/null
+++ b/.changeset/nervous-hotels-clean.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: attach `__svelte_meta` correctly to elements following a CSS wrapper
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js
index 6d3d8a68e6..99ed294d26 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js
@@ -1,7 +1,7 @@
 /** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, Pattern, Property, SequenceExpression, Statement } from 'estree' */
 /** @import { AST } from '#compiler' */
 /** @import { ComponentContext } from '../../types.js' */
-import { dev, is_ignored } from '../../../../../state.js';
+import { dev, is_ignored, locator } from '../../../../../state.js';
 import { get_attribute_chunks, object } from '../../../../../utils/ast.js';
 import * as b from '#compiler/builders';
 import { build_bind_this, memoize_expression, validate_binding } from '../shared/utils.js';
@@ -440,6 +440,13 @@ export function build_component(node, component_name, context, anchor = context.
 	}
 
 	if (Object.keys(custom_css_props).length > 0) {
+		if (dev) {
+			const loc = locator(node.start);
+			if (loc) {
+				context.state.locations.push([loc.line, loc.column]);
+			}
+		}
+
 		context.state.template.push(
 			context.state.metadata.namespace === 'svg'
 				? ''
diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/Component.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/Component.svelte
new file mode 100644
index 0000000000..5668311b0e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/Component.svelte
@@ -0,0 +1,7 @@
+

hello from component

+ + diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/_config.js new file mode 100644 index 0000000000..1d76b0dd00 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/_config.js @@ -0,0 +1,42 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + + html: ` +

hello

+ +

hello from component

+
+

goodbye

+ `, + + async test({ target, assert }) { + const h1 = target.querySelector('h1'); + const h2 = target.querySelector('h2'); + const p = target.querySelector('p'); + + // @ts-expect-error + assert.deepEqual(h1.__svelte_meta.loc, { + file: 'main.svelte', + line: 5, + column: 0 + }); + + // @ts-expect-error + assert.deepEqual(h2.__svelte_meta.loc, { + file: 'Component.svelte', + line: 1, + column: 0 + }); + + // @ts-expect-error + assert.deepEqual(p.__svelte_meta.loc, { + file: 'main.svelte', + line: 7, + column: 0 + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/main.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/main.svelte new file mode 100644 index 0000000000..f49a48fb52 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-meta-css-wrapper/main.svelte @@ -0,0 +1,7 @@ + + +

hello

+ +

goodbye

From 5a840982a7d06d1aaea59db8352fb2bf0188c0ec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 11:18:39 -0400 Subject: [PATCH 09/11] Version Packages (#15978) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/fifty-llamas-know.md | 5 ----- .changeset/nervous-hotels-clean.md | 5 ----- .changeset/short-ads-rhyme.md | 5 ----- packages/svelte/CHANGELOG.md | 10 ++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 6 files changed, 12 insertions(+), 17 deletions(-) delete mode 100644 .changeset/fifty-llamas-know.md delete mode 100644 .changeset/nervous-hotels-clean.md delete mode 100644 .changeset/short-ads-rhyme.md diff --git a/.changeset/fifty-llamas-know.md b/.changeset/fifty-llamas-know.md deleted file mode 100644 index f11c975f3d..0000000000 --- a/.changeset/fifty-llamas-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: simplify `
` cleaning
diff --git a/.changeset/nervous-hotels-clean.md b/.changeset/nervous-hotels-clean.md
deleted file mode 100644
index 77d5c186c7..0000000000
--- a/.changeset/nervous-hotels-clean.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: attach `__svelte_meta` correctly to elements following a CSS wrapper
diff --git a/.changeset/short-ads-rhyme.md b/.changeset/short-ads-rhyme.md
deleted file mode 100644
index 31434530ab..0000000000
--- a/.changeset/short-ads-rhyme.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: import untrack directly from client in `svelte/attachments`
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index b65a941bd9..4c65954c9f 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,15 @@
 # svelte
 
+## 5.32.2
+
+### Patch Changes
+
+- chore: simplify `
` cleaning ([#15980](https://github.com/sveltejs/svelte/pull/15980))
+
+- fix: attach `__svelte_meta` correctly to elements following a CSS wrapper ([#15982](https://github.com/sveltejs/svelte/pull/15982))
+
+- fix: import untrack directly from client in `svelte/attachments` ([#15974](https://github.com/sveltejs/svelte/pull/15974))
+
 ## 5.32.1
 
 ### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 64fa308000..168e7abe37 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
   "name": "svelte",
   "description": "Cybernetically enhanced web apps",
   "license": "MIT",
-  "version": "5.32.1",
+  "version": "5.32.2",
   "type": "module",
   "types": "./types/index.d.ts",
   "engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index c6748487fb..c93d7e2e25 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
  * The current version, as set in package.json.
  * @type {string}
  */
-export const VERSION = '5.32.1';
+export const VERSION = '5.32.2';
 export const PUBLIC_VERSION = '5';

From d1141a19b5eaa156d3cd39cb756f0a69dd3c3b8e Mon Sep 17 00:00:00 2001
From: Paolo Ricciuti 
Date: Thu, 22 May 2025 17:21:50 +0200
Subject: [PATCH 10/11] feat: functional template generation (#15538)

Co-authored-by: Rich Harris 
---
 .changeset/forty-llamas-unite.md              |   5 +
 .changeset/smart-boats-accept.md              |   5 +
 .../3-transform/client/transform-client.js    |   7 +-
 .../fix-attribute-casing.js                   |  18 ++
 .../client/transform-template/index.js        |  68 ++++++++
 .../client/transform-template/template.js     | 162 ++++++++++++++++++
 .../client/transform-template/types.d.ts      |  22 +++
 .../phases/3-transform/client/types.d.ts      |  23 +--
 .../3-transform/client/visitors/AwaitBlock.js |   2 +-
 .../3-transform/client/visitors/Comment.js    |   2 +-
 .../3-transform/client/visitors/EachBlock.js  |   2 +-
 .../3-transform/client/visitors/Fragment.js   | 136 ++-------------
 .../3-transform/client/visitors/HtmlTag.js    |   2 +-
 .../3-transform/client/visitors/IfBlock.js    |   2 +-
 .../3-transform/client/visitors/KeyBlock.js   |   2 +-
 .../client/visitors/RegularElement.js         |  66 ++-----
 .../3-transform/client/visitors/RenderTag.js  |   2 +-
 .../client/visitors/SlotElement.js            |   2 +-
 .../client/visitors/SvelteBoundary.js         |   2 +-
 .../client/visitors/SvelteElement.js          |   2 +-
 .../client/visitors/shared/component.js       |  23 ++-
 .../client/visitors/shared/fragment.js        |   4 +-
 .../server/visitors/RegularElement.js         |   6 +-
 .../src/compiler/phases/3-transform/utils.js  |   2 +-
 packages/svelte/src/compiler/types/index.d.ts |   9 +
 .../svelte/src/compiler/validate-options.js   |   2 +
 packages/svelte/src/constants.js              |   2 +
 .../src/internal/client/dev/elements.js       |   2 +-
 .../src/internal/client/dom/operations.js     |  41 +++++
 .../src/internal/client/dom/reconciler.js     |   2 +-
 .../src/internal/client/dom/template.js       | 145 +++++++++++++---
 .../svelte/src/internal/client/dom/types.d.ts |   4 +
 packages/svelte/src/internal/client/index.js  |  12 +-
 .../svelte/src/internal/client/types.d.ts     |   4 +
 .../svelte/src/internal/shared/types.d.ts     |   4 -
 packages/svelte/tests/helpers.js              |   2 +
 packages/svelte/tests/hydration/test.ts       |  11 +-
 packages/svelte/tests/runtime-browser/test.ts |   3 +-
 .../svelte/tests/runtime-legacy/shared.ts     |   3 +-
 .../custom-element-attributes/main.svelte     |  26 +--
 .../samples/functional-templating/_config.js  |   9 +
 .../samples/functional-templating/main.svelte |   1 +
 .../_expected/client/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   2 +-
 .../_expected/client/main.svelte.js           |   2 +-
 .../_expected/client/index.svelte.js          |   2 +-
 .../samples/functional-templating/_config.js  |   7 +
 .../_expected/client/index.svelte.js          |  25 +++
 .../_expected/server/index.svelte.js          |   5 +
 .../functional-templating/index.svelte        |   6 +
 .../_expected/client/index.svelte.js          |   2 +-
 .../hmr/_expected/client/index.svelte.js      |   2 +-
 .../_expected/client/index.svelte.js          |   2 +-
 .../purity/_expected/client/index.svelte.js   |   2 +-
 .../_expected/client/index.svelte.js          |   2 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   2 +-
 .../_expected/server/index.svelte.js          |   2 +-
 .../_expected/client/index.svelte.js          |   2 +-
 packages/svelte/types/index.d.ts              |  18 ++
 playgrounds/sandbox/run.js                    |  18 +-
 61 files changed, 666 insertions(+), 288 deletions(-)
 create mode 100644 .changeset/forty-llamas-unite.md
 create mode 100644 .changeset/smart-boats-accept.md
 create mode 100644 packages/svelte/src/compiler/phases/3-transform/client/transform-template/fix-attribute-casing.js
 create mode 100644 packages/svelte/src/compiler/phases/3-transform/client/transform-template/index.js
 create mode 100644 packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js
 create mode 100644 packages/svelte/src/compiler/phases/3-transform/client/transform-template/types.d.ts
 create mode 100644 packages/svelte/src/internal/client/dom/types.d.ts
 create mode 100644 packages/svelte/tests/runtime-runes/samples/functional-templating/_config.js
 create mode 100644 packages/svelte/tests/runtime-runes/samples/functional-templating/main.svelte
 create mode 100644 packages/svelte/tests/snapshot/samples/functional-templating/_config.js
 create mode 100644 packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js
 create mode 100644 packages/svelte/tests/snapshot/samples/functional-templating/_expected/server/index.svelte.js
 create mode 100644 packages/svelte/tests/snapshot/samples/functional-templating/index.svelte

diff --git a/.changeset/forty-llamas-unite.md b/.changeset/forty-llamas-unite.md
new file mode 100644
index 0000000000..933cece1c6
--- /dev/null
+++ b/.changeset/forty-llamas-unite.md
@@ -0,0 +1,5 @@
+---
+'svelte': minor
+---
+
+feat: XHTML compliance
diff --git a/.changeset/smart-boats-accept.md b/.changeset/smart-boats-accept.md
new file mode 100644
index 0000000000..4cdfeb30d0
--- /dev/null
+++ b/.changeset/smart-boats-accept.md
@@ -0,0 +1,5 @@
+---
+'svelte': minor
+---
+
+feat: add `fragments: 'html' | 'tree'` option for wider CSP compliance
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
index f2eda3a7d2..e2e006c14b 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
@@ -154,10 +154,6 @@ export function client_component(analysis, options) {
 		legacy_reactive_imports: [],
 		legacy_reactive_statements: new Map(),
 		metadata: {
-			context: {
-				template_needs_import_node: false,
-				template_contains_script_tag: false
-			},
 			namespace: options.namespace,
 			bound_contenteditable: false
 		},
@@ -174,8 +170,7 @@ export function client_component(analysis, options) {
 		update: /** @type {any} */ (null),
 		expressions: /** @type {any} */ (null),
 		after_update: /** @type {any} */ (null),
-		template: /** @type {any} */ (null),
-		locations: /** @type {any} */ (null)
+		template: /** @type {any} */ (null)
 	};
 
 	const module = /** @type {ESTree.Program} */ (
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-template/fix-attribute-casing.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/fix-attribute-casing.js
new file mode 100644
index 0000000000..ce56c43d7c
--- /dev/null
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/fix-attribute-casing.js
@@ -0,0 +1,18 @@
+const svg_attributes =
+	'accent-height accumulate additive alignment-baseline allowReorder alphabetic amplitude arabic-form ascent attributeName attributeType autoReverse azimuth baseFrequency baseline-shift baseProfile bbox begin bias by calcMode cap-height class clip clipPathUnits clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering contentScriptType contentStyleType cursor cx cy d decelerate descent diffuseConstant direction display divisor dominant-baseline dur dx dy edgeMode elevation enable-background end exponent externalResourcesRequired fill fill-opacity fill-rule filter filterRes filterUnits flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format from fr fx fy g1 g2 glyph-name glyph-orientation-horizontal glyph-orientation-vertical glyphRef gradientTransform gradientUnits hanging height href horiz-adv-x horiz-origin-x id ideographic image-rendering in in2 intercept k k1 k2 k3 k4 kernelMatrix kernelUnitLength kerning keyPoints keySplines keyTimes lang lengthAdjust letter-spacing lighting-color limitingConeAngle local marker-end marker-mid marker-start markerHeight markerUnits markerWidth mask maskContentUnits maskUnits mathematical max media method min mode name numOctaves offset onabort onactivate onbegin onclick onend onerror onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup onrepeat onresize onscroll onunload opacity operator order orient orientation origin overflow overline-position overline-thickness panose-1 paint-order pathLength patternContentUnits patternTransform patternUnits pointer-events points pointsAtX pointsAtY pointsAtZ preserveAlpha preserveAspectRatio primitiveUnits r radius refX refY rendering-intent repeatCount repeatDur requiredExtensions requiredFeatures restart result rotate rx ry scale seed shape-rendering slope spacing specularConstant specularExponent speed spreadMethod startOffset stdDeviation stemh stemv stitchTiles stop-color stop-opacity strikethrough-position strikethrough-thickness string stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale systemLanguage tabindex tableValues target targetX targetY text-anchor text-decoration text-rendering textLength to transform type u1 u2 underline-position underline-thickness unicode unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical values version vert-adv-y vert-origin-x vert-origin-y viewBox viewTarget visibility width widths word-spacing writing-mode x x-height x1 x2 xChannelSelector xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y y1 y2 yChannelSelector z zoomAndPan'.split(
+		' '
+	);
+
+const svg_attribute_lookup = new Map();
+
+svg_attributes.forEach((name) => {
+	svg_attribute_lookup.set(name.toLowerCase(), name);
+});
+
+/**
+ * @param {string} name
+ */
+export default function fix_attribute_casing(name) {
+	name = name.toLowerCase();
+	return svg_attribute_lookup.get(name) || name;
+}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-template/index.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/index.js
new file mode 100644
index 0000000000..d0327e702a
--- /dev/null
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/index.js
@@ -0,0 +1,68 @@
+/** @import { Location } from 'locate-character' */
+/** @import { Namespace } from '#compiler' */
+/** @import { ComponentClientTransformState } from '../types.js' */
+/** @import { Node } from './types.js' */
+import { TEMPLATE_USE_MATHML, TEMPLATE_USE_SVG } from '../../../../../constants.js';
+import { dev, locator } from '../../../../state.js';
+import * as b from '../../../../utils/builders.js';
+
+/**
+ * @param {Node[]} nodes
+ */
+function build_locations(nodes) {
+	const array = b.array([]);
+
+	for (const node of nodes) {
+		if (node.type !== 'element') continue;
+
+		const { line, column } = /** @type {Location} */ (locator(node.start));
+
+		const expression = b.array([b.literal(line), b.literal(column)]);
+		const children = build_locations(node.children);
+
+		if (children.elements.length > 0) {
+			expression.elements.push(children);
+		}
+
+		array.elements.push(expression);
+	}
+
+	return array;
+}
+
+/**
+ * @param {ComponentClientTransformState} state
+ * @param {Namespace} namespace
+ * @param {number} [flags]
+ */
+export function transform_template(state, namespace, flags = 0) {
+	const tree = state.options.fragments === 'tree';
+
+	const expression = tree ? state.template.as_tree() : state.template.as_html();
+
+	if (tree) {
+		if (namespace === 'svg') flags |= TEMPLATE_USE_SVG;
+		if (namespace === 'mathml') flags |= TEMPLATE_USE_MATHML;
+	}
+
+	let call = b.call(
+		tree ? `$.from_tree` : `$.from_${namespace}`,
+		expression,
+		flags ? b.literal(flags) : undefined
+	);
+
+	if (state.template.contains_script_tag) {
+		call = b.call(`$.with_script`, call);
+	}
+
+	if (dev) {
+		call = b.call(
+			'$.add_locations',
+			call,
+			b.member(b.id(state.analysis.name), '$.FILENAME', true),
+			build_locations(state.template.nodes)
+		);
+	}
+
+	return call;
+}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js
new file mode 100644
index 0000000000..8f7f8a1f43
--- /dev/null
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js
@@ -0,0 +1,162 @@
+/** @import { AST } from '#compiler' */
+/** @import { Node, Element } from './types'; */
+import { escape_html } from '../../../../../escaping.js';
+import { is_void } from '../../../../../utils.js';
+import * as b from '#compiler/builders';
+import fix_attribute_casing from './fix-attribute-casing.js';
+import { regex_starts_with_newline } from '../../../patterns.js';
+
+export class Template {
+	/**
+	 * `true` if HTML template contains a `
 
 
diff --git a/packages/svelte/tests/runtime-runes/samples/functional-templating/_config.js b/packages/svelte/tests/runtime-runes/samples/functional-templating/_config.js
new file mode 100644
index 0000000000..5d36f2e969
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/functional-templating/_config.js
@@ -0,0 +1,9 @@
+import { test } from '../../test';
+
+export default test({
+	compileOptions: {
+		fragments: 'tree'
+	},
+
+	html: `

hello

` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/functional-templating/main.svelte b/packages/svelte/tests/runtime-runes/samples/functional-templating/main.svelte new file mode 100644 index 0000000000..302a01f335 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/functional-templating/main.svelte @@ -0,0 +1 @@ +

hello

diff --git a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js index 3e5a12ed9d..9bb45ebf78 100644 --- a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js @@ -5,7 +5,7 @@ function increment(_, counter) { counter.count += 1; } -var root = $.template(` `, 1); +var root = $.from_html(` `, 1); export default function Await_block_scope($$anchor) { let counter = $.proxy({ count: 0 }); diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js index 390e86a351..ba3f4b155a 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js @@ -10,7 +10,7 @@ const snippet = ($$anchor) => { $.append($$anchor, text); }; -var root = $.template(` `, 1); +var root = $.from_html(` `, 1); export default function Bind_component_snippet($$anchor) { let value = $.state(''); diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js index 219db6ffd5..3a13fa7e15 100644 --- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js @@ -1,7 +1,7 @@ import 'svelte/internal/disclose-version'; import * as $ from 'svelte/internal/client'; -var root = $.template(`
`, 3); +var root = $.from_html(`
`, 3); export default function Main($$anchor) { // needs to be a snapshot test because jsdom does auto-correct the attribute casing diff --git a/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/client/index.svelte.js index 3d46a679b8..804a7c26f1 100644 --- a/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/client/index.svelte.js @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version'; import 'svelte/internal/flags/legacy'; import * as $ from 'svelte/internal/client'; -var root_1 = $.template(`

`); +var root_1 = $.from_html(`

`); export default function Each_index_non_null($$anchor) { var fragment = $.comment(); diff --git a/packages/svelte/tests/snapshot/samples/functional-templating/_config.js b/packages/svelte/tests/snapshot/samples/functional-templating/_config.js new file mode 100644 index 0000000000..23231c969b --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/functional-templating/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + fragments: 'tree' + } +}); diff --git a/packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js new file mode 100644 index 0000000000..792d5421e1 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js @@ -0,0 +1,25 @@ +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/legacy'; +import * as $ from 'svelte/internal/client'; + +var root = $.from_tree( + [ + ['h1', null, 'hello'], + ' ', + [ + 'div', + { class: 'potato' }, + ['p', null, 'child element'], + ' ', + ['p', null, 'another child element'] + ] + ], + 1 +); + +export default function Functional_templating($$anchor) { + var fragment = root(); + + $.next(2); + $.append($$anchor, fragment); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/functional-templating/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/functional-templating/_expected/server/index.svelte.js new file mode 100644 index 0000000000..dc49c0c213 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/functional-templating/_expected/server/index.svelte.js @@ -0,0 +1,5 @@ +import * as $ from 'svelte/internal/server'; + +export default function Functional_templating($$payload) { + $$payload.out += `

hello

child element

another child element

`; +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/functional-templating/index.svelte b/packages/svelte/tests/snapshot/samples/functional-templating/index.svelte new file mode 100644 index 0000000000..c0fe8965b8 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/functional-templating/index.svelte @@ -0,0 +1,6 @@ +

hello

+ +
+

child element

+

another child element

+
diff --git a/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js index 899c126001..68fdaa4570 100644 --- a/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version'; import 'svelte/internal/flags/legacy'; import * as $ from 'svelte/internal/client'; -var root = $.template(`

hello world

`); +var root = $.from_html(`

hello world

`); export default function Hello_world($$anchor) { var h1 = root(); diff --git a/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js index 3c8322500b..1fac1338c5 100644 --- a/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version'; import 'svelte/internal/flags/legacy'; import * as $ from 'svelte/internal/client'; -var root = $.template(`

hello world

`); +var root = $.from_html(`

hello world

`); function Hmr($$anchor) { var h1 = root(); diff --git a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js index 21f6ed9680..b46acee82e 100644 --- a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version'; import * as $ from 'svelte/internal/client'; var on_click = (_, count) => $.update(count); -var root = $.template(`

`, 1); +var root = $.from_html(`

`, 1); export default function Nullish_coallescence_omittance($$anchor) { let name = 'world'; diff --git a/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js index f661dbc01d..a351851875 100644 --- a/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version'; import 'svelte/internal/flags/legacy'; import * as $ from 'svelte/internal/client'; -var root = $.template(`

`, 1); +var root = $.from_html(`

`, 1); export default function Purity($$anchor) { var fragment = root(); diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js index 541b56a407..78147659ff 100644 --- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js @@ -1,7 +1,7 @@ import 'svelte/internal/disclose-version'; import * as $ from 'svelte/internal/client'; -var root = $.template(`

we don't need to traverse these nodes

or

these

ones

these

trailing

nodes

can

be

completely

ignored

`, 3); +var root = $.from_html(`

we don't need to traverse these nodes

or

these

ones

these

trailing

nodes

can

be

completely

ignored

`, 3); export default function Skip_static_subtree($$anchor, $$props) { var fragment = root(); diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js index e694c12647..a587c05e2c 100644 --- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js @@ -3,5 +3,5 @@ import * as $ from 'svelte/internal/server'; export default function Skip_static_subtree($$payload, $$props) { let { title, content } = $$props; - $$payload.out += `

${$.escape(title)}

we don't need to traverse these nodes

or

these

ones

${$.html(content)}

these

trailing

nodes

can

be

completely

ignored

`; + $$payload.out += `

${$.escape(title)}

we don't need to traverse these nodes

or

these

ones

${$.html(content)}

these

trailing

nodes

can

be

completely

ignored

`; } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js index a67210e541..c446b3d3ef 100644 --- a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js @@ -8,7 +8,7 @@ function reset(_, str, tpl) { $.set(tpl, ``); } -var root = $.template(` `, 1); +var root = $.from_html(` `, 1); export default function State_proxy_literal($$anchor) { let str = $.state(''); diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js index 7b2a884d70..f814dd4f84 100644 --- a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js @@ -11,5 +11,5 @@ export default function State_proxy_literal($$payload) { tpl = ``; } - $$payload.out += ` `; + $$payload.out += ` `; } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js index d520d1ef24..464435cb0a 100644 --- a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js @@ -1,7 +1,7 @@ import 'svelte/internal/disclose-version'; import * as $ from 'svelte/internal/client'; -var root = $.template(`

`); +var root = $.from_html(`

`); export default function Text_nodes_deriveds($$anchor) { let count1 = 0; diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index fcc1ec315c..201dbed9aa 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -985,6 +985,15 @@ declare module 'svelte/compiler' { * @default false */ preserveWhitespace?: boolean; + /** + * Which strategy to use when cloning DOM fragments: + * + * - `html` populates a `