From d5de86803d9539500a9448a2820d514e89df2f90 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jan 2025 09:03:48 -0500 Subject: [PATCH] opt into runes mode when using blocking await --- .../src/compiler/phases/2-analyze/index.js | 19 +++++++++++++------ packages/svelte/src/compiler/phases/scope.js | 18 ++++++++++++++++++ .../svelte/src/compiler/phases/types.d.ts | 1 + 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 41acfc9056..1712702157 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -203,9 +203,9 @@ function js(script, root, allow_reactive_declarations, parent) { body: [] }; - const { scope, scopes } = create_scopes(ast, root, allow_reactive_declarations, parent); + const { scope, scopes, is_async } = create_scopes(ast, root, allow_reactive_declarations, parent); - return { ast, scope, scopes }; + return { ast, scope, scopes, is_async }; } /** @@ -230,7 +230,7 @@ const RESERVED = ['$$props', '$$restProps', '$$slots']; * @returns {Analysis} */ export function analyze_module(ast, options) { - const { scope, scopes } = create_scopes(ast, new ScopeRoot(), false, null); + const { scope, scopes, is_async } = create_scopes(ast, new ScopeRoot(), false, null); for (const [name, references] of scope.references) { if (name[0] !== '$' || RESERVED.includes(name)) continue; @@ -259,7 +259,7 @@ export function analyze_module(ast, options) { ); return { - module: { ast, scope, scopes }, + module: { ast, scope, scopes, is_async }, name: options.filename, accessors: false, runes: true, @@ -282,7 +282,12 @@ export function analyze_component(root, source, options) { const module = js(root.module, scope_root, false, null); const instance = js(root.instance, scope_root, true, module.scope); - const { scope, scopes } = create_scopes(root.fragment, scope_root, false, instance.scope); + const { scope, scopes, is_async } = create_scopes( + root.fragment, + scope_root, + false, + instance.scope + ); /** @type {Template} */ const template = { ast: root.fragment, scope, scopes }; @@ -390,7 +395,9 @@ export function analyze_component(root, source, options) { const component_name = get_component_name(options.filename); - const runes = options.runes ?? Array.from(module.scope.references.keys()).some(is_rune); + const runes = + options.runes ?? + (is_async || instance.is_async || Array.from(module.scope.references.keys()).some(is_rune)); if (!runes) { for (let check of synthetic_stores_legacy_check) { diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 3536dd6a18..0a71127e33 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -345,7 +345,24 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { } }; + let is_async = false; + walk(ast, state, { + AwaitExpression(node, context) { + // this doesn't _really_ belong here, but it allows us to + // automatically opt into runes mode on encountering + // blocking awaits, without doing an additional walk + // before the analysis occurs + is_async ||= context.path.every( + ({ type }) => + type !== 'ArrowFunctionExpression' && + type !== 'FunctionExpression' && + type !== 'FunctionDeclaration' + ); + + context.next(); + }, + // references Identifier(node, { path, state }) { const parent = path.at(-1); @@ -713,6 +730,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { } return { + is_async, scope, scopes }; diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index c98c44225a..bf9c5158a0 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -13,6 +13,7 @@ export interface Js { ast: Program; scope: Scope; scopes: Map; + is_async: boolean; } export interface Template {