Skip to content

Commit a129592

Browse files
feat: allow const tag inside svelte:boundary (#14993)
* feat: allow const tag inside `svelte:boundary` * chore: add better test * docs: update docs for `@const`
1 parent efa5acf commit a129592

File tree

9 files changed

+70
-5
lines changed

9 files changed

+70
-5
lines changed

.changeset/curvy-lies-rush.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+
feat: allow const tag inside `svelte:boundary`

documentation/docs/03-template-syntax/[email protected]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ The `{@const ...}` tag defines a local constant.
1111
{/each}
1212
```
1313

14-
`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — or a `<Component />`.
14+
`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — a `<Component />` or a `<svelte:boundary`.

packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export function ConstTag(node, context) {
2424
grand_parent?.type !== 'EachBlock' &&
2525
grand_parent?.type !== 'AwaitBlock' &&
2626
grand_parent?.type !== 'SnippetBlock' &&
27+
grand_parent?.type !== 'SvelteBoundary' &&
2728
((grand_parent?.type !== 'RegularElement' && grand_parent?.type !== 'SvelteElement') ||
2829
!grand_parent.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')))
2930
) {

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,34 @@ export function SvelteBoundary(node, context) {
3333
const nodes = [];
3434

3535
/** @type {Statement[]} */
36-
const snippet_statements = [];
36+
const external_statements = [];
37+
38+
const snippets_visits = [];
3739

3840
// Capture the `failed` implicit snippet prop
3941
for (const child of node.fragment.nodes) {
4042
if (child.type === 'SnippetBlock' && child.expression.name === 'failed') {
43+
// we need to delay the visit of the snippets in case they access a ConstTag that is declared
44+
// after the snippets so that the visitor for the const tag can be updated
45+
snippets_visits.push(() => {
46+
/** @type {Statement[]} */
47+
const init = [];
48+
context.visit(child, { ...context.state, init });
49+
props.properties.push(b.prop('init', child.expression, child.expression));
50+
external_statements.push(...init);
51+
});
52+
} else if (child.type === 'ConstTag') {
4153
/** @type {Statement[]} */
4254
const init = [];
4355
context.visit(child, { ...context.state, init });
44-
props.properties.push(b.prop('init', child.expression, child.expression));
45-
snippet_statements.push(...init);
56+
external_statements.push(...init);
4657
} else {
4758
nodes.push(child);
4859
}
4960
}
5061

62+
snippets_visits.forEach((visit) => visit());
63+
5164
const block = /** @type {BlockStatement} */ (context.visit({ ...node.fragment, nodes }));
5265

5366
const boundary = b.stmt(
@@ -56,6 +69,6 @@ export function SvelteBoundary(node, context) {
5669

5770
context.state.template.push('<!>');
5871
context.state.init.push(
59-
snippet_statements.length > 0 ? b.block([...snippet_statements, boundary]) : boundary
72+
external_statements.length > 0 ? b.block([...external_statements, boundary]) : boundary
6073
);
6174
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script>
2+
throw new Error();
3+
</script>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
html: '<button></button><p>2</p>',
6+
mode: ['client'],
7+
test({ target, assert }) {
8+
const btn = target.querySelector('button');
9+
const p = target.querySelector('p');
10+
11+
flushSync(() => {
12+
btn?.click();
13+
});
14+
15+
assert.equal(p?.innerHTML, '4');
16+
}
17+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script>
2+
import FlakyComponent from "./FlakyComponent.svelte";
3+
let test=$state(1);
4+
</script>
5+
6+
<button onclick={()=>test++}></button>
7+
8+
<svelte:boundary>
9+
{@const double = test * 2}
10+
{#snippet failed()}
11+
<p>{double}</p>
12+
{/snippet}
13+
<FlakyComponent />
14+
</svelte:boundary>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
let a = $state("");
3+
</script>
4+
5+
<svelte:boundary>
6+
{@const x = a}
7+
{#snippet failed()}
8+
{x}
9+
{/snippet}
10+
<FlakyComponent />
11+
</svelte:boundary>

0 commit comments

Comments
 (0)