From 1391bedc4bd502490d64e7cf25e721914b5c1a7d Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 9 Apr 2024 15:34:29 +0200 Subject: [PATCH] fix: ensure top level snippets are defined when binding to component prop ...by hoisting top level snippets out of the binding loop in ssr mode fixes #11086 --- .changeset/gold-tools-nail.md | 5 +++ .../3-transform/server/transform-server.js | 30 +++++++++---- .../_expected/client/index.svelte.js | 39 +++++++++++++++++ .../_expected/server/index.svelte.js | 43 +++++++++++++++++++ .../bind-component-snippet/index.svelte | 13 ++++++ 5 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 .changeset/gold-tools-nail.md create mode 100644 packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/bind-component-snippet/index.svelte diff --git a/.changeset/gold-tools-nail.md b/.changeset/gold-tools-nail.md new file mode 100644 index 000000000000..7e99bd67a4a4 --- /dev/null +++ b/.changeset/gold-tools-nail.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: ensure top level snippets are defined when binding to component prop diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 3f07e4dbfb7b..8f35d8897a31 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -1614,14 +1614,15 @@ const template_visitors = { state.template.push(block_close); }, SnippetBlock(node, context) { - // TODO hoist where possible - context.state.init.push( - b.function_declaration( - node.expression, - [b.id('$$payload'), ...node.parameters], - /** @type {import('estree').BlockStatement} */ (context.visit(node.body)) - ) + const fn = b.function_declaration( + node.expression, + [b.id('$$payload'), ...node.parameters], + /** @type {import('estree').BlockStatement} */ (context.visit(node.body)) ); + // @ts-expect-error - TODO remove this hack once $$render_inner for legacy bindings is gone + fn.___snippet = true; + // TODO hoist where possible + context.state.init.push(fn); if (context.state.options.dev) { context.state.init.push(b.stmt(b.call('$.add_snippet_symbol', node.expression))); @@ -2221,14 +2222,27 @@ export function server_component(analysis, options) { // If the component binds to a child, we need to put the template in a loop and repeat until legacy bindings are stable. // We can remove this once the legacy syntax is gone. if (analysis.uses_component_bindings) { + const snippets = template.body.filter( + (node) => + node.type === 'FunctionDeclaration' && + // @ts-expect-error + node.___snippet + ); + const rest = template.body.filter( + (node) => + node.type !== 'FunctionDeclaration' || + // @ts-expect-error + !node.___snippet + ); template.body = [ + ...snippets, b.let('$$settled', b.true), b.let('$$inner_payload'), b.stmt( b.function( b.id('$$render_inner'), [b.id('$$payload')], - b.block(/** @type {import('estree').Statement[]} */ (template.body)) + b.block(/** @type {import('estree').Statement[]} */ (rest)) ) ), b.do_while( 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 new file mode 100644 index 000000000000..e9bfe0306318 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js @@ -0,0 +1,39 @@ +// index.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import "svelte/internal/disclose-version"; +import * as $ from "svelte/internal/client"; +import TextInput from './Child.svelte'; + +var root_1 = $.template(`Something`, 1); +var root = $.template(` `, 1); + +export default function Bind_component_snippet($$anchor, $$props) { + $.push($$props, true); + + let value = $.source(''); + const _snippet = snippet; + var fragment_1 = root(); + + function snippet($$anchor) { + var fragment = root_1(); + + $.append($$anchor, fragment); + } + + var node = $.first_child(fragment_1); + + TextInput(node, { + get value() { + return $.get(value); + }, + set value($$value) { + $.set(value, $.proxy($$value)); + } + }); + + var text = $.sibling(node, true); + + $.render_effect(() => $.set_text(text, ` value: ${$.stringify($.get(value))}`)); + $.append($$anchor, fragment_1); + $.pop(); +} \ No newline at end of file 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 new file mode 100644 index 000000000000..b1f83fed42d8 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js @@ -0,0 +1,43 @@ +// index.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import * as $ from "svelte/internal/server"; +import TextInput from './Child.svelte'; + +export default function Bind_component_snippet($$payload, $$props) { + $.push(true); + + let value = ''; + const _snippet = snippet; + + function snippet($$payload) { + $$payload.out += `Something`; + } + + let $$settled = true; + let $$inner_payload; + + function $$render_inner($$payload) { + $$payload.out += ``; + + TextInput($$payload, { + get value() { + return value; + }, + set value($$value) { + value = $$value; + $$settled = false; + } + }); + + $$payload.out += ` value: ${$.escape(value)}`; + }; + + do { + $$settled = true; + $$inner_payload = $.copy_payload($$payload); + $$render_inner($$inner_payload); + } while (!$$settled); + + $.assign_payload($$payload, $$inner_payload); + $.pop(); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/index.svelte b/packages/svelte/tests/snapshot/samples/bind-component-snippet/index.svelte new file mode 100644 index 000000000000..1d7a8f4f540c --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/index.svelte @@ -0,0 +1,13 @@ + + +{#snippet snippet()} + Something +{/snippet} + + +value: {value}