From f9cc2d25b9b348fb6c1c2b6b6f973804e6a5ed8c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 22 Jan 2026 16:05:26 -0500 Subject: [PATCH] fix: better code generation for const tags with async dependencies (#17518) --- .changeset/witty-streets-carry.md | 5 +++ .../3-transform/client/visitors/ConstTag.js | 16 ++++++--- .../3-transform/server/visitors/ConstTag.js | 6 ++-- .../snapshot/samples/async-const/_config.js | 3 ++ .../_expected/client/index.svelte.js | 35 +++++++++++++++++++ .../_expected/server/index.svelte.js | 33 +++++++++++++++++ .../snapshot/samples/async-const/index.svelte | 6 ++++ 7 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 .changeset/witty-streets-carry.md create mode 100644 packages/svelte/tests/snapshot/samples/async-const/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/async-const/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-const/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-const/index.svelte diff --git a/.changeset/witty-streets-carry.md b/.changeset/witty-streets-carry.md new file mode 100644 index 0000000000..f02d998409 --- /dev/null +++ b/.changeset/witty-streets-carry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: better code generation for const tags with async dependencies diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js index a9f922623e..ffeffe6ea4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js @@ -1,4 +1,4 @@ -/** @import { Pattern } from 'estree' */ +/** @import { Expression, Identifier, Pattern } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ /** @import { ExpressionMetadata } from '../../../nodes.js' */ @@ -88,8 +88,8 @@ export function ConstTag(node, context) { /** * @param {ComponentContext['state']} state - * @param {import('estree').Identifier} id - * @param {import('estree').Expression} expression + * @param {Identifier} id + * @param {Expression} expression * @param {ExpressionMetadata} metadata * @param {import('#compiler').Binding[]} bindings */ @@ -99,7 +99,9 @@ function add_const_declaration(state, id, expression, metadata, bindings) { const after = dev ? [b.stmt(b.call('$.get', id))] : []; const has_await = metadata.has_await; - const blockers = [...metadata.dependencies].map((dep) => dep.blocker).filter((b) => b !== null); + const blockers = [...metadata.dependencies] + .map((dep) => dep.blocker) + .filter((b) => b !== null && b.object !== state.async_consts?.id); if (has_await || state.async_consts || blockers.length > 0) { const run = (state.async_consts ??= { @@ -112,7 +114,11 @@ function add_const_declaration(state, id, expression, metadata, bindings) { const assignment = b.assignment('=', id, expression); const body = after.length === 0 ? assignment : b.block([b.stmt(assignment), ...after]); - if (blockers.length > 0) run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers)))); + if (blockers.length === 1) { + run.thunks.push(b.thunk(/** @type {Expression} */ (blockers[0]))); + } else if (blockers.length > 0) { + run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers)))); + } run.thunks.push(b.thunk(body, has_await)); diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ConstTag.js index fdc31e5238..d2ff9a10b4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ConstTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ConstTag.js @@ -15,7 +15,7 @@ export function ConstTag(node, context) { const has_await = node.metadata.expression.has_await; const blockers = [...node.metadata.expression.dependencies] .map((dep) => dep.blocker) - .filter((b) => b !== null); + .filter((b) => b !== null && b.object !== context.state.async_consts?.id); if (has_await || context.state.async_consts || blockers.length > 0) { const run = (context.state.async_consts ??= { @@ -30,7 +30,9 @@ export function ConstTag(node, context) { context.state.init.push(b.let(identifier.name)); } - if (blockers.length > 0) { + if (blockers.length === 1) { + run.thunks.push(b.thunk(/** @type {Expression} */ (blockers[0]))); + } else if (blockers.length > 0) { run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers)))); } diff --git a/packages/svelte/tests/snapshot/samples/async-const/_config.js b/packages/svelte/tests/snapshot/samples/async-const/_config.js new file mode 100644 index 0000000000..2e30bbeb16 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-const/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({ compileOptions: { experimental: { async: true } } }); diff --git a/packages/svelte/tests/snapshot/samples/async-const/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-const/_expected/client/index.svelte.js new file mode 100644 index 0000000000..8bd7e97780 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-const/_expected/client/index.svelte.js @@ -0,0 +1,35 @@ +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/async'; +import * as $ from 'svelte/internal/client'; + +var root_1 = $.from_html(`

`); + +export default function Async_const($$anchor) { + var fragment = $.comment(); + var node = $.first_child(fragment); + + { + var consequent = ($$anchor) => { + let a; + let b; + + var promises = $.run([ + async () => a = (await $.save($.async_derived(async () => (await $.save(1))())))(), + () => b = $.derived(() => $.get(a) + 1) + ]); + + var p = root_1(); + var text = $.child(p, true); + + $.reset(p); + $.template_effect(() => $.set_text(text, $.get(b)), void 0, void 0, [promises[1]]); + $.append($$anchor, p); + }; + + $.if(node, ($$render) => { + if (true) $$render(consequent); + }); + } + + $.append($$anchor, fragment); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-const/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-const/_expected/server/index.svelte.js new file mode 100644 index 0000000000..a726b903bc --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-const/_expected/server/index.svelte.js @@ -0,0 +1,33 @@ +import 'svelte/internal/flags/async'; +import * as $ from 'svelte/internal/server'; + +export default function Async_const($$renderer) { + if (true) { + $$renderer.push(''); + + let a; + let b; + + var promises = $$renderer.run([ + async () => { + a = (await $.save(1))(); + }, + + () => { + b = a + 1; + } + ]); + + $$renderer.push(`

`); + + $$renderer.async([promises[1]], ($$renderer) => { + $$renderer.push(() => $.escape(b)); + }); + + $$renderer.push(`

`); + } else { + $$renderer.push(''); + } + + $$renderer.push(``); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-const/index.svelte b/packages/svelte/tests/snapshot/samples/async-const/index.svelte new file mode 100644 index 0000000000..cf1bb40d88 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-const/index.svelte @@ -0,0 +1,6 @@ +{#if true} + {@const a = await 1} + {@const b = a + 1} + +

{b}

+{/if}