From be95b34f29de886bd75ba52e79719726509da7c1 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 16 Jan 2025 10:15:39 +0000 Subject: [PATCH] wip --- packages/svelte/src/ambient.d.ts | 2 + .../src/compiler/phases/2-analyze/index.js | 2 - .../2-analyze/visitors/AwaitExpression.js | 35 ------------------ .../2-analyze/visitors/CallExpression.js | 37 ++++++++++++++++++- .../3-transform/client/transform-client.js | 4 +- .../phases/3-transform/client/utils.js | 25 ++++++++----- .../client/visitors/AwaitExpression.js | 17 --------- .../client/visitors/VariableDeclaration.js | 4 ++ .../internal/client/dom/blocks/boundary.js | 11 +++++- .../svelte/src/internal/client/runtime.js | 6 +-- packages/svelte/src/utils.js | 1 + packages/svelte/types/index.d.ts | 2 + 12 files changed, 73 insertions(+), 73 deletions(-) delete mode 100644 packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js delete mode 100644 packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js diff --git a/packages/svelte/src/ambient.d.ts b/packages/svelte/src/ambient.d.ts index fbcecba8e4..560633c235 100644 --- a/packages/svelte/src/ambient.d.ts +++ b/packages/svelte/src/ambient.d.ts @@ -500,3 +500,5 @@ declare namespace $host { /** @deprecated */ export const toString: never; } + +declare function $await(value: Promise): [V, undefined | Promise]; diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index c3e5f47efc..76c1e94277 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -69,7 +69,6 @@ import { TransitionDirective } from './visitors/TransitionDirective.js'; import { UpdateExpression } from './visitors/UpdateExpression.js'; import { UseDirective } from './visitors/UseDirective.js'; import { VariableDeclarator } from './visitors/VariableDeclarator.js'; -import { AwaitExpression } from './visitors/AwaitExpression.js'; import is_reference from 'is-reference'; import { mark_subtree_dynamic } from './visitors/shared/fragment.js'; @@ -159,7 +158,6 @@ const visitors = { LetDirective, MemberExpression, NewExpression, - AwaitExpression, OnDirective, RegularElement, RenderTag, diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js deleted file mode 100644 index 8fda993559..0000000000 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +++ /dev/null @@ -1,35 +0,0 @@ -/** @import { AwaitExpression } from 'estree' */ -/** @import { Context } from '../types' */ -import { extract_identifiers } from '../../../utils/ast.js'; -import * as w from '../../../warnings.js'; - -/** - * @param {AwaitExpression} node - * @param {Context} context - */ -export function AwaitExpression(node, context) { - const declarator = context.path.at(-1); - const declaration = context.path.at(-2); - const program = context.path.at(-3); - - if (context.state.ast_type === 'instance') { - if ( - declarator?.type !== 'VariableDeclarator' || - context.state.function_depth !== 1 || - declaration?.type !== 'VariableDeclaration' || - program?.type !== 'Program' - ) { - throw new Error('TODO: invalid usage of AwaitExpression in component'); - } - for (const declarator of declaration.declarations) { - for (const id of extract_identifiers(declarator.id)) { - const binding = context.state.scope.get(id.name); - if (binding !== null) { - binding.kind = 'derived'; - } - } - } - } - - context.next(); -} diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 9f51cd61de..116450ea98 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -3,7 +3,7 @@ /** @import { Context } from '../types' */ import { get_rune } from '../../scope.js'; import * as e from '../../../errors.js'; -import { get_parent, unwrap_optional } from '../../../utils/ast.js'; +import { extract_identifiers, get_parent, unwrap_optional } from '../../../utils/ast.js'; import { is_pure, is_safe_identifier } from './shared/utils.js'; import { dev, locate_node, source } from '../../../state.js'; import * as b from '../../../utils/builders.js'; @@ -123,6 +123,39 @@ export function CallExpression(node, context) { break; + case '$await': { + if (node.arguments.length !== 1) { + e.rune_invalid_arguments_length(node, rune, 'exactly one argument'); + } + + const declarator = context.path.at(-1); + const declaration = context.path.at(-2); + const program = context.path.at(-3); + + if (context.state.ast_type !== 'instance') { + throw new Error('TODO: $await can only be used at the top-level of a component'); + } + if ( + declarator?.type !== 'VariableDeclarator' || + declarator?.id.type !== 'ArrayPattern' || + declaration?.type !== 'VariableDeclaration' || + declaration?.declarations.length !== 1 || + context.state.function_depth !== 1 || + program?.type !== 'Program' + ) { + throw new Error('TODO: invalid usage of $await in component'); + } + + for (const id of extract_identifiers(declarator.id)) { + const binding = context.state.scope.get(id.name); + if (binding !== null) { + binding.kind = 'derived'; + } + } + + break; + } + case '$inspect': if (node.arguments.length < 1) { e.rune_invalid_arguments_length(node, rune, 'one or more arguments'); @@ -207,7 +240,7 @@ export function CallExpression(node, context) { } // `$inspect(foo)` or `$derived(foo) should not trigger the `static-state-reference` warning - if (rune === '$inspect' || rune === '$derived') { + if (rune === '$inspect' || rune === '$derived' || rune === '$await') { context.next({ ...context.state, function_depth: context.state.function_depth + 1 }); } else { context.next(); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 6f38e03077..582c32b534 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -55,7 +55,6 @@ import { SvelteWindow } from './visitors/SvelteWindow.js'; import { TitleElement } from './visitors/TitleElement.js'; import { TransitionDirective } from './visitors/TransitionDirective.js'; import { UpdateExpression } from './visitors/UpdateExpression.js'; -import { AwaitExpression } from './visitors/AwaitExpression.js'; import { UseDirective } from './visitors/UseDirective.js'; import { VariableDeclaration } from './visitors/VariableDeclaration.js'; @@ -132,8 +131,7 @@ const visitors = { TransitionDirective, UpdateExpression, UseDirective, - VariableDeclaration, - AwaitExpression + VariableDeclaration }; /** diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 092631f4da..4e78edb8a5 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -328,10 +328,7 @@ export function wrap_unsafe_async_statements(statements, context) { const apply_async_statements = () => { new_statements.push( b.stmt( - b.call( - '$.script_effect', - b.thunk(b.block(/** @type {Statement[]} */ (async_statements))) - ) + b.call('$.script_effect', b.thunk(b.block(/** @type {Statement[]} */ (async_statements)))) ) ); async_statements = null; @@ -364,23 +361,31 @@ export function wrap_unsafe_async_statements(statements, context) { const declarator = statement.declarations[0]; const rune = get_rune(declarator.init, context.state.scope); - if (declarator.init?.type === 'AwaitExpression') { - // TODO: do we need to do anything here? + if ( + declarator.init == null || + declarator.init.type === 'FunctionExpression' || + declarator.init.type === 'ArrowFunctionExpression' || + declarator.init.type === 'Literal' + ) { + // Safe to not wrap } else if (rune === null || rune === '$state' || rune === '$state.raw') { const visited_declarator = /** @type {VariableDeclaration} */ (visited).declarations[0]; new_statements.push(b.let(visited_declarator.id)); - if (rune === '$state') { - debugger; - } if (visited_declarator.init != null) { push_async_statement( b.stmt(b.assignment('=', visited_declarator.id, visited_declarator.init)) ); } continue; - } else if (rune !== '$props' && rune !== '$derived' && rune !== '$derived.by') { + } else if (rune !== '$props' && rune !== '$derived' && rune !== '$derived.by' && rune !== '$await') { debugger; } + } else if ( + statement.type === 'FunctionDeclaration' || + statement.type === 'ClassDeclaration' || + statement.type === 'EmptyStatement' + ) { + // Safe to not wrap } if (async_statements !== 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 deleted file mode 100644 index 99096fa1a3..0000000000 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js +++ /dev/null @@ -1,17 +0,0 @@ -/** @import { AwaitExpression, Expression } from 'estree' */ -/** @import { ComponentContext } from '../types' */ - -import * as b from '../../../../utils/builders.js'; - -/** - * @param {AwaitExpression} node - * @param {ComponentContext} context - */ -export function AwaitExpression(node, context) { - // Inside component - if (context.state.analysis.instance) { - return b.call('$.await_derived', b.thunk(/** @type {Expression} */ (context.visit(node.argument)))); - } - - context.next(); -} diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js index afb90bbec7..94096c6c09 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js @@ -194,6 +194,10 @@ export function VariableDeclaration(node, context) { } continue; } + + if (rune === '$await') { + declarations.push(b.declarator(declarator.id, b.call('$.await_derived', b.thunk(value)))); + } } } else { for (const declarator of node.declarations) { diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index d9df49160e..87070fa65e 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -262,6 +262,7 @@ export function await_derived(fn) { block(() => { var promise = get(derived_promise) + try { get(value) } catch (e) { @@ -294,5 +295,13 @@ export function await_derived(fn) { return value.v; }, AWAIT_EFFECT); - return value; + var pending = derived(() => { + var promise = get(derived_promise); + if (previous_promise === promise) { + return null; + } + return promise; + }); + + return [value, pending]; } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 1fe582d97b..03bbe34625 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -630,7 +630,7 @@ export function update_effect(effect) { handle_error(error, effect, previous_effect, previous_component_context || effect.ctx); } finally { active_effect = previous_effect; - if ((flags & AWAIT_EFFECT) === 0) { + if ((flags & AWAIT_EFFECT) === 0 || previous_effect === null) { is_within_await = previous_is_within_await; } @@ -826,7 +826,7 @@ function process_effects(effect, collected_effects) { var is_skippable_branch = is_branch && (flags & CLEAN) !== 0; var sibling = current_effect.next; - if (!is_skippable_branch && ((flags & (INERT)) === 0 || (flags & (AWAIT_EFFECT)) !== 0)) { + if (!is_skippable_branch && ((flags & INERT) === 0 || (flags & AWAIT_EFFECT) !== 0)) { if ((flags & RENDER_EFFECT) !== 0) { if (is_branch) { current_effect.f ^= CLEAN; @@ -1035,7 +1035,7 @@ export function get(signal) { value = signal.v; - if (is_within_await && value === PENDING) { + if (value === PENDING) { throw PENDING; } diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index 76486d32ac..a41ea9d485 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -429,6 +429,7 @@ const RUNES = /** @type {const} */ ([ '$effect.pre', '$effect.tracking', '$effect.root', + '$await', '$inspect', '$inspect().with', '$inspect.trace', diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index d00b2b01ed..4ba4e3495d 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -3157,4 +3157,6 @@ declare namespace $host { export const toString: never; } +declare function $await(value: Promise): [V, undefined | Promise]; + //# sourceMappingURL=index.d.ts.map