Skip to content

Commit 27891cb

Browse files
authored
fix: ensure top level snippets are defined when binding to component prop (#11104)
...by hoisting top level snippets out of the binding loop in ssr mode fixes #11086
1 parent 4d0b743 commit 27891cb

File tree

5 files changed

+122
-8
lines changed

5 files changed

+122
-8
lines changed

.changeset/gold-tools-nail.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: ensure top level snippets are defined when binding to component prop

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,14 +1614,15 @@ const template_visitors = {
16141614
state.template.push(block_close);
16151615
},
16161616
SnippetBlock(node, context) {
1617-
// TODO hoist where possible
1618-
context.state.init.push(
1619-
b.function_declaration(
1620-
node.expression,
1621-
[b.id('$$payload'), ...node.parameters],
1622-
/** @type {import('estree').BlockStatement} */ (context.visit(node.body))
1623-
)
1617+
const fn = b.function_declaration(
1618+
node.expression,
1619+
[b.id('$$payload'), ...node.parameters],
1620+
/** @type {import('estree').BlockStatement} */ (context.visit(node.body))
16241621
);
1622+
// @ts-expect-error - TODO remove this hack once $$render_inner for legacy bindings is gone
1623+
fn.___snippet = true;
1624+
// TODO hoist where possible
1625+
context.state.init.push(fn);
16251626

16261627
if (context.state.options.dev) {
16271628
context.state.init.push(b.stmt(b.call('$.add_snippet_symbol', node.expression)));
@@ -2221,14 +2222,27 @@ export function server_component(analysis, options) {
22212222
// If the component binds to a child, we need to put the template in a loop and repeat until legacy bindings are stable.
22222223
// We can remove this once the legacy syntax is gone.
22232224
if (analysis.uses_component_bindings) {
2225+
const snippets = template.body.filter(
2226+
(node) =>
2227+
node.type === 'FunctionDeclaration' &&
2228+
// @ts-expect-error
2229+
node.___snippet
2230+
);
2231+
const rest = template.body.filter(
2232+
(node) =>
2233+
node.type !== 'FunctionDeclaration' ||
2234+
// @ts-expect-error
2235+
!node.___snippet
2236+
);
22242237
template.body = [
2238+
...snippets,
22252239
b.let('$$settled', b.true),
22262240
b.let('$$inner_payload'),
22272241
b.stmt(
22282242
b.function(
22292243
b.id('$$render_inner'),
22302244
[b.id('$$payload')],
2231-
b.block(/** @type {import('estree').Statement[]} */ (template.body))
2245+
b.block(/** @type {import('estree').Statement[]} */ (rest))
22322246
)
22332247
),
22342248
b.do_while(
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// index.svelte (Svelte VERSION)
2+
// Note: compiler output will change before 5.0 is released!
3+
import "svelte/internal/disclose-version";
4+
import * as $ from "svelte/internal/client";
5+
import TextInput from './Child.svelte';
6+
7+
var root_1 = $.template(`Something`, 1);
8+
var root = $.template(`<!> `, 1);
9+
10+
export default function Bind_component_snippet($$anchor, $$props) {
11+
$.push($$props, true);
12+
13+
let value = $.source('');
14+
const _snippet = snippet;
15+
var fragment_1 = root();
16+
17+
function snippet($$anchor) {
18+
var fragment = root_1();
19+
20+
$.append($$anchor, fragment);
21+
}
22+
23+
var node = $.first_child(fragment_1);
24+
25+
TextInput(node, {
26+
get value() {
27+
return $.get(value);
28+
},
29+
set value($$value) {
30+
$.set(value, $.proxy($$value));
31+
}
32+
});
33+
34+
var text = $.sibling(node, true);
35+
36+
$.render_effect(() => $.set_text(text, ` value: ${$.stringify($.get(value))}`));
37+
$.append($$anchor, fragment_1);
38+
$.pop();
39+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// index.svelte (Svelte VERSION)
2+
// Note: compiler output will change before 5.0 is released!
3+
import * as $ from "svelte/internal/server";
4+
import TextInput from './Child.svelte';
5+
6+
export default function Bind_component_snippet($$payload, $$props) {
7+
$.push(true);
8+
9+
let value = '';
10+
const _snippet = snippet;
11+
12+
function snippet($$payload) {
13+
$$payload.out += `Something`;
14+
}
15+
16+
let $$settled = true;
17+
let $$inner_payload;
18+
19+
function $$render_inner($$payload) {
20+
$$payload.out += `<!--[-->`;
21+
22+
TextInput($$payload, {
23+
get value() {
24+
return value;
25+
},
26+
set value($$value) {
27+
value = $$value;
28+
$$settled = false;
29+
}
30+
});
31+
32+
$$payload.out += `<!--]--> value: ${$.escape(value)}`;
33+
};
34+
35+
do {
36+
$$settled = true;
37+
$$inner_payload = $.copy_payload($$payload);
38+
$$render_inner($$inner_payload);
39+
} while (!$$settled);
40+
41+
$.assign_payload($$payload, $$inner_payload);
42+
$.pop();
43+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
import TextInput from './Child.svelte';
3+
4+
let value = $state('');
5+
const _snippet = snippet;
6+
</script>
7+
8+
{#snippet snippet()}
9+
Something
10+
{/snippet}
11+
12+
<TextInput bind:value />
13+
value: {value}

0 commit comments

Comments
 (0)