From d427ffd8b96c5a5509ee602bd77d58a90f24e784 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Jul 2025 10:37:37 -0400 Subject: [PATCH 1/2] chore: encapsulate expression memoization (#16269) * chore: encapsulate expression memoization * add comment * tweak * use b.id --- .../3-transform/client/transform-client.js | 4 +- .../phases/3-transform/client/types.d.ts | 5 +- .../3-transform/client/visitors/Fragment.js | 4 +- .../client/visitors/RegularElement.js | 61 ++++++++++--------- .../client/visitors/SvelteElement.js | 6 +- .../client/visitors/shared/element.js | 37 +++++------ .../client/visitors/shared/utils.js | 52 ++++++++++++---- 7 files changed, 99 insertions(+), 70 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index e85a35cf8e..a9c0651e0f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -168,9 +168,9 @@ export function client_component(analysis, options) { // these are set inside the `Fragment` visitor, and cannot be used until then init: /** @type {any} */ (null), update: /** @type {any} */ (null), - expressions: /** @type {any} */ (null), after_update: /** @type {any} */ (null), - template: /** @type {any} */ (null) + template: /** @type {any} */ (null), + memoizer: /** @type {any} */ (null) }; const module = /** @type {ESTree.Program} */ ( diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts index 2388ee1b00..cf5c942268 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts @@ -12,6 +12,7 @@ import type { AST, Namespace, ValidatedCompileOptions } from '#compiler'; import type { TransformState } from '../types.js'; import type { ComponentAnalysis } from '../../types.js'; import type { Template } from './transform-template/template.js'; +import type { Memoizer } from './visitors/shared/utils.js'; export interface ClientTransformState extends TransformState { /** @@ -49,8 +50,8 @@ export interface ComponentClientTransformState extends ClientTransformState { readonly update: Statement[]; /** Stuff that happens after the render effect (control blocks, dynamic elements, bindings, actions, etc) */ readonly after_update: Statement[]; - /** Expressions used inside the render effect */ - readonly expressions: Expression[]; + /** Memoized expressions */ + readonly memoizer: Memoizer; /** The HTML template string */ readonly template: Template; readonly metadata: { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js index 4825184d31..7cd2bd90ed 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js @@ -6,7 +6,7 @@ import * as b from '#compiler/builders'; import { clean_nodes, infer_namespace } from '../../utils.js'; import { transform_template } from '../transform-template/index.js'; import { process_children } from './shared/fragment.js'; -import { build_render_statement } from './shared/utils.js'; +import { build_render_statement, Memoizer } from './shared/utils.js'; import { Template } from '../transform-template/template.js'; /** @@ -64,8 +64,8 @@ export function Fragment(node, context) { ...context.state, init: [], update: [], - expressions: [], after_update: [], + memoizer: new Memoizer(), template: new Template(), transform: { ...context.state.transform }, metadata: { 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 ae8680f594..eed2a75506 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 @@ -22,7 +22,7 @@ import { build_set_style } from './shared/element.js'; import { process_children } from './shared/fragment.js'; -import { build_render_statement, build_template_chunk, get_expression_id } from './shared/utils.js'; +import { build_render_statement, build_template_chunk, Memoizer } from './shared/utils.js'; import { visit_event_attribute } from './shared/events.js'; /** @@ -253,8 +253,7 @@ export function RegularElement(node, context) { const { value, has_state } = build_attribute_value( attribute.value, context, - (value, metadata) => - metadata.has_call ? get_expression_id(context.state.expressions, value) : value + (value, metadata) => (metadata.has_call ? context.state.memoizer.add(value) : value) ); const update = build_element_attribute_update(node, node_id, name, value, attributes); @@ -455,11 +454,15 @@ function setup_select_synchronization(value_binding, context) { /** * @param {AST.ClassDirective[]} class_directives - * @param {Expression[]} expressions * @param {ComponentContext} context + * @param {Memoizer} memoizer * @return {ObjectExpression | Identifier} */ -export function build_class_directives_object(class_directives, expressions, context) { +export function build_class_directives_object( + class_directives, + context, + memoizer = context.state.memoizer +) { let properties = []; let has_call_or_state = false; @@ -471,38 +474,40 @@ export function build_class_directives_object(class_directives, expressions, con const directives = b.object(properties); - return has_call_or_state ? get_expression_id(expressions, directives) : directives; + return has_call_or_state ? memoizer.add(directives) : directives; } /** * @param {AST.StyleDirective[]} style_directives - * @param {Expression[]} expressions * @param {ComponentContext} context - * @return {ObjectExpression | ArrayExpression}} + * @param {Memoizer} memoizer + * @return {ObjectExpression | ArrayExpression | Identifier}} */ -export function build_style_directives_object(style_directives, expressions, context) { - let normal_properties = []; - let important_properties = []; +export function build_style_directives_object( + style_directives, + context, + memoizer = context.state.memoizer +) { + const normal = b.object([]); + const important = b.object([]); - for (const directive of style_directives) { + let has_call_or_state = false; + + for (const d of style_directives) { const expression = - directive.value === true - ? build_getter({ name: directive.name, type: 'Identifier' }, context.state) - : build_attribute_value(directive.value, context, (value, metadata) => - metadata.has_call ? get_expression_id(expressions, value) : value - ).value; - const property = b.init(directive.name, expression); - - if (directive.modifiers.includes('important')) { - important_properties.push(property); - } else { - normal_properties.push(property); - } + d.value === true + ? build_getter(b.id(d.name), context.state) + : build_attribute_value(d.value, context).value; + + 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; } - return important_properties.length - ? b.array([b.object(normal_properties), b.object(important_properties)]) - : b.object(normal_properties); + const directives = important.properties.length ? b.array([normal, important]) : normal; + + return has_call_or_state ? memoizer.add(directives) : directives; } /** @@ -624,7 +629,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont element === 'select' && attribute.value !== true && !is_text_attribute(attribute); const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => - metadata.has_call ? get_expression_id(state.expressions, value) : value + metadata.has_call ? state.memoizer.add(value) : value ); const evaluated = context.state.scope.evaluate(value); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js index 7381553dbe..6af5f5770e 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js @@ -10,7 +10,7 @@ import { build_attribute_effect, build_set_class } from './shared/element.js'; -import { build_render_statement } from './shared/utils.js'; +import { build_render_statement, Memoizer } from './shared/utils.js'; /** * @param {AST.SvelteElement} node @@ -46,8 +46,8 @@ export function SvelteElement(node, context) { node: element_id, init: [], update: [], - expressions: [], - after_update: [] + after_update: [], + memoizer: new Memoizer() } }; 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 10f942b7d4..d119b0457b 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 @@ -7,7 +7,7 @@ import { is_ignored } from '../../../../../state.js'; import { is_event_attribute } from '../../../../../utils/ast.js'; import * as b from '#compiler/builders'; import { build_class_directives_object, build_style_directives_object } from '../RegularElement.js'; -import { build_expression, build_template_chunk, get_expression_id } from './utils.js'; +import { build_expression, build_template_chunk, Memoizer } from './utils.js'; /** * @param {Array} attributes @@ -28,18 +28,12 @@ export function build_attribute_effect( /** @type {ObjectExpression['properties']} */ const values = []; - /** @type {Expression[]} */ - const expressions = []; - - /** @param {Expression} value */ - function memoize(value) { - return b.id(`$${expressions.push(value) - 1}`); - } + const memoizer = new Memoizer(); for (const attribute of attributes) { if (attribute.type === 'Attribute') { const { value } = build_attribute_value(attribute.value, context, (value, metadata) => - metadata.has_call ? memoize(value) : value + metadata.has_call ? memoizer.add(value) : value ); if ( @@ -57,7 +51,7 @@ export function build_attribute_effect( let value = /** @type {Expression} */ (context.visit(attribute)); if (attribute.metadata.expression.has_call) { - value = memoize(value); + value = memoizer.add(value); } values.push(b.spread(value)); @@ -69,7 +63,7 @@ export function build_attribute_effect( b.prop( 'init', b.array([b.id('$.CLASS')]), - build_class_directives_object(class_directives, expressions, context) + build_class_directives_object(class_directives, context, memoizer) ) ); } @@ -79,21 +73,20 @@ export function build_attribute_effect( b.prop( 'init', b.array([b.id('$.STYLE')]), - build_style_directives_object(style_directives, expressions, context) + build_style_directives_object(style_directives, context, memoizer) ) ); } + const ids = memoizer.apply(); + context.state.init.push( b.stmt( b.call( '$.attribute_effect', element_id, - b.arrow( - expressions.map((_, i) => b.id(`$${i}`)), - b.object(values) - ), - expressions.length > 0 && b.array(expressions.map((expression) => b.thunk(expression))), + b.arrow(ids, b.object(values)), + memoizer.sync_values(), element.metadata.scoped && context.state.analysis.css.hash !== '' && b.literal(context.state.analysis.css.hash), @@ -158,7 +151,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c value = b.call('$.clsx', value); } - return metadata.has_call ? get_expression_id(context.state.expressions, value) : value; + return metadata.has_call ? context.state.memoizer.add(value) : value; }); /** @type {Identifier | undefined} */ @@ -171,7 +164,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c let next; if (class_directives.length) { - next = build_class_directives_object(class_directives, context.state.expressions, context); + next = build_class_directives_object(class_directives, context); has_state ||= class_directives.some((d) => d.metadata.expression.has_state); if (has_state) { @@ -226,7 +219,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 ? get_expression_id(context.state.expressions, value) : value + metadata.has_call ? context.state.memoizer.add(value) : value ); /** @type {Identifier | undefined} */ @@ -235,11 +228,11 @@ export function build_set_style(node_id, attribute, style_directives, context) { /** @type {ObjectExpression | Identifier | undefined} */ let prev; - /** @type {ArrayExpression | ObjectExpression | undefined} */ + /** @type {Expression | undefined} */ let next; if (style_directives.length) { - next = build_style_directives_object(style_directives, context.state.expressions, context); + next = build_style_directives_object(style_directives, context); has_state ||= style_directives.some((d) => d.metadata.expression.has_state); if (has_state) { 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 0bd9bfb128..b80466ccc9 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 @@ -21,12 +21,41 @@ export function memoize_expression(state, value) { } /** - * Pushes `value` into `expressions` and returns a new id - * @param {Expression[]} expressions - * @param {Expression} value + * A utility for extracting complex expressions (such as call expressions) + * from templates and replacing them with `$0`, `$1` etc */ -export function get_expression_id(expressions, value) { - return b.id(`$${expressions.push(value) - 1}`); +export class Memoizer { + /** @type {Array<{ id: Identifier, expression: Expression }>} */ + #sync = []; + + /** + * @param {Expression} expression + */ + add(expression) { + const id = b.id('#'); // filled in later + + this.#sync.push({ id, expression }); + + return id; + } + + apply() { + return this.#sync.map((memo, i) => { + memo.id.name = `$${i}`; + return memo.id; + }); + } + + deriveds(runes = true) { + return this.#sync.map((memo) => + b.let(memo.id, b.call(runes ? '$.derived' : '$.derived_safe_equal', b.thunk(memo.expression))) + ); + } + + sync_values() { + if (this.#sync.length === 0) return; + return b.array(this.#sync.map((memo) => b.thunk(memo.expression))); + } } /** @@ -40,8 +69,7 @@ export function build_template_chunk( values, context, state = context.state, - memoize = (value, metadata) => - metadata.has_call ? get_expression_id(state.expressions, value) : value + memoize = (value, metadata) => (metadata.has_call ? state.memoizer.add(value) : value) ) { /** @type {Expression[]} */ const expressions = []; @@ -128,18 +156,20 @@ export function build_template_chunk( * @param {ComponentClientTransformState} state */ export function build_render_statement(state) { + const ids = state.memoizer.apply(); + const values = state.memoizer.sync_values(); + return b.stmt( b.call( '$.template_effect', b.arrow( - state.expressions.map((_, i) => b.id(`$${i}`)), + ids, state.update.length === 1 && state.update[0].type === 'ExpressionStatement' ? state.update[0].expression : b.block(state.update) ), - state.expressions.length > 0 && - b.array(state.expressions.map((expression) => b.thunk(expression))), - state.expressions.length > 0 && !state.analysis.runes && b.id('$.derived_safe_equal') + values, + values && !state.analysis.runes && b.id('$.derived_safe_equal') ) ); } From 11ec907fd57fec85e090d6a620743bd447f9d2ef Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Jul 2025 14:20:19 -0400 Subject: [PATCH 2/2] chore: simplify props (#16270) * simplify props * simplify * tweak * reorder a bit * simplify * unused * more * more * tweak * also appears to be unnecessary * changeset * apparently this is also unnecessary * explanatory comment --- .changeset/new-trees-behave.md | 5 + .../svelte/src/internal/client/constants.js | 2 - .../src/internal/client/reactivity/props.js | 175 +++++++----------- .../svelte/src/internal/client/runtime.js | 13 +- 4 files changed, 73 insertions(+), 122 deletions(-) create mode 100644 .changeset/new-trees-behave.md diff --git a/.changeset/new-trees-behave.md b/.changeset/new-trees-behave.md new file mode 100644 index 0000000000..d5fab30f3e --- /dev/null +++ b/.changeset/new-trees-behave.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: simplify props diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index dd3d1b2df6..cd5e0d2244 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -15,8 +15,6 @@ export const DESTROYED = 1 << 14; export const EFFECT_RAN = 1 << 15; /** 'Transparent' effects do not create a transition boundary */ export const EFFECT_TRANSPARENT = 1 << 16; -/** Svelte 4 legacy mode props need to be handled with deriveds and be recognized elsewhere, hence the dedicated flag */ -export const LEGACY_DERIVED_PROP = 1 << 17; export const INSPECT_EFFECT = 1 << 18; export const HEAD_EFFECT = 1 << 19; export const EFFECT_HAS_DERIVED = 1 << 20; diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index f3111361c0..f51291b1cc 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -8,12 +8,11 @@ import { PROPS_IS_UPDATED } from '../../../constants.js'; import { get_descriptor, is_function } from '../../shared/utils.js'; -import { mutable_source, set, source, update } from './sources.js'; +import { set, source, update } from './sources.js'; import { derived, derived_safe_equal } from './deriveds.js'; -import { get, captured_signals, untrack } from '../runtime.js'; -import { safe_equals } from './equality.js'; +import { get, untrack } from '../runtime.js'; import * as e from '../errors.js'; -import { LEGACY_DERIVED_PROP, LEGACY_PROPS, STATE_SYMBOL } from '#client/constants'; +import { LEGACY_PROPS, STATE_SYMBOL } from '#client/constants'; import { proxy } from '../proxy.js'; import { capture_store_binding } from './store.js'; import { legacy_mode_flag } from '../../flags/index.js'; @@ -260,89 +259,92 @@ function has_destroyed_component_ctx(current_value) { * @returns {(() => V | ((arg: V) => V) | ((arg: V, mutation: boolean) => V))} */ export function prop(props, key, flags, fallback) { - var immutable = (flags & PROPS_IS_IMMUTABLE) !== 0; var runes = !legacy_mode_flag || (flags & PROPS_IS_RUNES) !== 0; var bindable = (flags & PROPS_IS_BINDABLE) !== 0; var lazy = (flags & PROPS_IS_LAZY_INITIAL) !== 0; - var is_store_sub = false; - var prop_value; - - if (bindable) { - [prop_value, is_store_sub] = capture_store_binding(() => /** @type {V} */ (props[key])); - } else { - prop_value = /** @type {V} */ (props[key]); - } - - // Can be the case when someone does `mount(Component, props)` with `let props = $state({...})` - // or `createClassComponent(Component, props)` - var is_entry_props = STATE_SYMBOL in props || LEGACY_PROPS in props; - - var setter = - (bindable && - (get_descriptor(props, key)?.set ?? - (is_entry_props && key in props && ((v) => (props[key] = v))))) || - undefined; var fallback_value = /** @type {V} */ (fallback); var fallback_dirty = true; - var fallback_used = false; var get_fallback = () => { - fallback_used = true; if (fallback_dirty) { fallback_dirty = false; - if (lazy) { - fallback_value = untrack(/** @type {() => V} */ (fallback)); - } else { - fallback_value = /** @type {V} */ (fallback); - } + + fallback_value = lazy + ? untrack(/** @type {() => V} */ (fallback)) + : /** @type {V} */ (fallback); } return fallback_value; }; - if (prop_value === undefined && fallback !== undefined) { - if (setter && runes) { - e.props_invalid_value(key); - } + /** @type {((v: V) => void) | undefined} */ + var setter; - prop_value = get_fallback(); - if (setter) setter(prop_value); + if (bindable) { + // Can be the case when someone does `mount(Component, props)` with `let props = $state({...})` + // or `createClassComponent(Component, props)` + var is_entry_props = STATE_SYMBOL in props || LEGACY_PROPS in props; + + setter = + get_descriptor(props, key)?.set ?? + (is_entry_props && key in props ? (v) => (props[key] = v) : undefined); + } + + var initial_value; + var is_store_sub = false; + + if (bindable) { + [initial_value, is_store_sub] = capture_store_binding(() => /** @type {V} */ (props[key])); + } else { + initial_value = /** @type {V} */ (props[key]); + } + + if (initial_value === undefined && fallback !== undefined) { + initial_value = get_fallback(); + + if (setter) { + if (runes) e.props_invalid_value(key); + setter(initial_value); + } } /** @type {() => V} */ var getter; + if (runes) { getter = () => { var value = /** @type {V} */ (props[key]); if (value === undefined) return get_fallback(); fallback_dirty = true; - fallback_used = false; return value; }; } else { - // Svelte 4 did not trigger updates when a primitive value was updated to the same value. - // Replicate that behavior through using a derived - var derived_getter = (immutable ? derived : derived_safe_equal)( - () => /** @type {V} */ (props[key]) - ); - derived_getter.f |= LEGACY_DERIVED_PROP; getter = () => { - var value = get(derived_getter); - if (value !== undefined) fallback_value = /** @type {V} */ (undefined); + var value = /** @type {V} */ (props[key]); + + if (value !== undefined) { + // in legacy mode, we don't revert to the fallback value + // if the prop goes from defined to undefined. The easiest + // way to model this is to make the fallback undefined + // as soon as the prop has a value + fallback_value = /** @type {V} */ (undefined); + } + return value === undefined ? fallback_value : value; }; } - // easy mode — prop is never written to - if ((flags & PROPS_IS_UPDATED) === 0 && runes) { + // prop is never written to — we only need a getter + if ((flags & PROPS_IS_UPDATED) === 0) { return getter; } - // intermediate mode — prop is written to, but the parent component had - // `bind:foo` which means we can just call `$$props.foo = value` directly + // prop is written to, but the parent component had `bind:foo` which + // means we can just call `$$props.foo = value` directly if (setter) { var legacy_parent = props.$$legacy; + return function (/** @type {any} */ value, /** @type {boolean} */ mutation) { if (arguments.length > 0) { // We don't want to notify if the value was mutated and the parent is in runes mode. @@ -352,82 +354,39 @@ export function prop(props, key, flags, fallback) { if (!runes || !mutation || legacy_parent || is_store_sub) { /** @type {Function} */ (setter)(mutation ? getter() : value); } + return value; - } else { - return getter(); } + + return getter(); }; } - // hard mode. this is where it gets ugly — the value in the child should - // synchronize with the parent, but it should also be possible to temporarily - // set the value to something else locally. - var from_child = false; - var was_from_child = false; - - // The derived returns the current value. The underlying mutable - // source is written to from various places to persist this value. - var inner_current_value = mutable_source(prop_value); - var current_value = derived(() => { - var parent_value = getter(); - var child_value = get(inner_current_value); - - if (from_child) { - from_child = false; - was_from_child = true; - return child_value; - } - - was_from_child = false; - return (inner_current_value.v = parent_value); - }); - - // Ensure we eagerly capture the initial value if it's bindable - if (bindable) { - get(current_value); - } + // prop is written to, but there's no binding, which means we + // create a derived that we can write to locally + var d = ((flags & PROPS_IS_IMMUTABLE) !== 0 ? derived : derived_safe_equal)(getter); - if (!immutable) current_value.equals = safe_equals; + // Capture the initial value if it's bindable + if (bindable) get(d); return function (/** @type {any} */ value, /** @type {boolean} */ mutation) { - // legacy nonsense — need to ensure the source is invalidated when necessary - // also needed for when handling inspect logic so we can inspect the correct source signal - if (captured_signals !== null) { - // set this so that we don't reset to the parent value if `d` - // is invalidated because of `invalidate_inner_signals` (rather - // than because the parent or child value changed) - from_child = was_from_child; - // invoke getters so that signals are picked up by `invalidate_inner_signals` - getter(); - get(inner_current_value); - } - if (arguments.length > 0) { - const new_value = mutation ? get(current_value) : runes && bindable ? proxy(value) : value; - - if (!current_value.equals(new_value)) { - from_child = true; - set(inner_current_value, new_value); - // To ensure the fallback value is consistent when used with proxies, we - // update the local fallback_value, but only if the fallback is actively used - if (fallback_used && fallback_value !== undefined) { - fallback_value = new_value; - } + const new_value = mutation ? get(d) : runes && bindable ? proxy(value) : value; - if (has_destroyed_component_ctx(current_value)) { - return value; - } + set(d, new_value); - untrack(() => get(current_value)); // force a synchronisation immediately + if (fallback_value !== undefined) { + fallback_value = new_value; } return value; } - if (has_destroyed_component_ctx(current_value)) { - return current_value.v; + // TODO is this still necessary post-#16263? + if (has_destroyed_component_ctx(d)) { + return d.v; } - return get(current_value); + return get(d); }; } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 3e70537d7c..5a798ba3e9 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -20,7 +20,6 @@ import { STATE_SYMBOL, BLOCK_EFFECT, ROOT_EFFECT, - LEGACY_DERIVED_PROP, DISCONNECTED, EFFECT_IS_UPDATING, STALE_REACTION @@ -863,17 +862,7 @@ export function invalidate_inner_signals(fn) { var captured = capture_signals(() => untrack(fn)); for (var signal of captured) { - // Go one level up because derived signals created as part of props in legacy mode - if ((signal.f & LEGACY_DERIVED_PROP) !== 0) { - for (const dep of /** @type {Derived} */ (signal).deps || []) { - if ((dep.f & DERIVED) === 0) { - // Use internal_set instead of set here and below to avoid mutation validation - internal_set(dep, dep.v); - } - } - } else { - internal_set(signal, signal.v); - } + internal_set(signal, signal.v); } }