diff --git a/.changeset/smart-parents-swim.md b/.changeset/smart-parents-swim.md new file mode 100644 index 000000000000..c85000aaac97 --- /dev/null +++ b/.changeset/smart-parents-swim.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: reuse existing proxy when object has multiple references diff --git a/packages/svelte/src/internal/client/proxy/proxy.js b/packages/svelte/src/internal/client/proxy/proxy.js index ac94c0b492c3..9a68f23ec4f3 100644 --- a/packages/svelte/src/internal/client/proxy/proxy.js +++ b/packages/svelte/src/internal/client/proxy/proxy.js @@ -17,7 +17,7 @@ import { object_keys } from '../utils.js'; -/** @typedef {{ s: Map>; v: import('../types.js').SourceSignal; a: boolean, i: boolean }} Metadata */ +/** @typedef {{ s: Map>; v: import('../types.js').SourceSignal; a: boolean, i: boolean, p: StateObject }} Metadata */ /** @typedef {Record & { [STATE_SYMBOL]: Metadata }} StateObject */ export const STATE_SYMBOL = Symbol('$state'); @@ -35,15 +35,23 @@ const is_frozen = Object.isFrozen; * @returns {T} */ export function proxy(value, immutable = true) { - if (typeof value === 'object' && value != null && !is_frozen(value) && !(STATE_SYMBOL in value)) { + if (typeof value === 'object' && value != null && !is_frozen(value)) { + if (STATE_SYMBOL in value) { + return /** @type {T} */ (value[STATE_SYMBOL].p); + } + const prototype = get_prototype_of(value); // TODO handle Map and Set as well if (prototype === object_prototype || prototype === array_prototype) { - define_property(value, STATE_SYMBOL, { value: init(value, immutable), writable: false }); + const proxy = new Proxy(value, handler); + define_property(value, STATE_SYMBOL, { + value: init(value, proxy, immutable), + writable: false + }); // @ts-expect-error not sure how to fix this - return new Proxy(value, handler); + return proxy; } } @@ -102,15 +110,17 @@ export function unstate(value) { /** * @param {StateObject} value + * @param {StateObject} proxy * @param {boolean} immutable * @returns {Metadata} */ -function init(value, immutable) { +function init(value, proxy, immutable) { return { s: new Map(), v: source(0), a: is_array(value), - i: immutable + i: immutable, + p: proxy }; } diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-shared/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-shared/_config.js new file mode 100644 index 000000000000..d8baaf491540 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-shared/_config.js @@ -0,0 +1,30 @@ +import { test } from '../../test'; + +export default test({ + html: ` + + + `, + + async test({ assert, target }) { + const [btn1, btn2] = target.querySelectorAll('button'); + + await btn1?.click(); + assert.htmlEqual( + target.innerHTML, + ` + + + ` + ); + + await btn2?.click(); + assert.htmlEqual( + target.innerHTML, + ` + + + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-shared/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-shared/main.svelte new file mode 100644 index 000000000000..0e0b6223727f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-shared/main.svelte @@ -0,0 +1,14 @@ + + + + +