From a34c2337c07eb1423df82de3ff00b0df1a04edb3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 28 May 2025 18:49:35 -0400 Subject: [PATCH] skip the noop if known to be a function --- .../client/visitors/shared/component.js | 7 ++++++- packages/svelte/src/compiler/phases/scope.js | 21 ++++++++++++++++++- packages/svelte/src/internal/client/index.js | 2 +- packages/svelte/src/internal/shared/utils.js | 10 --------- 4 files changed, 27 insertions(+), 13 deletions(-) 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 3aa7f486ea..ff98d6d378 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 @@ -258,12 +258,17 @@ export function build_component(node, component_name, context, anchor = context. } } } else if (attribute.type === 'AttachTag') { + const evaluated = context.state.scope.evaluate(attribute.expression); + let expression = /** @type {Expression} */ (context.visit(attribute.expression)); if (attribute.metadata.expression.has_state) { expression = b.arrow( [b.id('$$node')], - b.call(b.call('$.safe_call', expression), b.id('$$node')) + b.call( + evaluated.is_function ? expression : b.logical('||', expression, b.id('$.noop')), + b.id('$$node') + ) ); } diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 75a26d487b..b4cdb6b446 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -20,6 +20,7 @@ const UNKNOWN = Symbol('unknown'); /** Includes `BigInt` */ export const NUMBER = Symbol('number'); export const STRING = Symbol('string'); +export const FUNCTION = Symbol('string'); /** @type {Record} */ const globals = { @@ -200,6 +201,13 @@ class Evaluation { */ is_number = true; + /** + * True if the value is known to be a function + * @readonly + * @type {boolean} + */ + is_function = true; + /** * @readonly * @type {any} @@ -209,7 +217,7 @@ class Evaluation { /** * * @param {Scope} scope - * @param {Expression} expression + * @param {Expression | FunctionDeclaration} expression * @param {Set} values */ constructor(scope, expression, values) { @@ -500,6 +508,13 @@ class Evaluation { break; } + case 'ArrowFunctionExpression': + case 'FunctionExpression': + case 'FunctionDeclaration': { + this.values.add(FUNCTION); + break; + } + default: { this.values.add(UNKNOWN); } @@ -516,6 +531,10 @@ class Evaluation { this.is_number = false; } + if (value !== FUNCTION) { + this.is_function = false; + } + if (value == null || value === UNKNOWN) { this.is_defined = false; } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 269dcfd17b..71c06d7b1b 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -155,7 +155,7 @@ export { } from './dom/operations.js'; export { attr, clsx } from '../shared/attributes.js'; export { snapshot } from '../shared/clone.js'; -export { noop, fallback, safe_call, to_array } from '../shared/utils.js'; +export { noop, fallback, to_array } from '../shared/utils.js'; export { invalid_default_snippet, validate_dynamic_element_tag, diff --git a/packages/svelte/src/internal/shared/utils.js b/packages/svelte/src/internal/shared/utils.js index 59f9bbfd0e..10f8597520 100644 --- a/packages/svelte/src/internal/shared/utils.js +++ b/packages/svelte/src/internal/shared/utils.js @@ -82,16 +82,6 @@ export function fallback(value, fallback, lazy = false) { : value; } -/** - * @param {*} value - */ -export function safe_call(value) { - if (is_function(value)) { - return value; - } - return noop; -} - /** * When encountering a situation like `let [a, b, c] = $derived(blah())`, * we need to stash an intermediate value that `a`, `b`, and `c` derive