diff --git a/.changeset/red-phones-brake.md b/.changeset/red-phones-brake.md new file mode 100644 index 000000000000..7d60764649c5 --- /dev/null +++ b/.changeset/red-phones-brake.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow `await` in `{@const }` in more blocks diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js index 35af96ba122e..ddc2897cd76e 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js @@ -24,4 +24,11 @@ export function SvelteBoundary(node, context) { } context.next(); + if (node.fragment.metadata.has_await) { + for (const child of node.fragment.nodes) { + if (child.type === 'SnippetBlock') { + child.body.metadata.has_await = true; + } + } + } } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js index e2e8e93f768c..d8924c4b4c0b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js @@ -37,7 +37,11 @@ export function AwaitBlock(node, context) { const declarations = argument?.declarations ?? []; const block = /** @type {BlockStatement} */ (then_context.visit(node.then, then_context.state)); - then_block = b.arrow(args, b.block([...declarations, ...block.body])); + then_block = b.arrow( + args, + b.block([...declarations, ...block.body]), + node.then.metadata.has_await + ); } if (node.catch) { @@ -53,7 +57,11 @@ export function AwaitBlock(node, context) { catch_context.visit(node.catch, catch_context.state) ); - catch_block = b.arrow(args, b.block([...declarations, ...block.body])); + catch_block = b.arrow( + args, + b.block([...declarations, ...block.body]), + node.catch.metadata.has_await + ); } context.state.init.push( @@ -63,7 +71,11 @@ export function AwaitBlock(node, context) { context.state.node, expression, node.pending - ? b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.pending))) + ? b.arrow( + [b.id('$$anchor')], + /** @type {BlockStatement} */ (context.visit(node.pending)), + node.pending.metadata.has_await + ) : b.null, then_block, catch_block diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js index 225a4f617c50..af9405b89aff 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js @@ -326,12 +326,16 @@ export function EachBlock(node, context) { b.literal(flags), thunk, key_function, - b.arrow(render_args, b.block(declarations.concat(block.body))) + b.arrow(render_args, b.block(declarations.concat(block.body)), node.body.metadata.has_await) ]; if (node.fallback) { args.push( - b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.fallback))) + b.arrow( + [b.id('$$anchor')], + /** @type {BlockStatement} */ (context.visit(node.fallback)), + node.fallback.metadata.has_await + ) ); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js index c7c576101e5d..61d7bdb54833 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js @@ -48,7 +48,8 @@ export function Fragment(node, context) { const is_single_child_not_needing_template = trimmed.length === 1 && (trimmed[0].type === 'SvelteFragment' || trimmed[0].type === 'TitleElement'); - const has_await = context.state.init !== null && (node.metadata.has_await || false); + const has_await = + node.metadata.has_await && !(parent.type === 'Root' && parent.fragment === node); const template_name = context.state.scope.root.unique('root'); // TODO infer name from parent const unsuspend = b.id('$$unsuspend'); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js index 3dd36b180bee..3f487bcb85a9 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js @@ -15,14 +15,21 @@ export function IfBlock(node, context) { const consequent = /** @type {BlockStatement} */ (context.visit(node.consequent)); const consequent_id = b.id(context.state.scope.generate('consequent')); - statements.push(b.var(consequent_id, b.arrow([b.id('$$anchor')], consequent))); + statements.push( + b.var( + consequent_id, + b.arrow([b.id('$$anchor')], consequent, node.consequent.metadata.has_await) + ) + ); let alternate_id; if (node.alternate) { const alternate = /** @type {BlockStatement} */ (context.visit(node.alternate)); alternate_id = b.id(context.state.scope.generate('alternate')); - statements.push(b.var(alternate_id, b.arrow([b.id('$$anchor')], alternate))); + statements.push( + b.var(alternate_id, b.arrow([b.id('$$anchor')], alternate, node.alternate.metadata.has_await)) + ); } const { has_await } = node.metadata.expression; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js index 52850090e3cd..cf27ab62762c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js @@ -18,7 +18,12 @@ export function KeyBlock(node, context) { const body = /** @type {Expression} */ (context.visit(node.fragment)); let statement = add_svelte_meta( - b.call('$.key', context.state.node, key, b.arrow([b.id('$$anchor')], body)), + b.call( + '$.key', + context.state.node, + key, + b.arrow([b.id('$$anchor')], body, node.fragment.metadata.has_await) + ), node, 'key' ); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js index 895522d47ab2..d26ad6be3dd8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js @@ -14,7 +14,7 @@ export function SnippetBlock(node, context) { // TODO hoist where possible /** @type {(Identifier | AssignmentPattern)[]} */ const args = [b.id('$$anchor')]; - const has_await = node.body.metadata.has_await || false; + const has_await = node.body.metadata.has_await; /** @type {BlockStatement} */ let body; 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 70df0223557d..b0005599731a 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 @@ -1,4 +1,4 @@ -/** @import { BlockStatement, Statement, Expression, VariableDeclaration } from 'estree' */ +/** @import { BlockStatement, Statement, Expression, VariableDeclaration, ArrowFunctionExpression, FunctionDeclaration } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import { dev } from '../../../../state.js'; @@ -60,14 +60,26 @@ export function SvelteBoundary(node, context) { const snippet = /** @type {VariableDeclaration} */ (statements[0]); + /** @type {FunctionDeclaration | (ArrowFunctionExpression & { body: BlockStatement})} */ const snippet_fn = dev ? // @ts-expect-error we know this shape is correct snippet.declarations[0].init.arguments[1] : snippet.declarations[0].init; - snippet_fn.body.body.unshift( - ...const_tags.filter((node) => node.type === 'VariableDeclaration') - ); + if (node.fragment.metadata.has_await) { + // we have to make sure the `$.suspend` goes before everything else + snippet_fn.body.body.splice( + dev ? 3 : 2, + 0, + ...const_tags.filter((node) => node.type === 'VariableDeclaration') + ); + } else { + snippet_fn.body.body.splice( + dev ? 2 : 1, + 0, + ...const_tags.filter((node) => node.type === 'VariableDeclaration') + ); + } hoisted.push(snippet); @@ -83,10 +95,19 @@ export function SvelteBoundary(node, context) { const block = /** @type {BlockStatement} */ (context.visit({ ...node.fragment, nodes })); - block.body.unshift(...const_tags); + if (!node.fragment.metadata.has_await) { + block.body.unshift(...const_tags); + } else { + block.body.splice(1, 0, ...const_tags); + } const boundary = b.stmt( - b.call('$.boundary', context.state.node, props, b.arrow([b.id('$$anchor')], block)) + b.call( + '$.boundary', + context.state.node, + props, + b.arrow([b.id('$$anchor')], block, node.fragment.metadata.has_await) + ) ); context.state.template.push_comment(); diff --git a/packages/svelte/tests/runtime-runes/samples/async-const/_config.js b/packages/svelte/tests/runtime-runes/samples/async-const/_config.js index 084d9c3874ef..8aeca875f395 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-const/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-const/_config.js @@ -7,6 +7,6 @@ export default test({ async test({ assert, target }) { await tick(); - assert.htmlEqual(target.innerHTML, `

Hello, world!

`); + assert.htmlEqual(target.innerHTML, `

Hello, world!

5 01234`); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/async-const/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-const/main.svelte index 9321bd792980..c8ac18e5c3d5 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-const/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-const/main.svelte @@ -3,6 +3,7 @@ + {@const number = await Promise.resolve(5)} {#snippet pending()}

Loading...

{/snippet} @@ -10,6 +11,13 @@ {#snippet greet()} {@const greeting = await `Hello, ${name}!`}

{greeting}

+ {number} + {#if number > 4} + {#each { length: number }, index} + {@const i = await index} + {i} + {/each} + {/if} {/snippet} {@render greet()}