From 4dc1c6fb8e2e921d168a2579276c5133c135fac2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 25 Oct 2025 20:52:16 -0400 Subject: [PATCH] chore: tweak memoizer logic --- .changeset/slimy-shirts-lose.md | 5 ++ .../client/visitors/RegularElement.js | 46 +++++++++++-------- .../3-transform/client/visitors/RenderTag.js | 2 +- .../client/visitors/SlotElement.js | 2 +- .../client/visitors/shared/component.js | 6 +-- .../client/visitors/shared/element.js | 14 ++---- .../client/visitors/shared/utils.js | 18 ++++++-- 7 files changed, 55 insertions(+), 38 deletions(-) create mode 100644 .changeset/slimy-shirts-lose.md diff --git a/.changeset/slimy-shirts-lose.md b/.changeset/slimy-shirts-lose.md new file mode 100644 index 0000000000..084fb07ea7 --- /dev/null +++ b/.changeset/slimy-shirts-lose.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: tweak memoizer logic diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index ab119e8f80..3998770a71 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -11,7 +11,7 @@ import { import { is_ignored } from '../../../../state.js'; import { is_event_attribute, is_text_attribute } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; -import { create_attribute, is_custom_element_node } from '../../../nodes.js'; +import { create_attribute, ExpressionMetadata, is_custom_element_node } from '../../../nodes.js'; import { clean_nodes, determine_namespace_for_children } from '../../utils.js'; import { build_getter } from '../utils.js'; import { @@ -267,10 +267,7 @@ export function RegularElement(node, context) { const { value, has_state } = build_attribute_value( attribute.value, context, - (value, metadata) => - metadata.has_call || metadata.has_await - ? context.state.memoizer.add(value, metadata.has_await) - : value + (value, metadata) => context.state.memoizer.add(value, metadata) ); const update = build_element_attribute_update(node, node_id, name, value, attributes); @@ -487,11 +484,25 @@ function setup_select_synchronization(value_binding, context) { ); } +/** + * @param {ExpressionMetadata} target + * @param {ExpressionMetadata} source + */ +function merge_metadata(target, source) { + target.has_assignment ||= source.has_assignment; + target.has_await ||= source.has_await; + target.has_call ||= source.has_call; + target.has_member_expression ||= source.has_member_expression; + target.has_state ||= source.has_state; + + for (const r of source.references) target.references.add(r); + for (const b of source.dependencies) target.dependencies.add(b); +} + /** * @param {AST.ClassDirective[]} class_directives * @param {ComponentContext} context * @param {Memoizer} memoizer - * @return {ObjectExpression | Identifier} */ export function build_class_directives_object( class_directives, @@ -499,26 +510,25 @@ export function build_class_directives_object( memoizer = context.state.memoizer ) { let properties = []; - let has_call_or_state = false; - let has_await = false; + + const metadata = new ExpressionMetadata(); for (const d of class_directives) { + merge_metadata(metadata, d.metadata.expression); + const expression = /** @type Expression */ (context.visit(d.expression)); properties.push(b.init(d.name, expression)); - has_call_or_state ||= d.metadata.expression.has_call || d.metadata.expression.has_state; - has_await ||= d.metadata.expression.has_await; } const directives = b.object(properties); - return has_call_or_state || has_await ? memoizer.add(directives, has_await) : directives; + return memoizer.add(directives, metadata); } /** * @param {AST.StyleDirective[]} style_directives * @param {ComponentContext} context * @param {Memoizer} memoizer - * @return {ObjectExpression | ArrayExpression | Identifier}} */ export function build_style_directives_object( style_directives, @@ -528,10 +538,11 @@ export function build_style_directives_object( const normal = b.object([]); const important = b.object([]); - let has_call_or_state = false; - let has_await = false; + const metadata = new ExpressionMetadata(); for (const d of style_directives) { + merge_metadata(metadata, d.metadata.expression); + const expression = d.value === true ? build_getter(b.id(d.name), context.state) @@ -539,14 +550,11 @@ export function build_style_directives_object( const object = d.modifiers.includes('important') ? important : normal; object.properties.push(b.init(d.name, expression)); - - has_call_or_state ||= d.metadata.expression.has_call || d.metadata.expression.has_state; - has_await ||= d.metadata.expression.has_await; } const directives = important.properties.length ? b.array([normal, important]) : normal; - return has_call_or_state || has_await ? memoizer.add(directives, has_await) : directives; + return memoizer.add(directives, metadata); } /** @@ -675,7 +683,7 @@ function build_element_special_value_attribute( element === 'select' && attribute.value !== true && !is_text_attribute(attribute); const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => - metadata.has_call || metadata.has_await ? state.memoizer.add(value, metadata.has_await) : value + state.memoizer.add(value, metadata) ); const evaluated = context.state.scope.evaluate(value); 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 b7a6e65557..b3619e8669 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 @@ -26,7 +26,7 @@ export function RenderTag(node, context) { let expression = build_expression(context, arg, metadata); if (metadata.has_await || metadata.has_call) { - expression = b.call('$.get', memoizer.add(expression, metadata.has_await)); + expression = b.call('$.get', memoizer.add(expression, metadata)); } args.push(b.thunk(expression)); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js index b87a13253b..f6db21212b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js @@ -35,7 +35,7 @@ export function SlotElement(node, context) { context, (value, metadata) => metadata.has_call || metadata.has_await - ? b.call('$.get', memoizer.add(value, metadata.has_await)) + ? b.call('$.get', memoizer.add(value, metadata)) : value ); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js index 5ca941fd70..688191fd20 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js @@ -134,7 +134,7 @@ export function build_component(node, component_name, context) { props_and_spreads.push( b.thunk( attribute.metadata.expression.has_await || attribute.metadata.expression.has_call - ? b.call('$.get', memoizer.add(expression, attribute.metadata.expression.has_await)) + ? b.call('$.get', memoizer.add(expression, attribute.metadata.expression)) : expression ) ); @@ -149,7 +149,7 @@ export function build_component(node, component_name, context) { build_attribute_value(attribute.value, context, (value, metadata) => { // TODO put the derived in the local block return metadata.has_call || metadata.has_await - ? b.call('$.get', memoizer.add(value, metadata.has_await)) + ? b.call('$.get', memoizer.add(value, metadata)) : value; }).value ) @@ -185,7 +185,7 @@ export function build_component(node, component_name, context) { }); return should_wrap_in_derived - ? b.call('$.get', memoizer.add(value, metadata.has_await)) + ? b.call('$.get', memoizer.add(value, metadata, true)) : value; } ); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js index dd390c99da..29baf2cad5 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js @@ -36,7 +36,7 @@ export function build_attribute_effect( for (const attribute of attributes) { if (attribute.type === 'Attribute') { const { value } = build_attribute_value(attribute.value, context, (value, metadata) => - metadata.has_call || metadata.has_await ? memoizer.add(value, metadata.has_await) : value + memoizer.add(value, metadata) ); if ( @@ -53,9 +53,7 @@ export function build_attribute_effect( } else { let value = /** @type {Expression} */ (context.visit(attribute)); - if (attribute.metadata.expression.has_call || attribute.metadata.expression.has_await) { - value = memoizer.add(value, attribute.metadata.expression.has_await); - } + value = memoizer.add(value, attribute.metadata.expression); values.push(b.spread(value)); } @@ -156,9 +154,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c value = b.call('$.clsx', value); } - return metadata.has_call || metadata.has_await - ? context.state.memoizer.add(value, metadata.has_await) - : value; + return context.state.memoizer.add(value, metadata); }); /** @type {Identifier | undefined} */ @@ -167,7 +163,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c /** @type {ObjectExpression | Identifier | undefined} */ let prev; - /** @type {ObjectExpression | Identifier | undefined} */ + /** @type {Expression | undefined} */ let next; if (class_directives.length) { @@ -228,7 +224,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c */ export function build_set_style(node_id, attribute, style_directives, context) { let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => - metadata.has_call ? context.state.memoizer.add(value, metadata.has_await) : value + context.state.memoizer.add(value, metadata) ); /** @type {Identifier | undefined} */ 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 46d2f2b777..691b78199e 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 @@ -24,12 +24,21 @@ export class Memoizer { /** * @param {Expression} expression - * @param {boolean} has_await + * @param {ExpressionMetadata} metadata + * @param {boolean} memoize_if_state */ - add(expression, has_await) { + add(expression, metadata, memoize_if_state = false) { + const should_memoize = + metadata.has_call || metadata.has_await || (memoize_if_state && metadata.has_state); + + if (!should_memoize) { + // no memoization required + return expression; + } + const id = b.id('#'); // filled in later - (has_await ? this.#async : this.#sync).push({ id, expression }); + (metadata.has_await ? this.#async : this.#sync).push({ id, expression }); return id; } @@ -73,8 +82,7 @@ export function build_template_chunk( values, context, state = context.state, - memoize = (value, metadata) => - metadata.has_call || metadata.has_await ? state.memoizer.add(value, metadata.has_await) : value + memoize = (value, metadata) => state.memoizer.add(value, metadata) ) { /** @type {Expression[]} */ const expressions = [];