From d3a69c55a52ad1065d7db227754c618e8ef90f14 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 27 Feb 2025 11:47:01 +0000 Subject: [PATCH 01/17] fix: access last safe value of prop on unmount --- .changeset/mean-dryers-fix.md | 5 ++ .../svelte/src/internal/client/constants.js | 3 + .../svelte/src/internal/client/context.js | 35 ++++++++-- .../src/internal/client/reactivity/props.js | 10 +++ .../svelte/src/internal/client/runtime.js | 12 +++- .../svelte/src/internal/client/types.d.ts | 4 ++ .../ondestroy-prop-access/Component.svelte | 10 +++ .../samples/ondestroy-prop-access/_config.js | 68 +++++++++++++++++++ .../samples/ondestroy-prop-access/main.svelte | 44 ++++++++++++ 9 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 .changeset/mean-dryers-fix.md create mode 100644 packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/main.svelte diff --git a/.changeset/mean-dryers-fix.md b/.changeset/mean-dryers-fix.md new file mode 100644 index 000000000000..96037f344005 --- /dev/null +++ b/.changeset/mean-dryers-fix.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: access last safe value of prop on unmount diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index a4840ce4ebd0..5108bdd2b3ad 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -25,3 +25,6 @@ export const STATE_SYMBOL = Symbol('$state'); export const STATE_SYMBOL_METADATA = Symbol('$state metadata'); export const LEGACY_PROPS = Symbol('legacy props'); export const LOADING_ATTR_SYMBOL = Symbol(''); + +export const CTX_CONTAINS_TEARDOWN = 1; +export const CTX_DESTROYED = 2; diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index bd94d5ad8a19..7ac3981b2a6b 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -11,8 +11,9 @@ import { set_active_reaction, untrack } from './runtime.js'; -import { effect } from './reactivity/effects.js'; +import { effect, teardown } from './reactivity/effects.js'; import { legacy_mode_flag } from '../flags/index.js'; +import { CTX_CONTAINS_TEARDOWN, CTX_DESTROYED } from './constants.js'; /** @type {ComponentContext | null} */ export let component_context = null; @@ -112,15 +113,17 @@ export function getAllContexts() { * @returns {void} */ export function push(props, runes = false, fn) { - component_context = { + var ctx = (component_context = { p: component_context, c: null, e: null, + f: 0, m: false, s: props, x: null, - l: null - }; + l: null, + tp: props + }); if (legacy_mode_flag && !runes) { component_context.l = { @@ -131,6 +134,24 @@ export function push(props, runes = false, fn) { }; } + teardown(() => { + if (ctx.f !== CTX_CONTAINS_TEARDOWN) { + return; + } + // Mark the context as destroyed, so any derived props can use + // the latest known value before teardown + ctx.f = CTX_DESTROYED; + + var teardown_props = ctx.tp; + // Apply the latest known props before teardown over existing props + for (var key in teardown_props) { + Object.defineProperty(props, key, { + value: teardown_props[key], + configurable: true + }); + } + }); + if (DEV) { // component function component_context.function = fn; @@ -171,6 +192,12 @@ export function pop(component) { dev_current_component_function = context_stack_item.p?.function ?? null; } context_stack_item.m = true; + + effect(() => { + if (context_stack_item.f === CTX_CONTAINS_TEARDOWN) { + context_stack_item.tp = { ...context_stack_item.s }; + } + }); } // Micro-optimization: Don't set .a above to the empty object // so it can be garbage-collected when the return here is unused diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index 5a3b30281f9f..fdff79131c6a 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -23,6 +23,8 @@ import { safe_equals } from './equality.js'; import * as e from '../errors.js'; import { BRANCH_EFFECT, + CTX_DESTROYED, + DESTROYED, LEGACY_DERIVED_PROP, LEGACY_PROPS, ROOT_EFFECT, @@ -31,6 +33,7 @@ import { import { proxy } from '../proxy.js'; import { capture_store_binding } from './store.js'; import { legacy_mode_flag } from '../../flags/index.js'; +import { component_context } from '../context.js'; /** * @param {((value?: number) => number)} fn @@ -369,6 +372,12 @@ export function prop(props, key, flags, fallback) { // source is written to from various places to persist this value. var inner_current_value = mutable_source(prop_value); var current_value = derived(() => { + var ctx = component_context; + + if (ctx !== null && ctx.f === CTX_DESTROYED) { + return get(inner_current_value); + } + var parent_value = getter(); var child_value = get(inner_current_value); @@ -413,6 +422,7 @@ export function prop(props, key, flags, fallback) { return value; } + return get(current_value); }; } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 486c819f3671..f5abc09aa6b5 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -22,7 +22,8 @@ import { ROOT_EFFECT, LEGACY_DERIVED_PROP, DISCONNECTED, - BOUNDARY_EFFECT + BOUNDARY_EFFECT, + CTX_CONTAINS_TEARDOWN } from './constants.js'; import { flush_tasks } from './dom/task.js'; import { internal_set } from './reactivity/sources.js'; @@ -566,7 +567,14 @@ export function update_effect(effect) { execute_effect_teardown(effect); var teardown = update_reaction(effect); - effect.teardown = typeof teardown === 'function' ? teardown : null; + if (typeof teardown === 'function') { + if (effect.ctx !== null && effect.ctx.f === 0) { + effect.ctx.f = CTX_CONTAINS_TEARDOWN; + } + effect.teardown = teardown; + } else { + effect.teardown = null; + } effect.wv = write_version; var deps = effect.deps; diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 7208ed77837e..ac5f6101b4d8 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -20,6 +20,8 @@ export type ComponentContext = { effect: null | Effect; reaction: null | Reaction; }>; + /** ctx flags */ + f: number; /** mounted */ m: boolean; /** @@ -57,6 +59,8 @@ export type ComponentContext = { * dev mode only: the component function */ function?: any; + /** teardown props */ + tp: Record; }; export type ComponentContextLegacy = ComponentContext & { diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/Component.svelte b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/Component.svelte new file mode 100644 index 000000000000..afe0fe10873d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/Component.svelte @@ -0,0 +1,10 @@ + + +

{count}

+ + diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js new file mode 100644 index 000000000000..9b43fa16b9c4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js @@ -0,0 +1,68 @@ +import { ok, test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + async test({ assert, target, logs }) { + const [btn1, btn2, btn3] = target.querySelectorAll('button'); + let ps = [...target.querySelectorAll('p')]; + + for (const p of ps) { + assert.equal(p.innerHTML, '0'); + } + + flushSync(() => { + btn1.click(); + }); + + // prop update normally if we are not unmounting + for (const p of ps) { + assert.equal(p.innerHTML, '1'); + } + + flushSync(() => { + btn3.click(); + }); + + // binding still works and update the value correctly + for (const p of ps) { + assert.equal(p.innerHTML, '0'); + } + + flushSync(() => { + btn1.click(); + }); + + flushSync(() => { + btn1.click(); + }); + + console.warn(logs); + + // the five components guarded by `count < 2` unmount and log + assert.deepEqual(logs, [1, true, 1, true, 1, true, 1, true, 1, true]); + + flushSync(() => { + btn2.click(); + }); + + // the three components guarded by `show` unmount and log + assert.deepEqual(logs, [ + 1, + true, + 1, + true, + 1, + true, + 1, + true, + 1, + true, + 2, + true, + 2, + true, + 2, + false + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/main.svelte b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/main.svelte new file mode 100644 index 000000000000..3e15ac32d4cb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/main.svelte @@ -0,0 +1,44 @@ + + + + + + +{#if count < 2} + +{/if} + + +{#if count < 2} + +{/if} + + +{#if count < 2} + +{/if} + + +{#if show} + +{/if} + + + + + + + + + + + + From eb9f587ffd8c1a6392e23911c1dac87f525dfebe Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 27 Feb 2025 11:52:59 +0000 Subject: [PATCH 02/17] fix: access last safe value of prop on unmount --- packages/svelte/src/internal/client/runtime.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index f5abc09aa6b5..d7364247bb31 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -567,14 +567,17 @@ export function update_effect(effect) { execute_effect_teardown(effect); var teardown = update_reaction(effect); + if (typeof teardown === 'function') { - if (effect.ctx !== null && effect.ctx.f === 0) { - effect.ctx.f = CTX_CONTAINS_TEARDOWN; + var ctx = effect.ctx; + if (ctx !== null && ctx.f === 0) { + ctx.f = CTX_CONTAINS_TEARDOWN; } effect.teardown = teardown; } else { effect.teardown = null; } + effect.wv = write_version; var deps = effect.deps; From b275b249d46d66dd66a8453ca8a2d01aff72b9a2 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 27 Feb 2025 12:06:04 +0000 Subject: [PATCH 03/17] fix: access last safe value of prop on unmount --- packages/svelte/src/internal/client/context.js | 3 ++- .../svelte/src/internal/client/reactivity/props.js | 14 +++++++++++++- .../samples/ondestroy-prop-access/_config.js | 4 ++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index 7ac3981b2a6b..180f7f2490b7 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -14,6 +14,7 @@ import { import { effect, teardown } from './reactivity/effects.js'; import { legacy_mode_flag } from '../flags/index.js'; import { CTX_CONTAINS_TEARDOWN, CTX_DESTROYED } from './constants.js'; +import { define_property } from '../shared/utils.js'; /** @type {ComponentContext | null} */ export let component_context = null; @@ -145,7 +146,7 @@ export function push(props, runes = false, fn) { var teardown_props = ctx.tp; // Apply the latest known props before teardown over existing props for (var key in teardown_props) { - Object.defineProperty(props, key, { + define_property(props, key, { value: teardown_props[key], configurable: true }); diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index fdff79131c6a..709b8a83524b 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -7,7 +7,7 @@ import { PROPS_IS_RUNES, PROPS_IS_UPDATED } from '../../../constants.js'; -import { get_descriptor, is_function } from '../../shared/utils.js'; +import { define_property, get_descriptor, is_function } from '../../shared/utils.js'; import { mutable_source, set, source, update } from './sources.js'; import { derived, derived_safe_equal } from './deriveds.js'; import { @@ -201,6 +201,18 @@ const spread_props_handler = { } return false; }, + defineProperty(target, key, descriptor) { + let i = target.props.length; + while (i--) { + let p = target.props[i]; + if (is_function(p)) p = p(); + if (typeof p === 'object' && p !== null && key in p) { + define_property(p, key, descriptor); + return true; + } + } + return false; + }, getOwnPropertyDescriptor(target, key) { let i = target.props.length; while (i--) { diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js index 9b43fa16b9c4..794d1f7017d5 100644 --- a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js @@ -61,8 +61,8 @@ export default test({ true, 2, true, - 2, - false + 1, + true ]); } }); From 7cb4c283af36d1951548d9579da1cb4bbfa0d603 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 27 Feb 2025 12:06:49 +0000 Subject: [PATCH 04/17] fix: access last safe value of prop on unmount --- .../src/internal/client/reactivity/props.js | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index 709b8a83524b..c4666caa9b4c 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -10,26 +10,10 @@ import { import { define_property, get_descriptor, is_function } from '../../shared/utils.js'; import { mutable_source, set, source, update } from './sources.js'; import { derived, derived_safe_equal } from './deriveds.js'; -import { - active_effect, - get, - captured_signals, - set_active_effect, - untrack, - active_reaction, - set_active_reaction -} from '../runtime.js'; +import { get, captured_signals, untrack } from '../runtime.js'; import { safe_equals } from './equality.js'; import * as e from '../errors.js'; -import { - BRANCH_EFFECT, - CTX_DESTROYED, - DESTROYED, - LEGACY_DERIVED_PROP, - LEGACY_PROPS, - ROOT_EFFECT, - STATE_SYMBOL -} from '../constants.js'; +import { CTX_DESTROYED, LEGACY_DERIVED_PROP, LEGACY_PROPS, STATE_SYMBOL } from '../constants.js'; import { proxy } from '../proxy.js'; import { capture_store_binding } from './store.js'; import { legacy_mode_flag } from '../../flags/index.js'; @@ -431,7 +415,6 @@ export function prop(props, key, flags, fallback) { } untrack(() => get(current_value)); // force a synchronisation immediately } - return value; } From e994a7a1326fe7813d3d7dc92b4751b8b5fdcad4 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 27 Feb 2025 12:07:07 +0000 Subject: [PATCH 05/17] fix: access last safe value of prop on unmount --- packages/svelte/src/internal/client/reactivity/props.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index c4666caa9b4c..2b21627bdd7b 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -415,9 +415,9 @@ export function prop(props, key, flags, fallback) { } untrack(() => get(current_value)); // force a synchronisation immediately } + return value; } - return get(current_value); }; } From b429eb6e68182e66233cc639c634ad61fa79ab05 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 27 Feb 2025 13:39:10 +0000 Subject: [PATCH 06/17] use snapshot --- packages/svelte/src/internal/client/context.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index 180f7f2490b7..e30b4aac45ff 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -15,6 +15,7 @@ import { effect, teardown } from './reactivity/effects.js'; import { legacy_mode_flag } from '../flags/index.js'; import { CTX_CONTAINS_TEARDOWN, CTX_DESTROYED } from './constants.js'; import { define_property } from '../shared/utils.js'; +import { snapshot } from '../shared/clone.js'; /** @type {ComponentContext | null} */ export let component_context = null; @@ -196,7 +197,7 @@ export function pop(component) { effect(() => { if (context_stack_item.f === CTX_CONTAINS_TEARDOWN) { - context_stack_item.tp = { ...context_stack_item.s }; + context_stack_item.tp = snapshot(context_stack_item.s, true) } }); } From f511b1bd3f03b3b419d8c065b7e8b32fdcc9fa48 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 27 Feb 2025 13:43:44 +0000 Subject: [PATCH 07/17] revert snapshot --- packages/svelte/src/internal/client/context.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index e30b4aac45ff..180f7f2490b7 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -15,7 +15,6 @@ import { effect, teardown } from './reactivity/effects.js'; import { legacy_mode_flag } from '../flags/index.js'; import { CTX_CONTAINS_TEARDOWN, CTX_DESTROYED } from './constants.js'; import { define_property } from '../shared/utils.js'; -import { snapshot } from '../shared/clone.js'; /** @type {ComponentContext | null} */ export let component_context = null; @@ -197,7 +196,7 @@ export function pop(component) { effect(() => { if (context_stack_item.f === CTX_CONTAINS_TEARDOWN) { - context_stack_item.tp = snapshot(context_stack_item.s, true) + context_stack_item.tp = { ...context_stack_item.s }; } }); } From 2e9d8ec738a88e2203bd560d2277929dd5dfe8b1 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 27 Feb 2025 15:40:06 +0000 Subject: [PATCH 08/17] feedback --- packages/svelte/src/internal/client/constants.js | 4 ++-- packages/svelte/src/internal/client/context.js | 6 +++--- .../src/internal/client/reactivity/props.js | 2 +- packages/svelte/src/internal/client/runtime.js | 4 ++-- .../ondestroy-prop-access-2/Component.svelte | 11 +++++++++++ .../samples/ondestroy-prop-access-2/_config.js | 14 ++++++++++++++ .../samples/ondestroy-prop-access-2/main.svelte | 15 +++++++++++++++ 7 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/main.svelte diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 5108bdd2b3ad..6312360a0dfb 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -26,5 +26,5 @@ export const STATE_SYMBOL_METADATA = Symbol('$state metadata'); export const LEGACY_PROPS = Symbol('legacy props'); export const LOADING_ATTR_SYMBOL = Symbol(''); -export const CTX_CONTAINS_TEARDOWN = 1; -export const CTX_DESTROYED = 2; +export const CTX_CONTAINS_TEARDOWN = 1 << 1; +export const CTX_DESTROYED = 1 << 2; diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index 180f7f2490b7..2457a9fa9d2e 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -136,12 +136,12 @@ export function push(props, runes = false, fn) { } teardown(() => { - if (ctx.f !== CTX_CONTAINS_TEARDOWN) { + if ((ctx.f & (CTX_CONTAINS_TEARDOWN | CTX_DESTROYED)) === 0) { return; } // Mark the context as destroyed, so any derived props can use // the latest known value before teardown - ctx.f = CTX_DESTROYED; + ctx.f ^= CTX_DESTROYED; var teardown_props = ctx.tp; // Apply the latest known props before teardown over existing props @@ -195,7 +195,7 @@ export function pop(component) { context_stack_item.m = true; effect(() => { - if (context_stack_item.f === CTX_CONTAINS_TEARDOWN) { + if ((context_stack_item.f & CTX_CONTAINS_TEARDOWN) !== 0) { context_stack_item.tp = { ...context_stack_item.s }; } }); diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index 2b21627bdd7b..a105d83d4820 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -370,7 +370,7 @@ export function prop(props, key, flags, fallback) { var current_value = derived(() => { var ctx = component_context; - if (ctx !== null && ctx.f === CTX_DESTROYED) { + if (ctx !== null && (ctx.f & CTX_DESTROYED) !== 0) { return get(inner_current_value); } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index d7364247bb31..30ee7d2dc14a 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -570,8 +570,8 @@ export function update_effect(effect) { if (typeof teardown === 'function') { var ctx = effect.ctx; - if (ctx !== null && ctx.f === 0) { - ctx.f = CTX_CONTAINS_TEARDOWN; + if (ctx !== null && (ctx.f & CTX_CONTAINS_TEARDOWN) === 0) { + ctx.f ^= CTX_CONTAINS_TEARDOWN; } effect.teardown = teardown; } else { diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/Component.svelte b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/Component.svelte new file mode 100644 index 000000000000..ebe50fab30dc --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/Component.svelte @@ -0,0 +1,11 @@ + + +{my_prop.foo} diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/_config.js b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/_config.js new file mode 100644 index 000000000000..81005cf73760 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/_config.js @@ -0,0 +1,14 @@ +import { test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + async test({ assert, target, logs }) { + const [btn1] = target.querySelectorAll('button'); + + flushSync(() => { + btn1.click(); + }); + + assert.deepEqual(logs, ['bar']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/main.svelte new file mode 100644 index 000000000000..c1cd483b8620 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/main.svelte @@ -0,0 +1,15 @@ + + + + +{#if value !== undefined} + +{/if} From 9f21043ca6bbd77115cc579b3b75cb91ed864d3e Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 27 Feb 2025 15:46:40 +0000 Subject: [PATCH 09/17] types --- packages/svelte/src/internal/client/types.d.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index ac5f6101b4d8..fad0df7a266e 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -24,10 +24,7 @@ export type ComponentContext = { f: number; /** mounted */ m: boolean; - /** - * props — needed for legacy mode lifecycle functions, and for `createEventDispatcher` - * @deprecated remove in 6.0 - */ + /** props — needed for legacy mode lifecycle functions, for `createEventDispatcher` and teardown */ s: Record; /** * exports (and props, if `accessors: true`) — needed for `createEventDispatcher` @@ -55,12 +52,12 @@ export type ComponentContext = { /** This tracks whether `$:` statements have run in the current cycle, to ensure they only run once */ r2: Source; }; + /** teardown props */ + tp: Record; /** * dev mode only: the component function */ function?: any; - /** teardown props */ - tp: Record; }; export type ComponentContextLegacy = ComponentContext & { From ed32963d004d05dcb427ca9c48c8b80da711a6de Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 3 Mar 2025 16:20:31 +0000 Subject: [PATCH 10/17] add test + fix spread props --- .../2-analyze/visitors/CallExpression.js | 3 ++ .../svelte/src/internal/client/constants.js | 1 + .../svelte/src/internal/client/context.js | 6 +++- .../src/internal/client/reactivity/props.js | 29 ++++++++++++++----- .../ondestroy-prop-access-3/Component.svelte | 5 ++++ .../ondestroy-prop-access-3/_config.js | 11 +++++++ .../ondestroy-prop-access-3/main.svelte | 19 ++++++++++++ .../samples/ondestroy-prop-access/_config.js | 2 +- 8 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/main.svelte diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 4d09d9293fb2..6c2171785244 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -42,6 +42,9 @@ export function CallExpression(node, context) { e.bindable_invalid_location(node); } + // We need context in case the bound prop is stale + context.state.analysis.needs_context = true; + break; case '$host': diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 6312360a0dfb..90e1578eef7b 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -24,6 +24,7 @@ export const EFFECT_HAS_DERIVED = 1 << 20; export const STATE_SYMBOL = Symbol('$state'); export const STATE_SYMBOL_METADATA = Symbol('$state metadata'); export const LEGACY_PROPS = Symbol('legacy props'); +export const TEARDOWN_PROPS = Symbol('teardown props'); export const LOADING_ATTR_SYMBOL = Symbol(''); export const CTX_CONTAINS_TEARDOWN = 1 << 1; diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index 2457a9fa9d2e..b2c4691fa975 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -13,7 +13,7 @@ import { } from './runtime.js'; import { effect, teardown } from './reactivity/effects.js'; import { legacy_mode_flag } from '../flags/index.js'; -import { CTX_CONTAINS_TEARDOWN, CTX_DESTROYED } from './constants.js'; +import { CTX_CONTAINS_TEARDOWN, CTX_DESTROYED, TEARDOWN_PROPS } from './constants.js'; import { define_property } from '../shared/utils.js'; /** @type {ComponentContext | null} */ @@ -144,6 +144,10 @@ export function push(props, runes = false, fn) { ctx.f ^= CTX_DESTROYED; var teardown_props = ctx.tp; + if (TEARDOWN_PROPS in props) { + props[TEARDOWN_PROPS] = teardown_props; + return; + } // Apply the latest known props before teardown over existing props for (var key in teardown_props) { define_property(props, key, { diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index a105d83d4820..532ea82eb1cc 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -13,7 +13,13 @@ import { derived, derived_safe_equal } from './deriveds.js'; import { get, captured_signals, untrack } from '../runtime.js'; import { safe_equals } from './equality.js'; import * as e from '../errors.js'; -import { CTX_DESTROYED, LEGACY_DERIVED_PROP, LEGACY_PROPS, STATE_SYMBOL } from '../constants.js'; +import { + CTX_DESTROYED, + LEGACY_DERIVED_PROP, + LEGACY_PROPS, + STATE_SYMBOL, + TEARDOWN_PROPS +} from '../constants.js'; import { proxy } from '../proxy.js'; import { capture_store_binding } from './store.js'; import { legacy_mode_flag } from '../../flags/index.js'; @@ -173,6 +179,12 @@ const spread_props_handler = { } }, set(target, key, value) { + // If the spread props have been torn down, then replace the existing props with + // the stale props from the teardown + if (key === TEARDOWN_PROPS) { + target.props = [value]; + return true; + } let i = target.props.length; while (i--) { let p = target.props[i]; @@ -215,6 +227,9 @@ const spread_props_handler = { } }, has(target, key) { + if (key === TEARDOWN_PROPS) { + return true; + } // To prevent a false positive `is_entry_props` in the `prop` function if (key === STATE_SYMBOL || key === LEGACY_PROPS) return false; @@ -368,12 +383,6 @@ export function prop(props, key, flags, fallback) { // source is written to from various places to persist this value. var inner_current_value = mutable_source(prop_value); var current_value = derived(() => { - var ctx = component_context; - - if (ctx !== null && (ctx.f & CTX_DESTROYED) !== 0) { - return get(inner_current_value); - } - var parent_value = getter(); var child_value = get(inner_current_value); @@ -418,6 +427,12 @@ export function prop(props, key, flags, fallback) { return value; } + + // If the prop is read, we might need to return the stale value if component ctx has been destroyed + if (current_value.ctx !== null && (current_value.ctx.f & CTX_DESTROYED) !== 0) { + return current_value.v; + } + return get(current_value); }; } diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/Component.svelte b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/Component.svelte new file mode 100644 index 000000000000..e72a19e94656 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/Component.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/_config.js b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/_config.js new file mode 100644 index 000000000000..03a9822cdaf2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/_config.js @@ -0,0 +1,11 @@ +import { test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + async test({ assert, target, logs }) { + const [btn1] = target.querySelectorAll('button'); + + btn1.click(); + flushSync(); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/main.svelte new file mode 100644 index 000000000000..85fab11ab169 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-3/main.svelte @@ -0,0 +1,19 @@ + + +{#if state} + {@const attributes = { title: state.title }} + +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js index 794d1f7017d5..7d1e67e21232 100644 --- a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/_config.js @@ -61,7 +61,7 @@ export default test({ true, 2, true, - 1, + 2, true ]); } From fcb11125ed9d0ab9e690986ee2a793ffe40cce1c Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 3 Mar 2025 16:46:04 +0000 Subject: [PATCH 11/17] tweak --- .../src/internal/client/reactivity/props.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index 532ea82eb1cc..e8dd70196698 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -7,7 +7,7 @@ import { PROPS_IS_RUNES, PROPS_IS_UPDATED } from '../../../constants.js'; -import { define_property, get_descriptor, is_function } from '../../shared/utils.js'; +import { get_descriptor, is_function } from '../../shared/utils.js'; import { mutable_source, set, source, update } from './sources.js'; import { derived, derived_safe_equal } from './deriveds.js'; import { get, captured_signals, untrack } from '../runtime.js'; @@ -23,7 +23,6 @@ import { import { proxy } from '../proxy.js'; import { capture_store_binding } from './store.js'; import { legacy_mode_flag } from '../../flags/index.js'; -import { component_context } from '../context.js'; /** * @param {((value?: number) => number)} fn @@ -197,18 +196,6 @@ const spread_props_handler = { } return false; }, - defineProperty(target, key, descriptor) { - let i = target.props.length; - while (i--) { - let p = target.props[i]; - if (is_function(p)) p = p(); - if (typeof p === 'object' && p !== null && key in p) { - define_property(p, key, descriptor); - return true; - } - } - return false; - }, getOwnPropertyDescriptor(target, key) { let i = target.props.length; while (i--) { From 3c6af50378b1ca27de25135ce86c8a8b64b0ad0b Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Mar 2025 10:34:18 +0000 Subject: [PATCH 12/17] tweak --- .../svelte/src/internal/client/context.js | 38 ++++++++++--------- .../src/internal/client/reactivity/props.js | 15 +++++++- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index b2c4691fa975..374b16e02b16 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -13,7 +13,7 @@ import { } from './runtime.js'; import { effect, teardown } from './reactivity/effects.js'; import { legacy_mode_flag } from '../flags/index.js'; -import { CTX_CONTAINS_TEARDOWN, CTX_DESTROYED, TEARDOWN_PROPS } from './constants.js'; +import { CTX_DESTROYED, TEARDOWN_PROPS } from './constants.js'; import { define_property } from '../shared/utils.js'; /** @type {ComponentContext | null} */ @@ -136,24 +136,27 @@ export function push(props, runes = false, fn) { } teardown(() => { - if ((ctx.f & (CTX_CONTAINS_TEARDOWN | CTX_DESTROYED)) === 0) { + if ((ctx.f & CTX_DESTROYED) !== 0) { return; } // Mark the context as destroyed, so any derived props can use // the latest known value before teardown ctx.f ^= CTX_DESTROYED; - var teardown_props = ctx.tp; - if (TEARDOWN_PROPS in props) { - props[TEARDOWN_PROPS] = teardown_props; - return; - } - // Apply the latest known props before teardown over existing props - for (var key in teardown_props) { - define_property(props, key, { - value: teardown_props[key], - configurable: true - }); + // Only apply the latest known props before teardown in legacy mode + if (!is_runes()) { + var teardown_props = ctx.tp; + if (TEARDOWN_PROPS in props) { + props[TEARDOWN_PROPS] = teardown_props; + return; + } + // Apply the latest known props before teardown over existing props + for (var key in teardown_props) { + define_property(props, key, { + value: teardown_props[key], + configurable: true + }); + } } }); @@ -198,11 +201,12 @@ export function pop(component) { } context_stack_item.m = true; - effect(() => { - if ((context_stack_item.f & CTX_CONTAINS_TEARDOWN) !== 0) { + // Only apply the latest known props before teardown in legacy mode + if (!is_runes()) { + effect(() => { context_stack_item.tp = { ...context_stack_item.s }; - } - }); + }); + } } // Micro-optimization: Don't set .a above to the empty object // so it can be garbage-collected when the return here is unused diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index e8dd70196698..80fc3bdc6855 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -1,4 +1,4 @@ -/** @import { Source } from './types.js' */ +/** @import { Derived, Source } from './types.js' */ import { DEV } from 'esm-env'; import { PROPS_IS_BINDABLE, @@ -250,6 +250,14 @@ export function spread_props(...props) { return new Proxy({ props }, spread_props_handler); } +/** + * @param {Derived} signal + * @returns {boolean} + */ +function in_destroyed_context(signal) { + return signal.ctx !== null && (signal.ctx.f & CTX_DESTROYED) !== 0; +} + /** * This function is responsible for synchronizing a possibly bound prop with the inner component state. * It is used whenever the compiler sees that the component writes to the prop, or when it has a default prop_value. @@ -409,6 +417,9 @@ export function prop(props, key, flags, fallback) { if (fallback_used && fallback_value !== undefined) { fallback_value = new_value; } + if (in_destroyed_context(current_value)) { + return value; + } untrack(() => get(current_value)); // force a synchronisation immediately } @@ -416,7 +427,7 @@ export function prop(props, key, flags, fallback) { } // If the prop is read, we might need to return the stale value if component ctx has been destroyed - if (current_value.ctx !== null && (current_value.ctx.f & CTX_DESTROYED) !== 0) { + if (in_destroyed_context(current_value)) { return current_value.v; } From d5bca1140f55daac84b6f56d6f4d5507a600fb10 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Mar 2025 10:44:12 +0000 Subject: [PATCH 13/17] move over 2 tests to legacy --- .../samples/ondestroy-prop-access-2/Component.svelte | 2 +- .../samples/ondestroy-prop-access-2/_config.js | 0 .../samples/ondestroy-prop-access-2/main.svelte | 2 +- .../samples/ondestroy-prop-access-3/Component.svelte | 2 +- .../samples/ondestroy-prop-access-3/_config.js | 0 .../samples/ondestroy-prop-access-3/main.svelte | 4 +--- 6 files changed, 4 insertions(+), 6 deletions(-) rename packages/svelte/tests/{runtime-runes => runtime-legacy}/samples/ondestroy-prop-access-2/Component.svelte (81%) rename packages/svelte/tests/{runtime-runes => runtime-legacy}/samples/ondestroy-prop-access-2/_config.js (100%) rename packages/svelte/tests/{runtime-runes => runtime-legacy}/samples/ondestroy-prop-access-2/main.svelte (84%) rename packages/svelte/tests/{runtime-runes => runtime-legacy}/samples/ondestroy-prop-access-3/Component.svelte (56%) rename packages/svelte/tests/{runtime-runes => runtime-legacy}/samples/ondestroy-prop-access-3/_config.js (100%) rename packages/svelte/tests/{runtime-runes => runtime-legacy}/samples/ondestroy-prop-access-3/main.svelte (84%) diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/Component.svelte b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/Component.svelte similarity index 81% rename from packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/Component.svelte rename to packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/Component.svelte index ebe50fab30dc..73347c4d7ff1 100644 --- a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access-2/Component.svelte +++ b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access-2/Component.svelte @@ -1,7 +1,7 @@ @@ -32,13 +30,13 @@ {/if} - + - + - + - + diff --git a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/Component.svelte b/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/Component.svelte deleted file mode 100644 index afe0fe10873d..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/ondestroy-prop-access/Component.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - -

{count}

- - From 5636ee8314efdbf0adbba4acd15e1a0a76244a79 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Mar 2025 11:59:50 +0000 Subject: [PATCH 15/17] fix --- packages/svelte/src/internal/client/constants.js | 3 +-- .../src/internal/client/reactivity/props.js | 9 ++++++++- packages/svelte/src/internal/client/runtime.js | 15 ++------------- .../ondestroy-prop-access/Component.svelte | 11 +++++++---- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 90e1578eef7b..cdd70f6c762e 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -27,5 +27,4 @@ export const LEGACY_PROPS = Symbol('legacy props'); export const TEARDOWN_PROPS = Symbol('teardown props'); export const LOADING_ATTR_SYMBOL = Symbol(''); -export const CTX_CONTAINS_TEARDOWN = 1 << 1; -export const CTX_DESTROYED = 1 << 2; +export const CTX_DESTROYED = 1 << 1; diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index 80fc3bdc6855..9cff81cc0d34 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -5,7 +5,8 @@ import { PROPS_IS_IMMUTABLE, PROPS_IS_LAZY_INITIAL, PROPS_IS_RUNES, - PROPS_IS_UPDATED + PROPS_IS_UPDATED, + UNINITIALIZED } from '../../../constants.js'; import { get_descriptor, is_function } from '../../shared/utils.js'; import { mutable_source, set, source, update } from './sources.js'; @@ -23,6 +24,7 @@ import { import { proxy } from '../proxy.js'; import { capture_store_binding } from './store.js'; import { legacy_mode_flag } from '../../flags/index.js'; +import { is_runes } from '../context.js'; /** * @param {((value?: number) => number)} fn @@ -391,6 +393,11 @@ export function prop(props, key, flags, fallback) { return (inner_current_value.v = parent_value); }); + // Read the prop eagerly to ensure it has a value + if (!is_runes()) { + get(current_value); + } + if (!immutable) current_value.equals = safe_equals; return function (/** @type {any} */ value, /** @type {boolean} */ mutation) { diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 19961b10c7e4..bbe4dc3d9b8f 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -22,8 +22,7 @@ import { ROOT_EFFECT, LEGACY_DERIVED_PROP, DISCONNECTED, - BOUNDARY_EFFECT, - CTX_CONTAINS_TEARDOWN + BOUNDARY_EFFECT } from './constants.js'; import { flush_tasks } from './dom/task.js'; import { internal_set } from './reactivity/sources.js'; @@ -567,17 +566,7 @@ export function update_effect(effect) { execute_effect_teardown(effect); var teardown = update_reaction(effect); - - if (typeof teardown === 'function') { - var ctx = effect.ctx; - if (ctx !== null && (ctx.f & CTX_CONTAINS_TEARDOWN) === 0) { - ctx.f ^= CTX_CONTAINS_TEARDOWN; - } - effect.teardown = teardown; - } else { - effect.teardown = null; - } - + effect.teardown = typeof teardown === 'function' ? teardown : null; effect.wv = write_version; var deps = effect.deps; diff --git a/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/Component.svelte b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/Component.svelte index 73347c4d7ff1..00e12070f42f 100644 --- a/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/Component.svelte +++ b/packages/svelte/tests/runtime-legacy/samples/ondestroy-prop-access/Component.svelte @@ -1,11 +1,14 @@ -{my_prop.foo} +

{count}

+ + From 6cec8ea60cc474c75c511668fe8e955d18d0f79d Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Mar 2025 12:03:00 +0000 Subject: [PATCH 16/17] fix --- packages/svelte/src/internal/client/context.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index 374b16e02b16..2ef5314804ba 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -144,7 +144,7 @@ export function push(props, runes = false, fn) { ctx.f ^= CTX_DESTROYED; // Only apply the latest known props before teardown in legacy mode - if (!is_runes()) { + if (!is_runes(ctx)) { var teardown_props = ctx.tp; if (TEARDOWN_PROPS in props) { props[TEARDOWN_PROPS] = teardown_props; @@ -202,7 +202,7 @@ export function pop(component) { context_stack_item.m = true; // Only apply the latest known props before teardown in legacy mode - if (!is_runes()) { + if (!is_runes(context_stack_item)) { effect(() => { context_stack_item.tp = { ...context_stack_item.s }; }); @@ -214,8 +214,8 @@ export function pop(component) { } /** @returns {boolean} */ -export function is_runes() { - return !legacy_mode_flag || (component_context !== null && component_context.l === null); +export function is_runes(ctx = component_context) { + return !legacy_mode_flag || (ctx !== null && ctx.l === null); } /** From 2c12812ffaf21884a21978f9fe637882346f0ed4 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Mar 2025 13:08:52 +0000 Subject: [PATCH 17/17] tweak --- packages/svelte/src/internal/client/reactivity/props.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index 9cff81cc0d34..4c4c4ce39c1a 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -5,8 +5,7 @@ import { PROPS_IS_IMMUTABLE, PROPS_IS_LAZY_INITIAL, PROPS_IS_RUNES, - PROPS_IS_UPDATED, - UNINITIALIZED + PROPS_IS_UPDATED } from '../../../constants.js'; import { get_descriptor, is_function } from '../../shared/utils.js'; import { mutable_source, set, source, update } from './sources.js'; @@ -393,8 +392,8 @@ export function prop(props, key, flags, fallback) { return (inner_current_value.v = parent_value); }); - // Read the prop eagerly to ensure it has a value - if (!is_runes()) { + // Read the bindable prop eagerly to ensure it has a value + if (!is_runes() && bindable) { get(current_value); }