diff --git a/.changeset/fast-ducks-drop.md b/.changeset/fast-ducks-drop.md new file mode 100644 index 0000000000..7622b8e92a --- /dev/null +++ b/.changeset/fast-ducks-drop.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +perf: use walk_readonly for analysis-phase AST walks diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js index 0370155c12..eef5b7fac4 100644 --- a/packages/svelte/src/compiler/migrate/index.js +++ b/packages/svelte/src/compiler/migrate/index.js @@ -1,10 +1,10 @@ /** @import { VariableDeclarator, Node, Identifier, AssignmentExpression, LabeledStatement, ExpressionStatement } from 'estree' */ -/** @import { Visitors } from 'zimmerframe' */ +/** @import { ReadonlyVisitors } from 'zimmerframe' */ /** @import { ComponentAnalysis } from '../phases/types.js' */ /** @import { Scope } from '../phases/scope.js' */ /** @import { AST, Binding, ValidatedCompileOptions } from '#compiler' */ import MagicString from 'magic-string'; -import { walk } from 'zimmerframe'; +import { walk_readonly } from 'zimmerframe'; import { parse } from '../phases/1-parse/index.js'; import { regex_valid_component_name } from '../phases/1-parse/state/element.js'; import { analyze_component } from '../phases/2-analyze/index.js'; @@ -215,11 +215,11 @@ export function migrate(source, { filename, use_ts } = {}) { } if (parsed.instance) { - walk(parsed.instance.content, state, instance_script); + walk_readonly(parsed.instance.content, state, instance_script); } state = { ...state, scope: analysis.template.scope }; - walk(parsed.fragment, state, template); + walk_readonly(parsed.fragment, state, template); let insertion_point = parsed.instance ? /** @type {number} */ (parsed.instance.content.start) @@ -483,7 +483,7 @@ export function migrate(source, { filename, use_ts } = {}) { * }} State */ -/** @type {Visitors} */ +/** @type {ReadonlyVisitors} */ const instance_script = { _(node, { state, next }) { // @ts-expect-error @@ -1054,7 +1054,7 @@ function trim_block(state, start, end) { } } -/** @type {Visitors} */ +/** @type {ReadonlyVisitors} */ const template = { Identifier(node, { state, path }) { handle_identifier(node, state, path); diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index d6052c9c3e..c0b3d6f511 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -1,7 +1,7 @@ /** @import { ComponentAnalysis } from '../../types.js' */ /** @import { AST } from '#compiler' */ -/** @import { Visitors } from 'zimmerframe' */ -import { walk } from 'zimmerframe'; +/** @import { ReadonlyVisitors } from 'zimmerframe' */ +import { walk_readonly } from 'zimmerframe'; import * as e from '../../../errors.js'; import { is_keyframes_node } from '../../css.js'; import { is_global, is_unscoped_pseudo_class } from './utils.js'; @@ -15,7 +15,7 @@ import { is_global, is_unscoped_pseudo_class } from './utils.js'; */ /** - * @typedef {Visitors} CssVisitors + * @typedef {ReadonlyVisitors} CssVisitors */ /** @@ -183,7 +183,7 @@ const css_visitors = { if (node.metadata.is_global_like || node.metadata.is_global) { // So that nested selectors like `:root:not(.x)` are not marked as unused for (const child of node.selectors) { - walk(/** @type {AST.CSS.Node} */ (child), null, { + walk_readonly(/** @type {AST.CSS.Node} */ (child), null, { ComplexSelector(node, context) { node.metadata.used = true; context.next(); @@ -222,7 +222,7 @@ const css_visitors = { node.metadata.is_global_block = is_global_block = true; for (let i = 1; i < child.selectors.length; i++) { - walk(/** @type {AST.CSS.Node} */ (child.selectors[i]), null, { + walk_readonly(/** @type {AST.CSS.Node} */ (child.selectors[i]), null, { ComplexSelector(node) { node.metadata.used = true; } @@ -327,5 +327,5 @@ export function analyze_css(stylesheet, analysis) { analysis }; - walk(stylesheet, css_state, css_visitors); + walk_readonly(stylesheet, css_state, css_visitors); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js index 39f485a9f7..6eece6d4d2 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js @@ -1,5 +1,5 @@ /** @import * as Compiler from '#compiler' */ -import { walk } from 'zimmerframe'; +import { walk_readonly } from 'zimmerframe'; import { get_parent_rules, get_possible_values, @@ -128,7 +128,7 @@ const seen = new Set(); * @param {Iterable} elements */ export function prune(stylesheet, elements) { - walk(/** @type {Compiler.AST.CSS.Node} */ (stylesheet), null, { + walk_readonly(/** @type {Compiler.AST.CSS.Node} */ (stylesheet), null, { Rule(node, context) { if (node.metadata.is_global_block) { context.visit(node.prelude); @@ -176,7 +176,7 @@ function get_relative_selectors(node) { // nesting could be inside pseudo classes like :is, :has or :where for (let selector of selectors) { - walk(selector, null, { + walk_readonly(selector, null, { // @ts-ignore NestingSelector() { has_explicit_nesting_selector = true; @@ -534,7 +534,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element, // with descendants, in which case we scope them all. if (name === 'not' && selector.args) { for (const complex_selector of selector.args.children) { - walk(complex_selector, null, { + walk_readonly(complex_selector, null, { ComplexSelector(node, context) { node.metadata.used = true; context.next(); @@ -869,7 +869,7 @@ function get_ancestor_elements(node, adjacent_only, seen = new Set()) { if (select_element && (!adjacent_only || is_direct_child)) { /** @type {Compiler.AST.RegularElement | null} */ let selectedcontent_element = null; - walk(select_element, null, { + walk_readonly(select_element, null, { RegularElement(child, context) { if (child.name === 'selectedcontent') { selectedcontent_element = child; @@ -912,7 +912,7 @@ function get_descendant_elements(node, adjacent_only, seen = new Set()) { * @param {Compiler.AST.SvelteNode} node */ function walk_children(node) { - walk(node, null, { + walk_readonly(node, null, { _(node, context) { if (node.type === 'RegularElement' || node.type === 'SvelteElement') { descendants.push(node); @@ -946,7 +946,7 @@ function get_descendant_elements(node, adjacent_only, seen = new Set()) { ); if (select_element) { - walk( + walk_readonly( select_element, { inside_option: false }, { diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js index 238c83f00e..33a1689fc3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js @@ -1,6 +1,6 @@ -/** @import { Visitors } from 'zimmerframe' */ +/** @import { ReadonlyVisitors } from 'zimmerframe' */ /** @import { AST } from '#compiler' */ -import { walk } from 'zimmerframe'; +import { walk_readonly } from 'zimmerframe'; import * as w from '../../../warnings.js'; import { is_keyframes_node } from '../../css.js'; @@ -8,10 +8,10 @@ import { is_keyframes_node } from '../../css.js'; * @param {AST.CSS.StyleSheet} stylesheet */ export function warn_unused(stylesheet) { - walk(stylesheet, { stylesheet }, visitors); + walk_readonly(stylesheet, { stylesheet }, visitors); } -/** @type {Visitors} */ +/** @type {ReadonlyVisitors} */ const visitors = { Atrule(node, context) { if (!is_keyframes_node(node)) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index cadd159b3e..38229a727c 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -2,7 +2,7 @@ /** @import { Binding, AST, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */ /** @import { AnalysisState, Visitors } from './types' */ /** @import { Analysis, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */ -import { walk } from 'zimmerframe'; +import { walk_readonly } from 'zimmerframe'; import { parse } from '../1-parse/acorn.js'; import * as e from '../../errors.js'; import * as w from '../../warnings.js'; @@ -295,7 +295,7 @@ export function analyze_module(source, options) { runes: true }); - walk( + walk_readonly( /** @type {ESTree.Node} */ (ast), { scope, @@ -642,7 +642,7 @@ export function analyze_component(root, source, options) { // more legacy nonsense: if an `each` binding is reassigned/mutated, // treat the expression as being mutated as well - walk(/** @type {AST.SvelteNode} */ (template.ast), null, { + walk_readonly(/** @type {AST.SvelteNode} */ (template.ast), null, { EachBlock(node) { const scope = /** @type {Scope} */ (template.scopes.get(node)); @@ -650,7 +650,7 @@ export function analyze_component(root, source, options) { if (binding.updated) { const state = { scope: /** @type {Scope} */ (scope.parent), scopes: template.scopes }; - walk(node.expression, state, { + walk_readonly(node.expression, state, { // @ts-expect-error _: set_scope, Identifier(node, context) { @@ -727,7 +727,7 @@ export function analyze_component(root, source, options) { derived_function_depth: -1 }; - walk(/** @type {AST.SvelteNode} */ (ast), state, visitors); + walk_readonly(/** @type {AST.SvelteNode} */ (ast), state, visitors); } // warn on any nonstate declarations that are a) reassigned and b) referenced in the template @@ -795,7 +795,7 @@ export function analyze_component(root, source, options) { derived_function_depth: -1 }; - walk(/** @type {AST.SvelteNode} */ (ast), state, visitors); + walk_readonly(/** @type {AST.SvelteNode} */ (ast), state, visitors); } for (const [name, binding] of instance.scope.declarations) { @@ -957,7 +957,7 @@ function calculate_blockers(instance, analysis) { if (seen.has(expression)) return; seen.add(expression); - walk( + walk_readonly( expression, { scope }, { @@ -1010,7 +1010,7 @@ function calculate_blockers(instance, analysis) { } } - walk( + walk_readonly( node, { scope }, { diff --git a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts index 9d24f9dbac..b4130c8424 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts +++ b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts @@ -35,12 +35,8 @@ export interface AnalysisState { derived_function_depth: number; } -export type Context = import('zimmerframe').Context< - AST.SvelteNode, - State ->; +export type Context = + import('zimmerframe').ReadonlyContext; -export type Visitors = import('zimmerframe').Visitors< - AST.SvelteNode, - State ->; +export type Visitors = + import('zimmerframe').ReadonlyVisitors; diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js index be3af1e59f..dc9903d3b5 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js @@ -44,7 +44,7 @@ import { } from '../../../../patterns.js'; import { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.js'; import { list } from '../../../../../utils/string.js'; -import { walk } from 'zimmerframe'; +import { walk_readonly } from 'zimmerframe'; import fuzzymatch from '../../../../1-parse/utils/fuzzymatch.js'; import { is_content_editable_binding } from '../../../../../../utils.js'; import * as w from '../../../../../warnings.js'; @@ -456,7 +456,7 @@ export function check_element(node, context) { /** @param {AST.TemplateNode} node */ const has_input_child = (node) => { let has = false; - walk( + walk_readonly( node, {}, { diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index e3560753b4..0a08fb0639 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -1,8 +1,8 @@ /** @import { BinaryOperator, ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, MemberExpression, LogicalOperator, Node, Pattern, UnaryOperator, VariableDeclarator, Super, SimpleLiteral, FunctionExpression, ArrowFunctionExpression } from 'estree' */ -/** @import { Context, Visitor } from 'zimmerframe' */ +/** @import { ReadonlyContext, ReadonlyVisitor } from 'zimmerframe' */ /** @import { AST, BindingKind, DeclarationKind } from '#compiler' */ import is_reference from 'is-reference'; -import { walk } from 'zimmerframe'; +import { walk_readonly } from 'zimmerframe'; import { ExpressionMetadata } from './nodes.js'; import * as b from '#compiler/builders'; import * as e from '../errors.js'; @@ -957,7 +957,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { } /** - * @type {Visitor} + * @type {ReadonlyVisitor} */ const create_block_scope = (node, { state, next }) => { const scope = state.scope.child(true); @@ -967,7 +967,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { }; /** - * @type {Visitor} + * @type {ReadonlyVisitor} */ const SvelteFragment = (node, { state, next }) => { const scope = state.scope.child(); @@ -976,7 +976,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { }; /** - * @type {Visitor} + * @type {ReadonlyVisitor} */ const Component = (node, context) => { node.metadata.scopes = { @@ -1017,7 +1017,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { }; /** - * @type {Visitor} + * @type {ReadonlyVisitor} */ const SvelteDirective = (node, { state, path, visit }) => { state.scope.reference(b.id(node.name.split('.')[0]), path); @@ -1029,7 +1029,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { let has_await = false; - walk(ast, state, { + walk_readonly(ast, state, { AwaitExpression(node, context) { // this doesn't _really_ belong here, but it allows us to // automatically opt into runes mode on encountering @@ -1245,7 +1245,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { let inside_rest = false; let is_rest_id = false; - walk(node.context, null, { + walk_readonly(node.context, null, { Identifier(node) { if (inside_rest && node === id) { is_rest_id = true; @@ -1418,7 +1418,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { /** * @template {{ scope: Scope, scopes: Map }} State * @param {AST.SvelteNode} node - * @param {Context} context + * @param {ReadonlyContext} context */ export function set_scope(node, { next, state }) { const scope = state.scopes.get(node); diff --git a/packages/svelte/src/compiler/utils/ast.js b/packages/svelte/src/compiler/utils/ast.js index 75aadd905b..8ffb81c71a 100644 --- a/packages/svelte/src/compiler/utils/ast.js +++ b/packages/svelte/src/compiler/utils/ast.js @@ -1,6 +1,6 @@ /** @import { AST, Scope } from '#compiler' */ /** @import * as ESTree from 'estree' */ -import { walk } from 'zimmerframe'; +import { walk_readonly } from 'zimmerframe'; import * as b from '#compiler/builders'; /** @@ -149,7 +149,7 @@ export function extract_all_identifiers_from_expression(expr) { /** @type {string[]} */ let keypath = []; - walk( + walk_readonly( expr, {}, { @@ -616,7 +616,7 @@ export function build_assignment_value(operator, left, right) { export function has_await_expression(node) { let has_await = false; - walk(node, null, { + walk_readonly(node, null, { AwaitExpression(_node, context) { has_await = true; context.stop();