` elements as selected by iterating over the array passed to `value`. If `value` is not an array, Svelte will emit this warning and keep the selected options as they are.
+
+To silence the warning, ensure that `value`:
+
+- is an array for an explicit selection
+- is `null` or `undefined` to keep the selection as is
+
### state_proxy_equality_mismatch
```
diff --git a/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md
index b579d38602..2af9021a6a 100644
--- a/documentation/docs/98-reference/.generated/compile-warnings.md
+++ b/documentation/docs/98-reference/.generated/compile-warnings.md
@@ -632,6 +632,12 @@ In some situations a selector may target an element that is not 'visible' to the
```
+### custom_element_props_identifier
+
+```
+Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option.
+```
+
### element_implicitly_closed
```
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 270460e5ed..72ab89eb4a 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,59 @@
# svelte
+## 5.33.4
+
+### Patch Changes
+
+- fix: narrow `defaultChecked` to boolean ([#16009](https://github.com/sveltejs/svelte/pull/16009))
+
+- fix: warn when using rest or identifier in custom elements without props option ([#16003](https://github.com/sveltejs/svelte/pull/16003))
+
+## 5.33.3
+
+### Patch Changes
+
+- fix: allow using typescript in `customElement.extend` option ([#16001](https://github.com/sveltejs/svelte/pull/16001))
+
+- fix: cleanup event handlers on media elements ([#16005](https://github.com/sveltejs/svelte/pull/16005))
+
+## 5.33.2
+
+### Patch Changes
+
+- fix: correctly parse escaped unicode characters in css selector ([#15976](https://github.com/sveltejs/svelte/pull/15976))
+
+- fix: don't mark deriveds as clean if updating during teardown ([#15997](https://github.com/sveltejs/svelte/pull/15997))
+
+## 5.33.1
+
+### Patch Changes
+
+- fix: make deriveds on the server lazy again ([#15964](https://github.com/sveltejs/svelte/pull/15964))
+
+## 5.33.0
+
+### Minor Changes
+
+- feat: XHTML compliance ([#15538](https://github.com/sveltejs/svelte/pull/15538))
+
+- feat: add `fragments: 'html' | 'tree'` option for wider CSP compliance ([#15538](https://github.com/sveltejs/svelte/pull/15538))
+
+## 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
+
+- 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/elements.d.ts b/packages/svelte/elements.d.ts
index 237e96c699..0172b0e358 100644
--- a/packages/svelte/elements.d.ts
+++ b/packages/svelte/elements.d.ts
@@ -1115,8 +1115,8 @@ export interface HTMLInputAttributes extends HTMLAttributes {
// needs both casing variants because language tools does lowercase names of non-shorthand attributes
defaultValue?: any;
defaultvalue?: any;
- defaultChecked?: any;
- defaultchecked?: any;
+ defaultChecked?: boolean | undefined | null;
+ defaultchecked?: boolean | undefined | null;
width?: number | string | undefined | null;
webkitdirectory?: boolean | undefined | null;
@@ -2066,7 +2066,7 @@ export interface SvelteHTMLElements {
failed?: import('svelte').Snippet<[error: unknown, reset: () => void]>;
};
- [name: string]: { [name: string]: any };
+ [name: string & {}]: { [name: string]: any };
}
export type ClassValue = string | import('clsx').ClassArray | import('clsx').ClassDictionary;
diff --git a/packages/svelte/messages/client-warnings/warnings.md b/packages/svelte/messages/client-warnings/warnings.md
index a96b09a25d..0a6af616c3 100644
--- a/packages/svelte/messages/client-warnings/warnings.md
+++ b/packages/svelte/messages/client-warnings/warnings.md
@@ -180,6 +180,17 @@ Consider the following code:
To fix it, either create callback props to communicate changes, or mark `person` as [`$bindable`]($bindable).
+## select_multiple_invalid_value
+
+> The `value` property of a `` element should be an array, but it received a non-array value. The selection will be kept as is.
+
+When using ``, Svelte will mark all selected `` elements as selected by iterating over the array passed to `value`. If `value` is not an array, Svelte will emit this warning and keep the selected options as they are.
+
+To silence the warning, ensure that `value`:
+
+- is an array for an explicit selection
+- is `null` or `undefined` to keep the selection as is
+
## state_proxy_equality_mismatch
> Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `%operator%` will produce unexpected results
diff --git a/packages/svelte/messages/compile-warnings/script.md b/packages/svelte/messages/compile-warnings/script.md
index 6603759156..26bd0c9027 100644
--- a/packages/svelte/messages/compile-warnings/script.md
+++ b/packages/svelte/messages/compile-warnings/script.md
@@ -1,3 +1,7 @@
+## custom_element_props_identifier
+
+> Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option.
+
## export_let_unused
> Component has unused export property '%name%'. If it is for external reference only, please consider using `export const %name%`
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index d374880887..a63771a0b4 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.33.4",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
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';
/**
diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js
index 42427dd9c4..756a88a824 100644
--- a/packages/svelte/src/compiler/index.js
+++ b/packages/svelte/src/compiler/index.js
@@ -43,6 +43,11 @@ export function compile(source, options) {
instance: parsed.instance && remove_typescript_nodes(parsed.instance),
module: parsed.module && remove_typescript_nodes(parsed.module)
};
+ if (combined_options.customElementOptions?.extend) {
+ combined_options.customElementOptions.extend = remove_typescript_nodes(
+ combined_options.customElementOptions?.extend
+ );
+ }
}
const analysis = analyze_component(parsed, source, combined_options);
diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js
index e15a47e6d5..80ab234d92 100644
--- a/packages/svelte/src/compiler/phases/1-parse/read/style.js
+++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js
@@ -12,6 +12,7 @@ const REGEX_NTH_OF =
const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/;
const REGEX_VALID_IDENTIFIER_CHAR = /[a-zA-Z0-9_-]/;
+const REGEX_UNICODE_SEQUENCE = /^\\[0-9a-fA-F]{1,6}(\r\n|\s)?/;
const REGEX_COMMENT_CLOSE = /\*\//;
const REGEX_HTML_COMMENT_CLOSE = /-->/;
@@ -580,25 +581,26 @@ function read_identifier(parser) {
e.css_expected_identifier(start);
}
- let escaped = false;
-
while (parser.index < parser.template.length) {
const char = parser.template[parser.index];
- if (escaped) {
- identifier += '\\' + char;
- escaped = false;
- } else if (char === '\\') {
- escaped = true;
+ if (char === '\\') {
+ const sequence = parser.match_regex(REGEX_UNICODE_SEQUENCE);
+ if (sequence) {
+ identifier += String.fromCodePoint(parseInt(sequence.slice(1), 16));
+ parser.index += sequence.length;
+ } else {
+ identifier += '\\' + parser.template[parser.index + 1];
+ parser.index += 2;
+ }
} else if (
/** @type {number} */ (char.codePointAt(0)) >= 160 ||
REGEX_VALID_IDENTIFIER_CHAR.test(char)
) {
identifier += char;
+ parser.index++;
} else {
break;
}
-
- parser.index++;
}
if (identifier === '') {
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js
index a7d08d315d..c4b4272aba 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js
@@ -4,6 +4,7 @@
import { get_rune } from '../../scope.js';
import { ensure_no_module_import_conflict, validate_identifier_name } from './shared/utils.js';
import * as e from '../../../errors.js';
+import * as w from '../../../warnings.js';
import { extract_paths } from '../../../utils/ast.js';
import { equal } from '../../../utils/assert.js';
@@ -52,6 +53,19 @@ export function VariableDeclarator(node, context) {
e.props_invalid_identifier(node);
}
+ if (
+ context.state.analysis.custom_element &&
+ context.state.options.customElementOptions?.props == null
+ ) {
+ let warn_on;
+ if (
+ node.id.type === 'Identifier' ||
+ (warn_on = node.id.properties.find((p) => p.type === 'RestElement')) != null
+ ) {
+ w.custom_element_props_identifier(warn_on ?? node.id);
+ }
+ }
+
context.state.analysis.needs_props = true;
if (node.id.type === 'Identifier') {
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 7c3e7b96d7..341382847d 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
@@ -156,10 +156,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,
async: []
@@ -178,8 +174,7 @@ export function client_component(analysis, options) {
expressions: /** @type {any} */ (null),
async_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 `
+
+name: {name}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-browser/test.ts b/packages/svelte/tests/runtime-browser/test.ts
index 582a10edf7..63e601b115 100644
--- a/packages/svelte/tests/runtime-browser/test.ts
+++ b/packages/svelte/tests/runtime-browser/test.ts
@@ -5,7 +5,7 @@ import * as path from 'node:path';
import { compile } from 'svelte/compiler';
import { afterAll, assert, beforeAll, describe } from 'vitest';
import { suite, suite_with_variants } from '../suite';
-import { write } from '../helpers';
+import { write, fragments } from '../helpers';
import type { Warning } from '#compiler';
const assert_file = path.resolve(__dirname, 'assert.js');
@@ -87,6 +87,7 @@ async function run_test(
build.onLoad({ filter: /\.svelte$/ }, (args) => {
const compiled = compile(fs.readFileSync(args.path, 'utf-8').replace(/\r/g, ''), {
generate: 'client',
+ fragments,
...config.compileOptions,
immutable: config.immutable,
customElement: test_dir.includes('custom-elements-samples'),
diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts
index f1b7476994..f60a926e1b 100644
--- a/packages/svelte/tests/runtime-legacy/shared.ts
+++ b/packages/svelte/tests/runtime-legacy/shared.ts
@@ -6,7 +6,7 @@ import { proxy } from 'svelte/internal/client';
import { flushSync, hydrate, mount, unmount } from 'svelte';
import { render } from 'svelte/server';
import { afterAll, assert, beforeAll } from 'vitest';
-import { compile_directory } from '../helpers.js';
+import { compile_directory, fragments } from '../helpers.js';
import { setup_html_equal } from '../html_equal.js';
import { raf } from '../animation-helpers.js';
import type { CompileOptions } from '#compiler';
@@ -175,6 +175,7 @@ async function common_setup(cwd: string, runes: boolean | undefined, config: Run
experimental: {
async: true
},
+ fragments,
...config.compileOptions,
immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true,
diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/_config.js b/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/_config.js
index 141d994a2f..40ef84a2e6 100644
--- a/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/_config.js
@@ -5,6 +5,7 @@ export default test({
html: `
0
doubled: 0
+ tripled: 0
`,
test({ assert, target }) {
@@ -17,6 +18,7 @@ export default test({
`
1
doubled: 2
+ tripled: 3
`
);
@@ -27,6 +29,7 @@ export default test({
`
2
doubled: 4
+ tripled: 6
`
);
}
diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte
index 2c4c8f1839..d971566396 100644
--- a/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte
@@ -2,14 +2,24 @@
class Counter {
count = $state(0);
#doubled = $derived(this.count * 2);
+ #tripled = $derived.by(() => this.count * this.by);
- get embiggened() {
+ constructor(by) {
+ this.by = by;
+ }
+
+ get embiggened1() {
return this.#doubled;
}
+
+ get embiggened2() {
+ return this.#tripled;
+ }
}
- const counter = new Counter();
+ const counter = new Counter(3);
counter.count++}>{counter.count}
-doubled: {counter.embiggened}
+doubled: {counter.embiggened1}
+tripled: {counter.embiggened2}
diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte
index 4c98245e5b..82774f160d 100644
--- a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte
@@ -1,18 +1,20 @@
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/_config.js
new file mode 100644
index 0000000000..1016ffb43e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/_config.js
@@ -0,0 +1,22 @@
+import { test } from '../../test';
+import { flushSync } from 'svelte';
+
+export default test({
+ html: `toggle (false) `,
+
+ async test({ assert, target, logs }) {
+ assert.deepEqual(logs, ['up', { foo: false, bar: false }]);
+
+ const button = target.querySelector('button');
+
+ flushSync(() => button?.click());
+ assert.deepEqual(logs, [
+ 'up',
+ { foo: false, bar: false },
+ 'down',
+ { foo: false, bar: false },
+ 'up',
+ { foo: true, bar: true }
+ ]);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/main.svelte
new file mode 100644
index 0000000000..fff81591d5
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/main.svelte
@@ -0,0 +1,14 @@
+
+
+ foo = !foo}>toggle ({foo})
diff --git a/packages/svelte/tests/runtime-runes/samples/event-media-element-cleanup/_config.js b/packages/svelte/tests/runtime-runes/samples/event-media-element-cleanup/_config.js
new file mode 100644
index 0000000000..775afcab89
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/event-media-element-cleanup/_config.js
@@ -0,0 +1,20 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+import { expect, vi } from 'vitest';
+
+const handler = vi.fn();
+
+export default test({
+ props: {
+ handler
+ },
+ async test({ target }) {
+ const button = target.querySelector('button');
+ const video = target.querySelector('video');
+
+ button?.click();
+ flushSync();
+ video?.dispatchEvent(new Event('someevent'));
+ expect(handler).not.toHaveBeenCalled();
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/event-media-element-cleanup/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-media-element-cleanup/main.svelte
new file mode 100644
index 0000000000..6a1f9b6b4b
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/event-media-element-cleanup/main.svelte
@@ -0,0 +1,9 @@
+
+
+ show = false}>show/hide
+{#if show}
+
+{/if}
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/runtime-runes/samples/select-multiple-invalid-value/_config.js b/packages/svelte/tests/runtime-runes/samples/select-multiple-invalid-value/_config.js
new file mode 100644
index 0000000000..1f04f176af
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/select-multiple-invalid-value/_config.js
@@ -0,0 +1,7 @@
+import { test } from '../../test';
+
+export default test({
+ warnings: [
+ 'The `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 @@
+
+ option
+
+
+ option
+
+
+ option
+
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
diff --git a/packages/svelte/tests/runtime-runes/samples/writable-derived-3/_config.js b/packages/svelte/tests/runtime-runes/samples/writable-derived-3/_config.js
new file mode 100644
index 0000000000..999e4ad6e0
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/writable-derived-3/_config.js
@@ -0,0 +1,5 @@
+import { test } from '../../test';
+
+export default test({
+ html: `3 3 3 3`
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/writable-derived-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/writable-derived-3/main.svelte
new file mode 100644
index 0000000000..0b20f811c3
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/writable-derived-3/main.svelte
@@ -0,0 +1,29 @@
+
+
+{x.on_class} {x.in_constructor} {x.on_class_private} {x.in_constructor_private}
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
a `, 3);
+var root = $.from_html(` we don't need to traverse these nodes
or
these
ones
these
trailing
nodes
can
be
completely
ignored
a `, 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
a `;
+ $$payload.out += ` ${$.escape(title)} we don't need to traverse these nodes
or
these
ones
${$.html(content)} these
trailing
nodes
can
be
completely
ignored
a `;
}
\ 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(` reset `, 1);
+var root = $.from_html(` reset `, 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 += ` reset `;
+ $$payload.out += ` reset `;
}
\ 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/tests/validator/samples/custom-element-props-identifier-props-option/input.svelte b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/input.svelte
new file mode 100644
index 0000000000..bb7b930dc3
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/input.svelte
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/warnings.json b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/warnings.json
new file mode 100644
index 0000000000..b880fe146c
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/warnings.json
@@ -0,0 +1,14 @@
+[
+ {
+ "code": "options_missing_custom_element",
+ "end": {
+ "column": 2,
+ "line": 3
+ },
+ "message": "The `customElement` option is used when generating a custom element. Did you forget the `customElement: true` compile option?",
+ "start": {
+ "column": 16,
+ "line": 1
+ }
+ }
+]
diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/input.svelte b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/input.svelte
new file mode 100644
index 0000000000..207b554527
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/input.svelte
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/warnings.json b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/warnings.json
new file mode 100644
index 0000000000..61e11ab108
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/warnings.json
@@ -0,0 +1,26 @@
+[
+ {
+ "code": "options_missing_custom_element",
+ "end": {
+ "column": 34,
+ "line": 1
+ },
+ "message": "The `customElement` option is used when generating a custom element. Did you forget the `customElement: true` compile option?",
+ "start": {
+ "column": 16,
+ "line": 1
+ }
+ },
+ {
+ "code": "custom_element_props_identifier",
+ "end": {
+ "column": 15,
+ "line": 4
+ },
+ "message": "Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option.",
+ "start": {
+ "column": 7,
+ "line": 4
+ }
+ }
+]
diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier/input.svelte b/packages/svelte/tests/validator/samples/custom-element-props-identifier/input.svelte
new file mode 100644
index 0000000000..ca5b16f8c3
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier/input.svelte
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier/warnings.json b/packages/svelte/tests/validator/samples/custom-element-props-identifier/warnings.json
new file mode 100644
index 0000000000..4c50bbd116
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier/warnings.json
@@ -0,0 +1,26 @@
+[
+ {
+ "code": "options_missing_custom_element",
+ "end": {
+ "column": 34,
+ "line": 1
+ },
+ "message": "The `customElement` option is used when generating a custom element. Did you forget the `customElement: true` compile option?",
+ "start": {
+ "column": 16,
+ "line": 1
+ }
+ },
+ {
+ "code": "custom_element_props_identifier",
+ "end": {
+ "column": 10,
+ "line": 4
+ },
+ "message": "Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option.",
+ "start": {
+ "column": 5,
+ "line": 4
+ }
+ }
+]
diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts
index a86e2c5b26..ed382295a6 100644
--- a/packages/svelte/types/index.d.ts
+++ b/packages/svelte/types/index.d.ts
@@ -1014,6 +1014,16 @@ declare module 'svelte/compiler' {
* @default false
*/
preserveWhitespace?: boolean;
+ /**
+ * Which strategy to use when cloning DOM fragments:
+ *
+ * - `html` populates a `` with `innerHTML` and clones it. This is faster, but cannot be used if your app's [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) includes [`require-trusted-types-for 'script'`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/require-trusted-types-for)
+ * - `tree` creates the fragment one element at a time and _then_ clones it. This is slower, but works everywhere
+ *
+ * @default 'html'
+ * @since 5.33
+ */
+ fragments?: 'html' | 'tree';
/**
* Set to `true` to force the compiler into runes mode, even if there are no indications of runes usage.
* Set to `false` to force the compiler into ignoring runes, even if there are indications of runes usage.
@@ -2906,6 +2916,16 @@ declare module 'svelte/types/compiler/interfaces' {
* @default false
*/
preserveWhitespace?: boolean;
+ /**
+ * Which strategy to use when cloning DOM fragments:
+ *
+ * - `html` populates a `` with `innerHTML` and clones it. This is faster, but cannot be used if your app's [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) includes [`require-trusted-types-for 'script'`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/require-trusted-types-for)
+ * - `tree` creates the fragment one element at a time and _then_ clones it. This is slower, but works everywhere
+ *
+ * @default 'html'
+ * @since 5.33
+ */
+ fragments?: 'html' | 'tree';
/**
* Set to `true` to force the compiler into runes mode, even if there are no indications of runes usage.
* Set to `false` to force the compiler into ignoring runes, even if there are indications of runes usage.
diff --git a/playgrounds/sandbox/run.js b/playgrounds/sandbox/run.js
index c053f7e29a..6bce17fff0 100644
--- a/playgrounds/sandbox/run.js
+++ b/playgrounds/sandbox/run.js
@@ -88,9 +88,25 @@ for (const generate of /** @type {const} */ (['client', 'server'])) {
}
write(output_js, compiled.js.code + '\n//# sourceMappingURL=' + path.basename(output_map));
-
write(output_map, compiled.js.map.toString());
+ // generate with fragments: 'tree'
+ if (generate === 'client') {
+ const compiled = compile(source, {
+ dev: true,
+ filename: input,
+ generate,
+ runes: argv.values.runes,
+ fragments: 'tree'
+ });
+
+ const output_js = `${cwd}/output/${generate}/${file}.tree.js`;
+ const output_map = `${cwd}/output/${generate}/${file}.tree.js.map`;
+
+ write(output_js, compiled.js.code + '\n//# sourceMappingURL=' + path.basename(output_map));
+ write(output_map, compiled.js.map.toString());
+ }
+
if (compiled.css) {
write(output_css, compiled.css.code);
}