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 <rich.harris@vercel.com>
pull/12242/head
Dominic Gannaway 2 days ago committed by GitHub
parent f9859d10c1
commit 434e1adda6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: deconflict multiple snippets of the same name

@ -1098,13 +1098,12 @@ function serialize_update(statement) {
}
/**
*
* @param {import('../types.js').ComponentClientTransformState} state
* @param {import('estree').Statement[]} update
*/
function serialize_render_stmt(state) {
return state.update.length === 1
? serialize_update(state.update[0])
: b.stmt(b.call('$.template_effect', b.thunk(b.block(state.update))));
function serialize_render_stmt(update) {
return update.length === 1
? serialize_update(update[0])
: b.stmt(b.call('$.template_effect', b.thunk(b.block(update))));
}
/**
@ -1732,7 +1731,7 @@ export const template_visitors = {
}
if (state.update.length > 0) {
body.push(serialize_render_stmt(state));
body.push(serialize_render_stmt(state.update));
}
body.push(...state.after_update);
@ -2179,8 +2178,15 @@ export const template_visitors = {
state.options.preserveComments
);
/** Whether or not we need to wrap the children in `{...}` to avoid declaration conflicts */
const has_declaration = node.fragment.nodes.some((node) => node.type === 'SnippetBlock');
const child_state = has_declaration
? { ...state, init: [], update: [], after_update: [] }
: state;
for (const node of hoisted) {
context.visit(node, state);
context.visit(node, child_state);
}
process_children(
@ -2193,9 +2199,19 @@ export const template_visitors = {
: context.state.node
),
true,
{ ...context, state }
{ ...context, state: child_state }
);
if (has_declaration) {
context.state.init.push(
b.block([
...child_state.init,
child_state.update.length > 0 ? serialize_render_stmt(child_state.update) : b.empty,
...child_state.after_update
])
);
}
if (has_direction_attribute) {
// This fixes an issue with Chromium where updates to text content within an element
// 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 = {
/** @type {import('estree').Statement[]} */
const inner = inner_context.state.init;
if (inner_context.state.update.length > 0) {
inner.push(serialize_render_stmt(inner_context.state));
inner.push(serialize_render_stmt(inner_context.state.update));
}
inner.push(...inner_context.state.after_update);
inner.push(
@ -2757,7 +2773,7 @@ export const template_visitors = {
snippet = b.call('$.wrap_snippet', snippet, b.id(context.state.analysis.name));
}
const declaration = b.var(node.expression, snippet);
const declaration = b.const(node.expression, snippet);
// Top-level snippets are hoisted so they can be referenced in the `<script>`
if (context.path.length === 1 && context.path[0].type === 'Fragment') {

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

@ -27,8 +27,8 @@
"parameters": [
{
"type": "Identifier",
"name": "msg",
"start": 43,
"end": 46,
"loc": {
"start": {
"line": 3,
@ -39,7 +39,7 @@
"column": 25
}
},
"end": 46,
"name": "msg",
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start": 46,

@ -0,0 +1,24 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
mode: ['client'],
test({ assert, target }) {
const btn = target.querySelector('button');
btn?.click();
btn?.click();
btn?.click();
flushSync();
assert.htmlEqual(
target.innerHTML,
`
<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>
<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>
<p style="color: blue">4</p><p style="color: blue">5</p><p style="color: blue">6</p></div></div>
`
);
}
});

@ -0,0 +1,29 @@
<script>
let numbers = $state([1, 2, 3]);
</script>
<button onclick={() => numbers.push(numbers.length + 1)}>
push
</button>
<div style="display: grid; grid-template-columns: 1fr 1fr">
<div>
{#snippet x(n)}
<p style="color: red">{n}</p>
{/snippet}
{#each numbers as n}
{@render x(n)}
{/each}
</div>
<div>
{#snippet x(n)}
<p style="color: blue">{n}</p>
{/snippet}
{#each numbers as n}
{@render x(n)}
{/each}
</div>
</div>

@ -6,7 +6,7 @@ var root_1 = $.template(`Something`, 1);
var root = $.template(`<!> `, 5);
export default function Bind_component_snippet($$anchor) {
var snippet = ($$anchor) => {
const snippet = ($$anchor) => {
var fragment = root_1();
$.append($$anchor, fragment);

Loading…
Cancel
Save