From 31bc6a4b89d63efbf6b75b7c614f7e1f653a6094 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 13 Apr 2025 13:31:02 -0400 Subject: [PATCH] WIP --- packages/svelte/src/internal/client/proxy.js | 81 +++++++++---------- .../src/internal/client/reactivity/sources.js | 17 ++-- 2 files changed, 48 insertions(+), 50 deletions(-) diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 4ad24712ea..fef54973ae 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -8,7 +8,8 @@ import { get_descriptor, get_prototype_of, is_array, - object_prototype + object_prototype, + run_all } from '../shared/utils.js'; import { PROXY_ONCHANGE_SYMBOL, STATE_SYMBOL } from './constants.js'; import { get_stack } from './dev/tracing.js'; @@ -33,31 +34,25 @@ function identity(fn) { * @returns {T} */ export function proxy(value, onchange) { + return create_proxy(value, onchange ? [onchange] : []); +} + +/** + * @template T + * @param {T} value + * @param {Array<() => void>} onchanges + * @returns {T} + */ +export function create_proxy(value, onchanges) { // if non-proxyable, or is already a proxy, return `value` if (typeof value !== 'object' || value === null) { return value; } if (STATE_SYMBOL in value) { - if (onchange) { - // @ts-ignore - value[PROXY_ONCHANGE_SYMBOL](onchange); - } - return value; } - if (onchange) { - // if there's an onchange we actually store that but override the value - // to store every other onchange that new proxies might add - var onchanges = new Set([onchange]); - onchange = () => { - for (let onchange of onchanges) { - onchange(); - } - }; - } - const prototype = get_prototype_of(value); if (prototype !== object_prototype && prototype !== array_prototype) { @@ -116,7 +111,7 @@ export function proxy(value, onchange) { } else { set( s, - with_parent(() => proxy(descriptor.value, onchange)) + with_parent(() => create_proxy(descriptor.value, onchanges)) ); } @@ -145,10 +140,10 @@ export function proxy(value, onchange) { } } - // when we delete a property if the source is a proxy we remove the current onchange from - // the proxy `onchanges` so that it doesn't trigger it anymore - if (onchange && typeof s.v === 'object' && s.v !== null && STATE_SYMBOL in s.v) { - s.v[PROXY_ONCHANGE_SYMBOL](onchange, true); + // when we delete a property if the source is a proxy we remove the parent `onchanges` from + // the child `onchanges` so that it doesn't trigger it anymore + if (typeof s.v === 'object' && s.v !== null && STATE_SYMBOL in s.v) { + s.v[PROXY_ONCHANGE_SYMBOL](onchanges, true); } set(s, UNINITIALIZED); @@ -164,15 +159,16 @@ export function proxy(value, onchange) { } if (prop === PROXY_ONCHANGE_SYMBOL) { - return (/** @type {(() => unknown)} */ value, /** @type {boolean} */ remove) => { - // we either add or remove the passed in value - // to the onchanges array or we set every source onchange - // to the passed in value (if it's undefined it will make the chain stop) - // if (onchange != null && value) { + return (/** @type {(Array<() => void>)} */ callbacks, /** @type {boolean} */ remove) => { if (remove) { - onchanges?.delete(value); + let i = callbacks.length; + while (i--) { + const index = onchanges.lastIndexOf(callbacks[i]); + if (index === -1) throw new Error('TODO this should not happen'); + onchanges.splice(index, 1); + } } else { - onchanges?.add(value); + onchanges.push(...callbacks); } }; } @@ -183,7 +179,7 @@ export function proxy(value, onchange) { // create a source, but only if it's an own property and not a prototype property if (s === undefined && (!exists || get_descriptor(target, prop)?.writable)) { s = with_parent(() => - source(proxy(exists ? target[prop] : UNINITIALIZED, onchange), stack) + source(create_proxy(exists ? target[prop] : UNINITIALIZED, onchanges), stack) ); sources.set(prop, s); } @@ -195,11 +191,7 @@ export function proxy(value, onchange) { v = Reflect.get(target, prop, receiver); - if ( - is_proxied_array && - onchange != null && - array_methods.includes(/** @type {string} */ (prop)) - ) { + if (is_proxied_array && array_methods.includes(/** @type {string} */ (prop))) { return batch_onchange(v); } @@ -242,7 +234,9 @@ export function proxy(value, onchange) { (active_effect !== null && (!has || get_descriptor(target, prop)?.writable)) ) { if (s === undefined) { - s = with_parent(() => source(has ? proxy(target[prop], onchange) : UNINITIALIZED, stack)); + s = with_parent(() => + source(has ? create_proxy(target[prop], onchanges) : UNINITIALIZED, stack) + ); sources.set(prop, s); } @@ -269,12 +263,11 @@ export function proxy(value, onchange) { var other_s = sources.get(i + ''); if (other_s !== undefined) { if ( - onchange && typeof other_s.v === 'object' && other_s.v !== null && STATE_SYMBOL in other_s.v ) { - other_s.v[PROXY_ONCHANGE_SYMBOL](onchange, true); + other_s.v[PROXY_ONCHANGE_SYMBOL](onchanges, true); } set(other_s, UNINITIALIZED); } else if (i in target) { @@ -299,21 +292,23 @@ export function proxy(value, onchange) { } else { has = s.v !== UNINITIALIZED; - // when we set a property if the source is a proxy we remove the current onchange from - // the proxy `onchanges` so that it doesn't trigger it anymore - if (onchange && typeof s.v === 'object' && s.v !== null && STATE_SYMBOL in s.v) { - s.v[PROXY_ONCHANGE_SYMBOL](onchange, true); + // when we set a property if the source is a proxy we remove the parent `onchanges` from + // the child `onchanges` so that it doesn't trigger it anymore + if (typeof s.v === 'object' && s.v !== null && STATE_SYMBOL in s.v) { + s.v[PROXY_ONCHANGE_SYMBOL](onchanges, true); } } if (s !== undefined) { set( s, - with_parent(() => proxy(value, onchange)) + with_parent(() => create_proxy(value, onchanges)) ); } })(); + run_all(onchanges); + var descriptor = Reflect.getOwnPropertyDescriptor(target, prop); // Set the new value before updating any signals so that any listeners get the new value diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 6fc459c843..13eb445a0f 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -168,9 +168,17 @@ export function set(source, value, should_proxy = false) { e.state_unsafe_mutation(); } - let new_value = should_proxy ? proxy(value, source.o) : value; + var onchange = source.o; - return internal_set(source, new_value); + var new_value = should_proxy ? proxy(value, onchange) : value; + + internal_set(source, new_value); + + if (onchange && new_value !== value) { + onchange(); + } + + return new_value; } /** @@ -183,11 +191,6 @@ export function internal_set(source, value) { if (!source.equals(value)) { var old_value = source.v; - if (typeof old_value === 'object' && old_value != null && source.o) { - // @ts-ignore - old_value[PROXY_ONCHANGE_SYMBOL]?.(source.o, true); - } - if (is_destroying_effect) { old_values.set(source, value); } else {