From a129592e5b248a734a68da6e9028941803a3d063 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 14 Jan 2025 01:36:33 +0100 Subject: [PATCH] feat: allow const tag inside `svelte:boundary` (#14993) * feat: allow const tag inside `svelte:boundary` * chore: add better test * docs: update docs for `@const` --- .changeset/curvy-lies-rush.md | 5 +++++ .../docs/03-template-syntax/09-@const.md | 2 +- .../phases/2-analyze/visitors/ConstTag.js | 1 + .../client/visitors/SvelteBoundary.js | 21 +++++++++++++++---- .../const-tag-boundary/FlakyComponent.svelte | 3 +++ .../samples/const-tag-boundary/_config.js | 17 +++++++++++++++ .../samples/const-tag-boundary/main.svelte | 14 +++++++++++++ .../errors.json | 1 + .../input.svelte | 11 ++++++++++ 9 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 .changeset/curvy-lies-rush.md create mode 100644 packages/svelte/tests/runtime-runes/samples/const-tag-boundary/FlakyComponent.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte create mode 100644 packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/errors.json create mode 100644 packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/input.svelte diff --git a/.changeset/curvy-lies-rush.md b/.changeset/curvy-lies-rush.md new file mode 100644 index 000000000000..25c6ffc1d91b --- /dev/null +++ b/.changeset/curvy-lies-rush.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: allow const tag inside `svelte:boundary` diff --git a/documentation/docs/03-template-syntax/09-@const.md b/documentation/docs/03-template-syntax/09-@const.md index f4bde77c23e8..c42d3560fd0e 100644 --- a/documentation/docs/03-template-syntax/09-@const.md +++ b/documentation/docs/03-template-syntax/09-@const.md @@ -11,4 +11,4 @@ The `{@const ...}` tag defines a local constant. {/each} ``` -`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — or a ``. +`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — a `` or a ` a.type === 'Attribute' && a.name === 'slot'))) ) { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js index ef9f6bd798b1..325485d4c003 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js @@ -33,21 +33,34 @@ export function SvelteBoundary(node, context) { const nodes = []; /** @type {Statement[]} */ - const snippet_statements = []; + const external_statements = []; + + const snippets_visits = []; // Capture the `failed` implicit snippet prop for (const child of node.fragment.nodes) { if (child.type === 'SnippetBlock' && child.expression.name === 'failed') { + // we need to delay the visit of the snippets in case they access a ConstTag that is declared + // after the snippets so that the visitor for the const tag can be updated + snippets_visits.push(() => { + /** @type {Statement[]} */ + const init = []; + context.visit(child, { ...context.state, init }); + props.properties.push(b.prop('init', child.expression, child.expression)); + external_statements.push(...init); + }); + } else if (child.type === 'ConstTag') { /** @type {Statement[]} */ const init = []; context.visit(child, { ...context.state, init }); - props.properties.push(b.prop('init', child.expression, child.expression)); - snippet_statements.push(...init); + external_statements.push(...init); } else { nodes.push(child); } } + snippets_visits.forEach((visit) => visit()); + const block = /** @type {BlockStatement} */ (context.visit({ ...node.fragment, nodes })); const boundary = b.stmt( @@ -56,6 +69,6 @@ export function SvelteBoundary(node, context) { context.state.template.push(''); context.state.init.push( - snippet_statements.length > 0 ? b.block([...snippet_statements, boundary]) : boundary + external_statements.length > 0 ? b.block([...external_statements, boundary]) : boundary ); } diff --git a/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/FlakyComponent.svelte b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/FlakyComponent.svelte new file mode 100644 index 000000000000..8bbec90de4ea --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/FlakyComponent.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js new file mode 100644 index 000000000000..4338969a48e0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: '

2

', + mode: ['client'], + test({ target, assert }) { + const btn = target.querySelector('button'); + const p = target.querySelector('p'); + + flushSync(() => { + btn?.click(); + }); + + assert.equal(p?.innerHTML, '4'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte new file mode 100644 index 000000000000..25ea8a3ffc59 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte @@ -0,0 +1,14 @@ + + + + + + {@const double = test * 2} + {#snippet failed()} +

{double}

+ {/snippet} + +
\ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/errors.json b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/errors.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/input.svelte b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/input.svelte new file mode 100644 index 000000000000..5708cc36ca00 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/input.svelte @@ -0,0 +1,11 @@ + + + + {@const x = a} + {#snippet failed()} + {x} + {/snippet} + + \ No newline at end of file