From 3fc998f1cd927aafed6ba931b4773f40a2c2bf18 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 26 Oct 2025 10:27:08 -0400 Subject: [PATCH] WIP --- .../3-transform/client/visitors/Program.js | 14 +++---- .../3-transform/server/visitors/HtmlTag.js | 2 +- .../3-transform/server/visitors/Program.js | 14 +++---- .../server/visitors/shared/utils.js | 24 ++++++----- .../svelte/src/internal/server/renderer.js | 7 ++-- .../_expected/server/index.svelte.js | 30 ++++++++------ .../_expected/server/index.svelte.js | 20 ++++++---- .../_expected/server/index.svelte.js | 22 +++++----- .../_expected/server/index.svelte.js | 22 +++++----- .../_expected/server/index.svelte.js | 40 ++++++++++--------- 10 files changed, 111 insertions(+), 84 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Program.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Program.js index fdda4c9b2e..b5d94fb2ad 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Program.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Program.js @@ -339,14 +339,14 @@ function transform_body(program, context) { // TODO we likely need to account for updates that happen after the declaration, // e.g. `let obj = $state()` followed by a later `obj = {...}`, otherwise // a synchronous `{obj.foo}` will fail - } - for (const binding of context.state.scope.declarations.values()) { - // if the binding is updated (TODO or passed to a function, in which case it - // could be mutated), play it safe and block until the end. In future we - // could develop more sophisticated static analysis to optimise further - if (binding.updated) { - binding.blocker = b.member(promises, b.literal(statements.length - 1), true); + for (const binding of context.state.scope.declarations.values()) { + // if the binding is updated (TODO or passed to a function, in which case it + // could be mutated), play it safe and block until the end. In future we + // could develop more sophisticated static analysis to optimise further + if (binding.updated) { + binding.blocker = b.member(promises, b.literal(statements.length - 1), true); + } } } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/HtmlTag.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/HtmlTag.js index a5e044d5d2..08fd2133ee 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/HtmlTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/HtmlTag.js @@ -12,5 +12,5 @@ export function HtmlTag(node, context) { const expression = /** @type {Expression} */ (context.visit(node.expression)); const call = b.call('$.html', expression); - context.state.template.push(create_push(call, node.metadata.expression)); + context.state.template.push(create_push(call, node.metadata.expression, true)); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Program.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Program.js index 7bbe40db4f..3712783502 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Program.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Program.js @@ -209,14 +209,14 @@ function transform_body(program, context) { // TODO we likely need to account for updates that happen after the declaration, // e.g. `let obj = $state()` followed by a later `obj = {...}`, otherwise // a synchronous `{obj.foo}` will fail - } - for (const binding of context.state.scope.declarations.values()) { - // if the binding is updated (TODO or passed to a function, in which case it - // could be mutated), play it safe and block until the end. In future we - // could develop more sophisticated static analysis to optimise further - if (binding.updated) { - binding.blocker = b.member(promises, b.literal(statements.length - 1), true); + for (const binding of context.state.scope.declarations.values()) { + // if the binding is updated (TODO or passed to a function, in which case it + // could be mutated), play it safe and block until the end. In future we + // could develop more sophisticated static analysis to optimise further + if (binding.updated) { + binding.blocker = b.member(promises, b.literal(statements.length - 1), true); + } } } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js index 2d54ab0644..8ba2dd1b07 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js @@ -77,12 +77,11 @@ export function process_children(nodes, { visit, state }) { } for (const node of nodes) { - if (node.type === 'ExpressionTag' && node.metadata.expression.has_await) { + if (node.type === 'ExpressionTag' && node.metadata.expression.is_async()) { flush(); - const visited = /** @type {Expression} */ (visit(node.expression)); - state.template.push( - b.stmt(b.call('$$renderer.push', b.thunk(b.call('$.escape', visited), true))) - ); + + const expression = /** @type {Expression} */ (visit(node.expression)); + state.template.push(create_push(b.call('$.escape', expression), node.metadata.expression)); } else if (node.type === 'Text' || node.type === 'Comment' || node.type === 'ExpressionTag') { sequence.push(node); } else { @@ -277,26 +276,33 @@ export function create_child_block(body, async) { * @param {BlockStatement | Expression} body * @param {ArrayExpression} blockers * @param {boolean} has_await + * @param {boolean} markers */ -export function create_async_block(body, blockers = b.array([]), has_await = true) { +export function create_async_block(body, blockers = b.array([]), has_await = true, markers = true) { return b.stmt( - b.call('$$renderer.async', blockers, b.arrow([b.id('$$renderer')], body, has_await)) + b.call( + '$$renderer.async', + blockers, + b.arrow([b.id('$$renderer')], body, has_await), + markers && b.true + ) ); } /** * @param {Expression} expression * @param {ExpressionMetadata} metadata + * @param {boolean} markers * @returns {Expression | Statement} */ -export function create_push(expression, metadata) { +export function create_push(expression, metadata, markers = false) { if (metadata.is_async()) { let statement = b.stmt(b.call('$$renderer.push', b.thunk(expression, metadata.has_await))); const blockers = metadata.blockers(); if (blockers.elements.length > 0) { - statement = create_async_block(b.block([statement]), blockers, false); + statement = create_async_block(b.block([statement]), blockers, false, markers); } return statement; diff --git a/packages/svelte/src/internal/server/renderer.js b/packages/svelte/src/internal/server/renderer.js index 3562b5a60d..d60fe81d03 100644 --- a/packages/svelte/src/internal/server/renderer.js +++ b/packages/svelte/src/internal/server/renderer.js @@ -102,8 +102,9 @@ export class Renderer { /** * @param {Array>} blockers * @param {(renderer: Renderer) => void} fn + * @param {boolean} markers */ - async(blockers, fn) { + async(blockers, fn, markers) { let callback = fn; if (blockers.length > 0) { @@ -123,9 +124,9 @@ export class Renderer { }; } - this.#out.push(BLOCK_OPEN); + if (markers) this.#out.push(BLOCK_OPEN); this.child(callback); - this.#out.push(BLOCK_CLOSE); + if (markers) this.#out.push(BLOCK_CLOSE); } /** diff --git a/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js index 99c788da5e..448267de8c 100644 --- a/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js @@ -2,24 +2,28 @@ import 'svelte/internal/flags/async'; import * as $ from 'svelte/internal/server'; export default function Async_each_fallback_hoisting($$renderer) { - $$renderer.async([], async ($$renderer) => { - const each_array = $.ensure_array_like((await $.save(Promise.resolve([])))()); + $$renderer.async( + [], + async ($$renderer) => { + const each_array = $.ensure_array_like((await $.save(Promise.resolve([])))()); - if (each_array.length !== 0) { - $$renderer.push(''); + if (each_array.length !== 0) { + $$renderer.push(''); - for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) { - let item = each_array[$$index]; + for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) { + let item = each_array[$$index]; + $$renderer.push(``); + $$renderer.push(async () => $.escape(await Promise.reject('This should never be reached'))); + } + } else { + $$renderer.push(''); $$renderer.push(``); - $$renderer.push(async () => $.escape(await Promise.reject('This should never be reached'))); + $$renderer.push(async () => $.escape(await Promise.resolve(4))); } - } else { - $$renderer.push(''); - $$renderer.push(``); - $$renderer.push(async () => $.escape(await Promise.resolve(4))); - } - }); + }, + true + ); $$renderer.push(``); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js index ee53e3c10a..6d214df2d4 100644 --- a/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js @@ -8,16 +8,20 @@ export default function Async_each_hoisting($$renderer) { $$renderer.push(``); - $$renderer.async([], async ($$renderer) => { - const each_array = $.ensure_array_like((await $.save(Promise.resolve([first, second, third])))()); + $$renderer.async( + [], + async ($$renderer) => { + const each_array = $.ensure_array_like((await $.save(Promise.resolve([first, second, third])))()); - for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) { - let item = each_array[$$index]; + for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) { + let item = each_array[$$index]; - $$renderer.push(``); - $$renderer.push(async () => $.escape(await item)); - } - }); + $$renderer.push(``); + $$renderer.push(async () => $.escape(await item)); + } + }, + true + ); $$renderer.push(``); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js index 1855af6500..3e0b50f3f2 100644 --- a/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js @@ -2,15 +2,19 @@ import 'svelte/internal/flags/async'; import * as $ from 'svelte/internal/server'; export default function Async_if_alternate_hoisting($$renderer) { - $$renderer.async([], async ($$renderer) => { - if ((await $.save(Promise.resolve(false)))()) { - $$renderer.push(''); - $$renderer.push(async () => $.escape(await Promise.reject('no no no'))); - } else { - $$renderer.push(''); - $$renderer.push(async () => $.escape(await Promise.resolve('yes yes yes'))); - } - }); + $$renderer.async( + [], + async ($$renderer) => { + if ((await $.save(Promise.resolve(false)))()) { + $$renderer.push(''); + $$renderer.push(async () => $.escape(await Promise.reject('no no no'))); + } else { + $$renderer.push(''); + $$renderer.push(async () => $.escape(await Promise.resolve('yes yes yes'))); + } + }, + true + ); $$renderer.push(``); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js index a74b59e895..fadaec745f 100644 --- a/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js @@ -2,15 +2,19 @@ import 'svelte/internal/flags/async'; import * as $ from 'svelte/internal/server'; export default function Async_if_hoisting($$renderer) { - $$renderer.async([], async ($$renderer) => { - if ((await $.save(Promise.resolve(true)))()) { - $$renderer.push(''); - $$renderer.push(async () => $.escape(await Promise.resolve('yes yes yes'))); - } else { - $$renderer.push(''); - $$renderer.push(async () => $.escape(await Promise.reject('no no no'))); - } - }); + $$renderer.async( + [], + async ($$renderer) => { + if ((await $.save(Promise.resolve(true)))()) { + $$renderer.push(''); + $$renderer.push(async () => $.escape(await Promise.resolve('yes yes yes'))); + } else { + $$renderer.push(''); + $$renderer.push(async () => $.escape(await Promise.reject('no no no'))); + } + }, + true + ); $$renderer.push(``); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js index e9048861aa..49b8bf9cda 100644 --- a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js @@ -18,24 +18,28 @@ export default function Async_in_derived($$renderer, $$props) { } ]); - $$renderer.async([], async ($$renderer) => { - if (true) { - $$renderer.push(''); - - const yes1 = (await $.save(1))(); - const yes2 = foo((await $.save(1))()); - - const no1 = (async () => { - return await 1; - })(); - - const no2 = (async () => { - return await 1; - })(); - } else { - $$renderer.push(''); - } - }); + $$renderer.async( + [], + async ($$renderer) => { + if (true) { + $$renderer.push(''); + + const yes1 = (await $.save(1))(); + const yes2 = foo((await $.save(1))()); + + const no1 = (async () => { + return await 1; + })(); + + const no2 = (async () => { + return await 1; + })(); + } else { + $$renderer.push(''); + } + }, + true + ); $$renderer.push(``); });