From b9a6a16269c0a8d1ccbc00f3d32f485915a6a473 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 28 Oct 2025 12:08:08 +0100 Subject: [PATCH] docs + tidy --- .../src/compiler/phases/2-analyze/index.js | 2 +- .../3-transform/server/visitors/Program.js | 7 ++--- .../server/visitors/shared/utils.js | 30 ++++++++++++++----- .../3-transform/shared/transform-async.js | 24 +++++++++++++++ .../src/internal/client/dom/blocks/if.js | 3 +- .../client/dom/elements/attributes.js | 2 +- .../src/internal/client/reactivity/async.js | 9 +----- .../src/internal/client/reactivity/sources.js | 2 +- 8 files changed, 55 insertions(+), 24 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 7267203a1c..ed71b898ed 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -770,7 +770,7 @@ export function analyze_component(root, source, options) { // for now, assume everything touched by the callee ends up mutating the object // TODO optimise this better - // special case — no need to peek inside effects + // special case — no need to peek inside effects as they only run once async work has completed const rune = get_rune(node, context.state.scope); if (rune === '$effect') return; 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 b40491db41..6b5227d598 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 @@ -1,5 +1,5 @@ /** @import { Node, Program } from 'estree' */ -/** @import { Context, ComponentContext } from '../types' */ +/** @import { Context, ComponentServerTransformState } from '../types' */ import * as b from '#compiler/builders'; import { transform_body } from '../../shared/transform-async.js'; @@ -9,13 +9,12 @@ import { transform_body } from '../../shared/transform-async.js'; */ export function Program(node, context) { if (context.state.is_instance) { - // @ts-ignore wtf - const c = /** @type {ComponentContext} */ (context); + const state = /** @type {ComponentServerTransformState} */ (context.state); return { ...node, body: transform_body( - c.state.analysis.instance_body, + state.analysis.instance_body, b.id('$$renderer.run'), (node) => /** @type {Node} */ (context.visit(node)) ) 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 b31ca94c6e..6bc0ab3233 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 @@ -1,5 +1,5 @@ /** @import { Expression, Identifier, Node, Statement, BlockStatement, ArrayExpression } from 'estree' */ -/** @import { AST, Binding } from '#compiler' */ +/** @import { AST } from '#compiler' */ /** @import { ComponentContext, ServerTransformState } from '../../types.js' */ import { escape_html } from '../../../../../../escaping.js'; @@ -276,12 +276,17 @@ export function create_child_block(body, async) { * @param {BlockStatement | Expression} body * @param {ArrayExpression} blockers * @param {boolean} has_await - * @param {boolean} markers + * @param {boolean} needs_hydration_markers */ -export function create_async_block(body, blockers = b.array([]), has_await = true, markers = true) { +export function create_async_block( + body, + blockers = b.array([]), + has_await = true, + needs_hydration_markers = true +) { return b.stmt( b.call( - markers ? '$$renderer.async_block' : '$$renderer.async', + needs_hydration_markers ? '$$renderer.async_block' : '$$renderer.async', blockers, b.arrow([b.id('$$renderer')], body, has_await) ) @@ -291,17 +296,22 @@ export function create_async_block(body, blockers = b.array([]), has_await = tru /** * @param {Expression} expression * @param {ExpressionMetadata} metadata - * @param {boolean} markers + * @param {boolean} needs_hydration_markers * @returns {Expression | Statement} */ -export function create_push(expression, metadata, markers = false) { +export function create_push(expression, metadata, needs_hydration_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, markers); + statement = create_async_block( + b.block([statement]), + blockers, + false, + needs_hydration_markers + ); } return statement; @@ -321,6 +331,12 @@ export function call_component_renderer(body, component_fn_id) { ); } +/** + * A utility for optimising promises in templates. Without it code like + * `` would be transformed + * into two blocking promises, with it it's using `Promise.all` to await them. + * It also keeps track of blocking promises, i.e. those that need to be resolved before continuing. + */ export class PromiseOptimiser { /** @type {Expression[]} */ expressions = []; diff --git a/packages/svelte/src/compiler/phases/3-transform/shared/transform-async.js b/packages/svelte/src/compiler/phases/3-transform/shared/transform-async.js index 817c3ef1eb..444b8d7d94 100644 --- a/packages/svelte/src/compiler/phases/3-transform/shared/transform-async.js +++ b/packages/svelte/src/compiler/phases/3-transform/shared/transform-async.js @@ -3,16 +3,39 @@ import * as b from '#compiler/builders'; /** + * Transforms the body of the instance script in such a way that await expressions are made non-blocking as much as possible. + * + * Example Transformation: + * ```js + * let x = 1; + * let data = await fetch('/api'); + * let y = data.value; + * ``` + * becomes: + * ```js + * let x = 1; + * var data, y; + * var $$promises = $.run([ + * () => data = await fetch('/api'), + * () => y = data.value + * ]); + * ``` + * where `$$promises` is an array of promises that are resolved in the order they are declared, + * and which expressions in the template can await on like `await $$promises[0]` which means they + * wouldn't have to wait for e.g. `$$promises[1]` to resolve. + * * @param {ComponentAnalysis['instance_body']} instance_body * @param {ESTree.Expression} runner * @param {(node: ESTree.Node) => ESTree.Node} transform * @returns {Array} */ export function transform_body(instance_body, runner, transform) { + // Any sync statements before the first await expression const statements = instance_body.sync.map( (node) => /** @type {ESTree.Statement | ESTree.VariableDeclaration} */ (transform(node)) ); + // Declarations for the await expressions (they will asign to them; need to be hoisted to be available in whole instance scope) if (instance_body.declarations.length > 0) { statements.push( b.declaration( @@ -22,6 +45,7 @@ export function transform_body(instance_body, runner, transform) { ); } + // Thunks for the await expressions if (instance_body.async.length > 0) { const thunks = instance_body.async.map((s) => { if (s.node.type === 'VariableDeclarator') { diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index a80532a68f..7fa5ca464d 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -6,8 +6,7 @@ import { read_hydration_instruction, skip_nodes, set_hydrate_node, - set_hydrating, - hydrate_node + set_hydrating } from '../hydration.js'; import { block } from '../../reactivity/effects.js'; import { HYDRATION_START_ELSE } from '../../../../constants.js'; diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 97bad28be3..d970e7c885 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -480,10 +480,10 @@ function set_attributes( /** * @param {Element & ElementCSSInlineStyle} element - * @param {Array>} blockers * @param {(...expressions: any) => Record} fn * @param {Array<() => any>} sync * @param {Array<() => Promise>} async + * @param {Array>} blockers * @param {string} [css_hash] * @param {boolean} [should_remove_defaults] * @param {boolean} [skip_warning] diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index e3459a2d06..0262270f75 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -25,14 +25,7 @@ import { set_from_async_derived } from './deriveds.js'; import { aborted } from './effects.js'; -import { - hydrate_next, - hydrate_node, - hydrating, - set_hydrate_node, - set_hydrating, - skip_nodes -} from '../dom/hydration.js'; +import { hydrate_next, hydrating, set_hydrate_node, skip_nodes } from '../dom/hydration.js'; /** * @param {Array>} blockers diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 2cf7c80b25..b480d4155a 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -34,7 +34,7 @@ import * as e from '../errors.js'; import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js'; import { get_stack, tag_proxy } from '../dev/tracing.js'; import { component_context, is_runes } from '../context.js'; -import { Batch, current_batch, eager_block_effects, schedule_effect } from './batch.js'; +import { Batch, eager_block_effects, schedule_effect } from './batch.js'; import { proxy } from '../proxy.js'; import { execute_derived } from './deriveds.js';