From 17b126b7272d94517644e1d659714eef184e309e Mon Sep 17 00:00:00 2001 From: 7nik Date: Mon, 3 Nov 2025 12:41:40 +0200 Subject: [PATCH] fix: more fine-grained SvelteSet and SvelteMap --- .changeset/khaki-ravens-carry.md | 5 ++ packages/svelte/src/reactivity/map.js | 64 ++++++++++------------ packages/svelte/src/reactivity/map.test.ts | 7 +-- packages/svelte/src/reactivity/set.js | 21 +++---- packages/svelte/src/reactivity/set.test.ts | 8 +-- 5 files changed, 46 insertions(+), 59 deletions(-) create mode 100644 .changeset/khaki-ravens-carry.md diff --git a/.changeset/khaki-ravens-carry.md b/.changeset/khaki-ravens-carry.md new file mode 100644 index 000000000000..cc381abf2a48 --- /dev/null +++ b/.changeset/khaki-ravens-carry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: more fine-grained SvelteSet and SvelteMap diff --git a/packages/svelte/src/reactivity/map.js b/packages/svelte/src/reactivity/map.js index 014b5e7c7ca1..f386526614f1 100644 --- a/packages/svelte/src/reactivity/map.js +++ b/packages/svelte/src/reactivity/map.js @@ -56,6 +56,7 @@ export class SvelteMap extends Map { #version = state(0); #size = state(0); #update_version = update_version || -1; + #fully_trackable = true; /** * @param {Iterable | null | undefined} [value] @@ -75,7 +76,11 @@ export class SvelteMap extends Map { for (var [key, v] of value) { super.set(key, v); } - this.#size.v = super.size; + var size = super.size; + this.#size.v = size; + if (size > 0) { + this.#fully_trackable = false; + } } } @@ -96,27 +101,19 @@ export class SvelteMap extends Map { has(key) { var sources = this.#sources; var s = sources.get(key); + var has = super.has(key); if (s === undefined) { - var ret = super.get(key); - if (ret !== undefined) { - s = this.#source(0); - - if (DEV) { - tag(s, `SvelteMap get(${label(key)})`); - } + s = this.#source(has ? 0 : -1); + sources.set(key, s); - sources.set(key, s); - } else { - // We should always track the version in case - // the Set ever gets this value in the future. - get(this.#version); - return false; + if (DEV) { + tag(s, `SvelteMap get(${label(key)})`); } } - get(s); - return true; + + return has; } /** @@ -134,24 +131,15 @@ export class SvelteMap extends Map { var s = sources.get(key); if (s === undefined) { - var ret = super.get(key); - if (ret !== undefined) { - s = this.#source(0); - - if (DEV) { - tag(s, `SvelteMap get(${label(key)})`); - } + s = this.#source(super.has(key) ? 0 : -1); + sources.set(key, s); - sources.set(key, s); - } else { - // We should always track the version in case - // the Set ever gets this value in the future. - get(this.#version); - return undefined; + if (DEV) { + tag(s, `SvelteMap get(${label(key)})`); } } - get(s); + return super.get(key); } @@ -176,6 +164,11 @@ export class SvelteMap extends Map { sources.set(key, s); set(this.#size, super.size); increment(version); + // if it is a signal for non-existing value + } else if (s.v === -1) { + set(this.#size, super.size); + increment(s); + increment(version); } else if (prev_res !== value) { increment(s); @@ -200,8 +193,7 @@ export class SvelteMap extends Map { var s = sources.get(key); var res = super.delete(key); - if (s !== undefined) { - sources.delete(key); + if (s !== undefined && s.v !== -1) { set(this.#size, super.size); set(s, -1); increment(this.#version); @@ -222,14 +214,13 @@ export class SvelteMap extends Map { set(s, -1); } increment(this.#version); - sources.clear(); } #read_all() { get(this.#version); var sources = this.#sources; - if (this.#size.v !== sources.size) { + if (!this.#fully_trackable) { for (var key of super.keys()) { if (!sources.has(key)) { var s = this.#source(0); @@ -240,10 +231,11 @@ export class SvelteMap extends Map { sources.set(key, s); } } + this.#fully_trackable = true; } - for ([, s] of this.#sources) { - get(s); + for ([, s] of sources) { + if (s.v !== -1) get(s); } } diff --git a/packages/svelte/src/reactivity/map.test.ts b/packages/svelte/src/reactivity/map.test.ts index 2f9f064b425f..9f04db040b54 100644 --- a/packages/svelte/src/reactivity/map.test.ts +++ b/packages/svelte/src/reactivity/map.test.ts @@ -52,7 +52,6 @@ test('map.values()', () => { false, [1, 2, 4, 5], 0, - false, [], 1, true, @@ -214,11 +213,11 @@ test('not invoking reactivity when value is not in the map after changes', () => const cleanup = effect_root(() => { render_effect(() => { - log.push(map.get(1)); + log.push('1', map.get(1)); }); render_effect(() => { - log.push(map.get(2)); + log.push('2', map.get(2)); }); flushSync(() => { @@ -230,7 +229,7 @@ test('not invoking reactivity when value is not in the map after changes', () => }); }); - assert.deepEqual(log, [1, undefined, undefined, undefined, 1, undefined]); + assert.deepEqual(log, ['1', 1, '2', undefined, '1', undefined, '1', 1]); cleanup(); }); diff --git a/packages/svelte/src/reactivity/set.js b/packages/svelte/src/reactivity/set.js index d7c2deeaae86..a4d610f00a55 100644 --- a/packages/svelte/src/reactivity/set.js +++ b/packages/svelte/src/reactivity/set.js @@ -122,15 +122,7 @@ export class SvelteSet extends Set { var s = sources.get(value); if (s === undefined) { - if (!has) { - // If the value doesn't exist, track the version in case it's added later - // but don't create sources willy-nilly to track all possible values - get(this.#version); - return false; - } - - s = this.#source(true); - + s = this.#source(has); if (DEV) { tag(s, `SvelteSet has(${label(value)})`); } @@ -144,6 +136,12 @@ export class SvelteSet extends Set { /** @param {T} value */ add(value) { + var s = this.#sources.get(value); + + if (s?.v === false) { + set(s, true); + } + if (!super.has(value)) { super.add(value); set(this.#size, super.size); @@ -160,7 +158,6 @@ export class SvelteSet extends Set { var s = sources.get(value); if (s !== undefined) { - sources.delete(value); set(s, false); } @@ -178,13 +175,11 @@ export class SvelteSet extends Set { } // Clear first, so we get nice console.log outputs with $inspect super.clear(); - var sources = this.#sources; - for (var s of sources.values()) { + for (var s of this.#sources.values()) { set(s, false); } - sources.clear(); set(this.#size, 0); increment(this.#version); } diff --git a/packages/svelte/src/reactivity/set.test.ts b/packages/svelte/src/reactivity/set.test.ts index a56e43ff8d53..654bd317c931 100644 --- a/packages/svelte/src/reactivity/set.test.ts +++ b/packages/svelte/src/reactivity/set.test.ts @@ -30,7 +30,7 @@ test('set.values()', () => { set.clear(); }); - assert.deepEqual(log, [5, true, [1, 2, 3, 4, 5], 4, false, [1, 2, 4, 5], 0, false, []]); + assert.deepEqual(log, [5, true, [1, 2, 3, 4, 5], 4, false, [1, 2, 4, 5], 0, []]); cleanup(); }); @@ -143,12 +143,8 @@ test('not invoking reactivity when value is not in the set after changes', () => false, 'has 2', false, - 'has 3', - false, 'has 2', - true, - 'has 3', - false + true ]); cleanup();