diff --git a/.changeset/few-horses-wink.md b/.changeset/few-horses-wink.md new file mode 100644 index 000000000000..8ffa3640c09f --- /dev/null +++ b/.changeset/few-horses-wink.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: use function declaration for snippets in server output to avoid TDZ violation diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js index 5118679b3489..238485e665b6 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js @@ -1,4 +1,4 @@ -/** @import { ArrowFunctionExpression, BlockStatement, CallExpression } from 'estree' */ +/** @import { BlockStatement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types.js' */ import { dev } from '../../../../state.js'; @@ -9,27 +9,21 @@ import * as b from '#compiler/builders'; * @param {ComponentContext} context */ export function SnippetBlock(node, context) { - const body = /** @type {BlockStatement} */ (context.visit(node.body)); + let fn = b.function_declaration( + node.expression, + [b.id('$$payload'), ...node.parameters], + /** @type {BlockStatement} */ (context.visit(node.body)) + ); - if (dev) { - body.body.unshift(b.stmt(b.call('$.validate_snippet_args', b.id('$$payload')))); - } + // @ts-expect-error - TODO remove this hack once $$render_inner for legacy bindings is gone + fn.___snippet = true; - /** @type {ArrowFunctionExpression | CallExpression} */ - let fn = b.arrow([b.id('$$payload'), ...node.parameters], body); + const statements = node.metadata.can_hoist ? context.state.hoisted : context.state.init; if (dev) { - fn = b.call('$.prevent_snippet_stringification', fn); + fn.body.body.unshift(b.stmt(b.call('$.validate_snippet_args', b.id('$$payload')))); + statements.push(b.stmt(b.call('$.prevent_snippet_stringification', fn.id))); } - const declaration = b.declaration('const', [b.declarator(node.expression, fn)]); - - // @ts-expect-error - TODO remove this hack once $$render_inner for legacy bindings is gone - fn.___snippet = true; - - if (node.metadata.can_hoist) { - context.state.hoisted.push(declaration); - } else { - context.state.init.push(declaration); - } + statements.push(fn); } diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-access-in-script/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-access-in-script/_config.js new file mode 100644 index 000000000000..ed0ead960bdb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-access-in-script/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-access-in-script/fn.js b/packages/svelte/tests/runtime-runes/samples/snippet-access-in-script/fn.js new file mode 100644 index 000000000000..9e9a48c60cbe --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-access-in-script/fn.js @@ -0,0 +1,3 @@ +export function fn(snippet) { + return snippet; +} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-access-in-script/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-access-in-script/main.svelte new file mode 100644 index 000000000000..cc73aa31db18 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-access-in-script/main.svelte @@ -0,0 +1,10 @@ + + +{#snippet test()} + {variable} +{/snippet} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-invalid-call/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-invalid-call/_config.js new file mode 100644 index 000000000000..7e72fd751ab6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-invalid-call/_config.js @@ -0,0 +1,9 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + + error: 'invalid_snippet_arguments' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-invalid-call/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-invalid-call/main.svelte new file mode 100644 index 000000000000..3c47db3f96eb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-invalid-call/main.svelte @@ -0,0 +1,7 @@ + + +{#snippet test()} +
hello
+{/snippet} diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js index 04bfbf6ae47b..cadae2cf15c0 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js @@ -1,9 +1,9 @@ import * as $ from 'svelte/internal/server'; import TextInput from './Child.svelte'; -const snippet = ($$payload) => { +function snippet($$payload) { $$payload.out += `Something`; -}; +} export default function Bind_component_snippet($$payload) { let value = '';