diff --git a/.changeset/curvy-lies-rush.md b/.changeset/curvy-lies-rush.md
new file mode 100644
index 0000000000..25c6ffc1d9
--- /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 f4bde77c23..c42d3560fd 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 ef9f6bd798..325485d4c0 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 0000000000..8bbec90de4
--- /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 0000000000..4338969a48
--- /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 0000000000..25ea8a3ffc
--- /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 0000000000..fe51488c70
--- /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 0000000000..5708cc36ca
--- /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