feat: allow const tag inside `svelte:boundary` (#14993)

* feat: allow const tag inside `svelte:boundary`

* chore: add better test

* docs: update docs for `@const`
pull/15005/head
Paolo Ricciuti 8 months ago committed by GitHub
parent efa5acf24e
commit a129592e5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: allow const tag inside `svelte:boundary`

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

@ -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')))
) {

@ -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
);
}

@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: '<button></button><p>2</p>',
mode: ['client'],
test({ target, assert }) {
const btn = target.querySelector('button');
const p = target.querySelector('p');
flushSync(() => {
btn?.click();
});
assert.equal(p?.innerHTML, '4');
}
});

@ -0,0 +1,14 @@
<script>
import FlakyComponent from "./FlakyComponent.svelte";
let test=$state(1);
</script>
<button onclick={()=>test++}></button>
<svelte:boundary>
{@const double = test * 2}
{#snippet failed()}
<p>{double}</p>
{/snippet}
<FlakyComponent />
</svelte:boundary>

@ -0,0 +1,11 @@
<script>
let a = $state("");
</script>
<svelte:boundary>
{@const x = a}
{#snippet failed()}
{x}
{/snippet}
<FlakyComponent />
</svelte:boundary>
Loading…
Cancel
Save