Skip to content

Commit 434e1ad

Browse files
trueadmRich-Harris
andauthored
fix: deconflict multiple snippets of the same name (#12221)
* fix: deconflict multiple snippets of the same name * address feedback * only create BlockStatement when necessary --------- Co-authored-by: Rich Harris <[email protected]>
1 parent f9859d1 commit 434e1ad

File tree

7 files changed

+89
-15
lines changed

7 files changed

+89
-15
lines changed

.changeset/witty-hornets-think.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: deconflict multiple snippets of the same name

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,13 +1098,12 @@ function serialize_update(statement) {
10981098
}
10991099

11001100
/**
1101-
*
1102-
* @param {import('../types.js').ComponentClientTransformState} state
1101+
* @param {import('estree').Statement[]} update
11031102
*/
1104-
function serialize_render_stmt(state) {
1105-
return state.update.length === 1
1106-
? serialize_update(state.update[0])
1107-
: b.stmt(b.call('$.template_effect', b.thunk(b.block(state.update))));
1103+
function serialize_render_stmt(update) {
1104+
return update.length === 1
1105+
? serialize_update(update[0])
1106+
: b.stmt(b.call('$.template_effect', b.thunk(b.block(update))));
11081107
}
11091108

11101109
/**
@@ -1732,7 +1731,7 @@ export const template_visitors = {
17321731
}
17331732

17341733
if (state.update.length > 0) {
1735-
body.push(serialize_render_stmt(state));
1734+
body.push(serialize_render_stmt(state.update));
17361735
}
17371736

17381737
body.push(...state.after_update);
@@ -2179,8 +2178,15 @@ export const template_visitors = {
21792178
state.options.preserveComments
21802179
);
21812180

2181+
/** Whether or not we need to wrap the children in `{...}` to avoid declaration conflicts */
2182+
const has_declaration = node.fragment.nodes.some((node) => node.type === 'SnippetBlock');
2183+
2184+
const child_state = has_declaration
2185+
? { ...state, init: [], update: [], after_update: [] }
2186+
: state;
2187+
21822188
for (const node of hoisted) {
2183-
context.visit(node, state);
2189+
context.visit(node, child_state);
21842190
}
21852191

21862192
process_children(
@@ -2193,9 +2199,19 @@ export const template_visitors = {
21932199
: context.state.node
21942200
),
21952201
true,
2196-
{ ...context, state }
2202+
{ ...context, state: child_state }
21972203
);
21982204

2205+
if (has_declaration) {
2206+
context.state.init.push(
2207+
b.block([
2208+
...child_state.init,
2209+
child_state.update.length > 0 ? serialize_render_stmt(child_state.update) : b.empty,
2210+
...child_state.after_update
2211+
])
2212+
);
2213+
}
2214+
21992215
if (has_direction_attribute) {
22002216
// This fixes an issue with Chromium where updates to text content within an element
22012217
// does not update the direction when set to auto. If we just re-assign the dir, this fixes it.
@@ -2292,7 +2308,7 @@ export const template_visitors = {
22922308
/** @type {import('estree').Statement[]} */
22932309
const inner = inner_context.state.init;
22942310
if (inner_context.state.update.length > 0) {
2295-
inner.push(serialize_render_stmt(inner_context.state));
2311+
inner.push(serialize_render_stmt(inner_context.state.update));
22962312
}
22972313
inner.push(...inner_context.state.after_update);
22982314
inner.push(
@@ -2757,7 +2773,7 @@ export const template_visitors = {
27572773
snippet = b.call('$.wrap_snippet', snippet, b.id(context.state.analysis.name));
27582774
}
27592775

2760-
const declaration = b.var(node.expression, snippet);
2776+
const declaration = b.const(node.expression, snippet);
27612777

27622778
// Top-level snippets are hoisted so they can be referenced in the `<script>`
27632779
if (context.path.length === 1 && context.path[0].type === 'Fragment') {

packages/svelte/tests/migrate/samples/svelte-element/output.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
<svelte:element this={"div"} />
44

55
<!-- we don't try to fix this bug, we just leave it as-is -->
6-
<svelte:element this="h{n}" />
6+
<svelte:element this="h{n}" />

packages/svelte/tests/parser-modern/samples/snippets/output.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
"parameters": [
2828
{
2929
"type": "Identifier",
30-
"name": "msg",
3130
"start": 43,
31+
"end": 46,
3232
"loc": {
3333
"start": {
3434
"line": 3,
@@ -39,7 +39,7 @@
3939
"column": 25
4040
}
4141
},
42-
"end": 46,
42+
"name": "msg",
4343
"typeAnnotation": {
4444
"type": "TSTypeAnnotation",
4545
"start": 46,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
7+
test({ assert, target }) {
8+
const btn = target.querySelector('button');
9+
10+
btn?.click();
11+
btn?.click();
12+
btn?.click();
13+
flushSync();
14+
15+
assert.htmlEqual(
16+
target.innerHTML,
17+
`
18+
<button>push</button><div style="display: grid; grid-template-columns: 1fr 1fr"><div><p style="color: red">1</p><p style="color: red">2</p><p style="color: red">3</p>
19+
<p style="color: red">4</p><p style="color: red">5</p><p style="color: red">6</p></div><div><p style="color: blue">1</p><p style="color: blue">2</p><p style="color: blue">3</p>
20+
<p style="color: blue">4</p><p style="color: blue">5</p><p style="color: blue">6</p></div></div>
21+
`
22+
);
23+
}
24+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script>
2+
let numbers = $state([1, 2, 3]);
3+
</script>
4+
5+
<button onclick={() => numbers.push(numbers.length + 1)}>
6+
push
7+
</button>
8+
9+
<div style="display: grid; grid-template-columns: 1fr 1fr">
10+
<div>
11+
{#snippet x(n)}
12+
<p style="color: red">{n}</p>
13+
{/snippet}
14+
15+
{#each numbers as n}
16+
{@render x(n)}
17+
{/each}
18+
</div>
19+
20+
<div>
21+
{#snippet x(n)}
22+
<p style="color: blue">{n}</p>
23+
{/snippet}
24+
25+
{#each numbers as n}
26+
{@render x(n)}
27+
{/each}
28+
</div>
29+
</div>

packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ var root_1 = $.template(`Something`, 1);
66
var root = $.template(`<!> `, 5);
77

88
export default function Bind_component_snippet($$anchor) {
9-
var snippet = ($$anchor) => {
9+
const snippet = ($$anchor) => {
1010
var fragment = root_1();
1111

1212
$.append($$anchor, fragment);

0 commit comments

Comments
 (0)