diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 80ff005ebc..c18ef0c25b 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -265,7 +265,8 @@ export function analyze_module(ast, options) { runes: true, immutable: true, tracing: analysis.tracing, - async_deriveds: new Set() + async_deriveds: new Set(), + blocking_awaits: new Set() }; } @@ -453,7 +454,8 @@ export function analyze_component(root, source, options) { snippet_renderers: new Map(), snippets: new Set(), is_async: false, - async_deriveds: new Set() + async_deriveds: new Set(), + blocking_awaits: new Set() }; if (!runes) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js index f8e4cb6ab8..5c6d45098b 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js @@ -6,15 +6,22 @@ * @param {Context} context */ export function AwaitExpression(node, context) { - if (!context.state.analysis.runes) { - throw new Error('TODO runes mode only'); + const tla = context.state.ast_type === 'instance' && context.state.function_depth === 1; + const blocking = tla || !!context.state.expression; + + if (blocking) { + if (!context.state.analysis.runes) { + throw new Error('TODO runes mode only'); + } + + context.state.analysis.blocking_awaits.add(node); } if (context.state.expression) { context.state.expression.is_async = true; } - if (context.state.ast_type === 'instance' && context.state.scope.function_depth === 1) { + if (tla) { context.state.analysis.is_async = true; } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js index c6151992bf..c892efd421 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js @@ -15,6 +15,7 @@ export function visit_function(node, context) { context.next({ ...context.state, - function_depth: context.state.function_depth + 1 + function_depth: context.state.function_depth + 1, + expression: null }); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js index 809a7b43f8..a26923862c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js @@ -7,12 +7,19 @@ import * as b from '../../../../utils/builders.js'; * @param {ComponentContext} context */ export function AwaitExpression(node, context) { + if (!context.state.analysis.runes) { + return context.next(); + } + + const block = context.state.analysis.blocking_awaits.has(node); + return b.call( b.member( b.await( b.call( '$.preserve_context', - node.argument && /** @type {Expression} */ (context.visit(node.argument)) + node.argument && /** @type {Expression} */ (context.visit(node.argument)), + block && b.true ) ), 'read' diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index ce308f6f17..dcbffdfc58 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -1,5 +1,12 @@ import type { AST, Binding } from '#compiler'; -import type { CallExpression, Identifier, LabeledStatement, Node, Program } from 'estree'; +import type { + AwaitExpression, + CallExpression, + Identifier, + LabeledStatement, + Node, + Program +} from 'estree'; import type { Scope, ScopeRoot } from './scope.js'; export interface Js { @@ -34,6 +41,9 @@ export interface Analysis { /** A set of deriveds that contain `await` expressions */ async_deriveds: Set; + + /** A set of `await` expressions that should trigger suspense */ + blocking_awaits: Set; } export interface ComponentAnalysis extends Analysis { diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index ab9f51d6a0..48f01aaaa9 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -247,14 +247,15 @@ export function trigger_async_boundary(effect, trigger) { /** * @template T * @param {Promise} promise + * @param {boolean} block * @returns {Promise<{ read: () => T }>} */ -export async function preserve_context(promise) { +export function preserve_context(promise, block = false) { var previous_effect = active_effect; var previous_reaction = active_reaction; var previous_component_context = component_context; - let boundary = active_effect; + let boundary = block ? active_effect : null; while (boundary !== null) { if ((boundary.f & BOUNDARY_EFFECT) !== 0) { break; @@ -263,25 +264,25 @@ export async function preserve_context(promise) { boundary = boundary.parent; } - if (boundary === null) { + if (block && boundary === null) { throw new Error('cannot suspend outside a boundary'); } // @ts-ignore - boundary.fn(ASYNC_INCREMENT); + boundary?.fn(ASYNC_INCREMENT); - const value = await promise; + return promise.then((value) => { + return { + read() { + set_active_effect(previous_effect); + set_active_reaction(previous_reaction); + set_component_context(previous_component_context); - return { - read() { - set_active_effect(previous_effect); - set_active_reaction(previous_reaction); - set_component_context(previous_component_context); + // @ts-ignore + boundary?.fn(ASYNC_DECREMENT); - // @ts-ignore - boundary.fn(ASYNC_DECREMENT); - - return value; - } - }; + return value; + } + }; + }); }