From 46daad33a1111ff0efb76e83a03f4298e38e89f8 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Mon, 13 Jan 2025 13:35:31 +0100 Subject: [PATCH 1/3] feat: allow const tag inside `svelte:boundary` --- .changeset/curvy-lies-rush.md | 5 +++++ .../phases/2-analyze/visitors/ConstTag.js | 1 + .../client/visitors/SvelteBoundary.js | 21 +++++++++++++++---- .../const-tag-boundary/FlakyComponent.svelte | 3 +++ .../samples/const-tag-boundary/_config.js | 8 +++++++ .../samples/const-tag-boundary/main.svelte | 14 +++++++++++++ .../errors.json | 1 + .../input.svelte | 11 ++++++++++ 8 files changed, 60 insertions(+), 4 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/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js index 214b4bcb9e6b..3f5e0473c5fd 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js @@ -24,6 +24,7 @@ export function ConstTag(node, context) { grand_parent?.type !== 'EachBlock' && grand_parent?.type !== 'AwaitBlock' && grand_parent?.type !== 'SnippetBlock' && + grand_parent?.type !== 'SvelteBoundary' && ((grand_parent?.type !== 'RegularElement' && grand_parent?.type !== 'SvelteElement') || !grand_parent.attributes.some((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..eff1fe5dd211 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js @@ -0,0 +1,8 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ' 2', + mode: ['client'] + // TODO fix reactivity lost in failed snippet and add a test here +}); 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..67d9b6f77ecb --- /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 From d93af127e69e4589ee1bfd0d4482ef323cb62719 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Mon, 13 Jan 2025 22:09:17 +0100 Subject: [PATCH 2/3] chore: add better test --- .../samples/const-tag-boundary/_config.js | 15 ++++++++++++--- .../samples/const-tag-boundary/main.svelte | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) 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 index eff1fe5dd211..4338969a48e0 100644 --- a/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js @@ -2,7 +2,16 @@ import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ - html: ' 2', - mode: ['client'] - // TODO fix reactivity lost in failed snippet and add a test here + 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 index 67d9b6f77ecb..25ea8a3ffc59 100644 --- a/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte @@ -8,7 +8,7 @@ {@const double = test * 2} {#snippet failed()} - {double} +

{double}

{/snippet}
\ No newline at end of file From c0989f90380c5fcb093f534e92f782149c5df69d Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 14 Jan 2025 00:51:37 +0100 Subject: [PATCH 3/3] docs: update docs for `@const` --- documentation/docs/03-template-syntax/09-@const.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 `