From 1d188f595c9db864642cdb979260f7132ecd01c0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 6 Jun 2025 18:53:56 -0400 Subject: [PATCH] WIP --- .../src/compiler/phases/1-parse/state/tag.js | 29 ++++++++++++++--- .../phases/2-analyze/visitors/AwaitBlock.js | 5 ++- .../phases/2-analyze/visitors/ConstTag.js | 5 ++- .../phases/2-analyze/visitors/HtmlTag.js | 2 +- .../phases/2-analyze/visitors/IfBlock.js | 8 ++++- .../phases/2-analyze/visitors/KeyBlock.js | 3 +- .../phases/2-analyze/visitors/RenderTag.js | 2 +- .../3-transform/client/visitors/AttachTag.js | 4 +-- .../3-transform/client/visitors/AwaitBlock.js | 4 +-- .../3-transform/client/visitors/EachBlock.js | 14 ++++++--- .../3-transform/client/visitors/HtmlTag.js | 4 +-- .../3-transform/client/visitors/IfBlock.js | 4 +-- .../3-transform/client/visitors/KeyBlock.js | 4 +-- .../3-transform/client/visitors/RenderTag.js | 8 +++-- .../client/visitors/shared/utils.js | 31 +++++++++++++++++++ .../svelte/src/compiler/types/template.d.ts | 21 +++++++++++++ 16 files changed, 120 insertions(+), 28 deletions(-) diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 4153463c83..5d77d6a8f4 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -63,7 +63,10 @@ function open(parser) { end: -1, test: read_expression(parser), consequent: create_fragment(), - alternate: null + alternate: null, + metadata: { + expression: create_expression_metadata() + } }); parser.allow_whitespace(); @@ -244,7 +247,10 @@ function open(parser) { error: null, pending: null, then: null, - catch: null + catch: null, + metadata: { + expression: create_expression_metadata() + } }); if (parser.eat('then')) { @@ -326,7 +332,10 @@ function open(parser) { start, end: -1, expression, - fragment: create_fragment() + fragment: create_fragment(), + metadata: { + expression: create_expression_metadata() + } }); parser.stack.push(block); @@ -461,7 +470,10 @@ function next(parser) { elseif: true, test: expression, consequent: create_fragment(), - alternate: null + alternate: null, + metadata: { + expression: create_expression_metadata() + } }); parser.stack.push(child); @@ -624,7 +636,10 @@ function special(parser) { type: 'HtmlTag', start, end: parser.index, - expression + expression, + metadata: { + expression: create_expression_metadata() + } }); return; @@ -699,6 +714,9 @@ function special(parser) { declarations: [{ type: 'VariableDeclarator', id, init, start: id.start, end: init.end }], start: start + 2, // start at const, not at @const end: parser.index - 1 + }, + metadata: { + expression: create_expression_metadata() } }); } @@ -725,6 +743,7 @@ function special(parser) { end: parser.index, expression: /** @type {AST.RenderTag['expression']} */ (expression), metadata: { + expression: create_expression_metadata(), dynamic: false, arguments: [], path: [], diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js index a71f325154..5aa04ba3b9 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js @@ -41,5 +41,8 @@ export function AwaitBlock(node, context) { mark_subtree_dynamic(context.path); - context.next(); + context.visit(node.expression, { ...context.state, expression: node.metadata.expression }); + if (node.pending) context.visit(node.pending); + if (node.then) context.visit(node.then); + if (node.catch) context.visit(node.catch); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js index f723f8447c..d5f5f7b2e0 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js @@ -32,5 +32,8 @@ export function ConstTag(node, context) { e.const_tag_invalid_placement(node); } - context.next(); + const declaration = node.declaration.declarations[0]; + + context.visit(declaration.id); + context.visit(declaration.init, { ...context.state, expression: node.metadata.expression }); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/HtmlTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/HtmlTag.js index c89b11ad36..7b0e501760 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/HtmlTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/HtmlTag.js @@ -15,5 +15,5 @@ export function HtmlTag(node, context) { // unfortunately this is necessary in order to fix invalid HTML mark_subtree_dynamic(context.path); - context.next(); + context.next({ ...context.state, expression: node.metadata.expression }); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js index a65771bcfc..dcdae3587f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js @@ -17,5 +17,11 @@ export function IfBlock(node, context) { mark_subtree_dynamic(context.path); - context.next(); + context.visit(node.test, { + ...context.state, + expression: node.metadata.expression + }); + + context.visit(node.consequent); + if (node.alternate) context.visit(node.alternate); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js index 88bb6a98e7..09e604ea66 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js @@ -16,5 +16,6 @@ export function KeyBlock(node, context) { mark_subtree_dynamic(context.path); - context.next(); + context.visit(node.expression, { ...context.state, expression: node.metadata.expression }); + context.visit(node.fragment); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js index a8c9d408bd..1230ef6b04 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js @@ -54,7 +54,7 @@ export function RenderTag(node, context) { mark_subtree_dynamic(context.path); - context.visit(callee); + context.visit(callee, { ...context.state, expression: node.metadata.expression }); for (const arg of expression.arguments) { const metadata = create_expression_metadata(); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AttachTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AttachTag.js index 85b3720e9c..9d19e5d4a7 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AttachTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AttachTag.js @@ -2,7 +2,7 @@ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import * as b from '../../../../utils/builders.js'; -import { build_legacy_expression } from './shared/utils.js'; +import { build_legacy_expression_2 } from './shared/utils.js'; /** * @param {AST.AttachTag} node @@ -11,7 +11,7 @@ import { build_legacy_expression } from './shared/utils.js'; export function AttachTag(node, context) { const expression = context.state.analysis.runes ? /** @type {Expression} */ (context.visit(node.expression)) - : build_legacy_expression(node.expression, context); + : build_legacy_expression_2(context, node.expression, node.metadata.expression); context.state.init.push(b.stmt(b.call('$.attach', context.state.node, b.thunk(expression)))); context.next(); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js index 044424d14d..fcac636d7d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js @@ -5,7 +5,7 @@ import { extract_identifiers } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; import { create_derived } from '../utils.js'; import { get_value } from './shared/declarations.js'; -import { build_legacy_expression } from './shared/utils.js'; +import { build_legacy_expression_2 } from './shared/utils.js'; /** * @param {AST.AwaitBlock} node @@ -18,7 +18,7 @@ export function AwaitBlock(node, context) { const expression = b.thunk( context.state.analysis.runes ? /** @type {Expression} */ (context.visit(node.expression)) - : build_legacy_expression(node.expression, context) + : build_legacy_expression_2(context, node.expression, node.metadata.expression) ); let then_block; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js index 27fdebf9d5..959ac4723d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js @@ -14,7 +14,7 @@ import { extract_paths, object } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; import { build_getter } from '../utils.js'; import { get_value } from './shared/declarations.js'; -import { build_legacy_expression } from './shared/utils.js'; +import { build_legacy_expression_2 } from './shared/utils.js'; /** * @param {AST.EachBlock} node @@ -31,10 +31,14 @@ export function EachBlock(node, context) { }; const collection = context.state.analysis.runes ? /** @type {Expression} */ (context.visit(node.expression, parent_scope_state)) - : build_legacy_expression(node.expression, { - ...context, - state: parent_scope_state - }); + : build_legacy_expression_2( + { + ...context, + state: parent_scope_state + }, + node.expression, + node.metadata.expression + ); if (!each_node_meta.is_controlled) { context.state.template.push_comment(); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js index b67eff6109..19a4a7de17 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/HtmlTag.js @@ -3,7 +3,7 @@ /** @import { ComponentContext } from '../types' */ import { is_ignored } from '../../../../state.js'; import * as b from '#compiler/builders'; -import { build_legacy_expression } from './shared/utils.js'; +import { build_legacy_expression_2 } from './shared/utils.js'; /** * @param {AST.HtmlTag} node @@ -14,7 +14,7 @@ export function HtmlTag(node, context) { const expression = context.state.analysis.runes ? /** @type {Expression} */ (context.visit(node.expression)) - : build_legacy_expression(node.expression, context); + : build_legacy_expression_2(context, node.expression, node.metadata.expression); const is_svg = context.state.metadata.namespace === 'svg'; const is_mathml = context.state.metadata.namespace === 'mathml'; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js index 3b3badce4b..ad6521e295 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js @@ -2,7 +2,7 @@ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import * as b from '#compiler/builders'; -import { build_legacy_expression } from './shared/utils.js'; +import { build_legacy_expression_2 } from './shared/utils.js'; /** * @param {AST.IfBlock} node @@ -34,7 +34,7 @@ export function IfBlock(node, context) { const test = context.state.analysis.runes ? /** @type {Expression} */ (context.visit(node.test)) - : build_legacy_expression(node.test, context); + : build_legacy_expression_2(context, node.test, node.metadata.expression); /** @type {Expression[]} */ const args = [ diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js index c3b4344da0..a8b66bbe2c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js @@ -2,7 +2,7 @@ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import * as b from '#compiler/builders'; -import { build_legacy_expression } from './shared/utils.js'; +import { build_legacy_expression_2 } from './shared/utils.js'; /** * @param {AST.KeyBlock} node @@ -13,7 +13,7 @@ export function KeyBlock(node, context) { const key = context.state.analysis.runes ? /** @type {Expression} */ (context.visit(node.expression)) - : build_legacy_expression(node.expression, context); + : build_legacy_expression_2(context, node.expression, node.metadata.expression); const body = /** @type {Expression} */ (context.visit(node.fragment)); context.state.init.push( diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js index 083011681f..5b20403a63 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js @@ -3,7 +3,7 @@ /** @import { ComponentContext } from '../types' */ import { unwrap_optional } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; -import { build_legacy_expression } from './shared/utils.js'; +import { build_legacy_expression_2 } from './shared/utils.js'; /** * @param {AST.RenderTag} node @@ -34,7 +34,11 @@ export function RenderTag(node, context) { let snippet_function = context.state.analysis.runes ? /** @type {Expression} */ (context.visit(callee)) - : build_legacy_expression(/** @type {Expression} */ (callee), context); + : build_legacy_expression_2( + context, + /** @type {Expression} */ (callee), + node.metadata.expression + ); if (node.metadata.dynamic) { // If we have a chain expression then ensure a nullish snippet function gets turned into an empty one diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js index 4703faedb6..cce6f15396 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js @@ -435,6 +435,37 @@ function is_pure_expression(expression) { } } +/** + * + * @param {ComponentContext} context + * @param {Expression} expression + * @param {ExpressionMetadata} metadata + */ +export function build_legacy_expression_2(context, expression, metadata) { + const sequence = b.sequence([]); + + for (const binding of metadata.dependencies) { + if (binding.kind === 'normal') { + continue; + } + + var getter = build_getter({ ...binding.node }, context.state); + + if (binding.kind === 'rest_prop') { + getter = b.call('Object.keys', getter); + } else if (binding.kind === 'bindable_prop') { + getter = b.call('$.deep_read_state', getter); + } + + sequence.expressions.push(getter); + } + + const value = /** @type {Expression} */ (context.visit(expression)); + sequence.expressions.push(b.call('$.untrack', b.thunk(value))); + + return sequence; +} + /** * Serializes an expression with reactivity like in Svelte 4 * @param {Expression} expression diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index cefc7fa7a2..2a7ec7b5c6 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -135,6 +135,10 @@ export namespace AST { export interface HtmlTag extends BaseNode { type: 'HtmlTag'; expression: Expression; + /** @internal */ + metadata: { + expression: ExpressionMetadata; + }; } /** An HTML comment */ @@ -151,6 +155,10 @@ export namespace AST { declaration: VariableDeclaration & { declarations: [VariableDeclarator & { id: Pattern; init: Expression }]; }; + /** @internal */ + metadata: { + expression: ExpressionMetadata; + }; } /** A `{@debug ...}` tag */ @@ -165,6 +173,7 @@ export namespace AST { expression: SimpleCallExpression | (ChainExpression & { expression: SimpleCallExpression }); /** @internal */ metadata: { + expression: ExpressionMetadata; dynamic: boolean; arguments: ExpressionMetadata[]; path: SvelteNode[]; @@ -447,6 +456,10 @@ export namespace AST { test: Expression; consequent: Fragment; alternate: Fragment | null; + /** @internal */ + metadata: { + expression: ExpressionMetadata; + }; } /** An `{#await ...}` block */ @@ -461,12 +474,20 @@ export namespace AST { pending: Fragment | null; then: Fragment | null; catch: Fragment | null; + /** @internal */ + metadata: { + expression: ExpressionMetadata; + }; } export interface KeyBlock extends BaseNode { type: 'KeyBlock'; expression: Expression; fragment: Fragment; + /** @internal */ + metadata: { + expression: ExpressionMetadata; + }; } export interface SnippetBlock extends BaseNode {