diff --git a/packages/svelte/src/reactivity/map.js b/packages/svelte/src/reactivity/map.js index 48d06a05a7..5ff327a7f4 100644 --- a/packages/svelte/src/reactivity/map.js +++ b/packages/svelte/src/reactivity/map.js @@ -3,6 +3,9 @@ import { DEV } from 'esm-env'; import { set, source, state, increment } from '../internal/client/reactivity/sources.js'; import { label, tag } from '../internal/client/dev/tracing.js'; import { get, update_version } from '../internal/client/runtime.js'; +import { async_mode_flag } from '../internal/flags/index.js'; + +const MISSING = Symbol('missing'); /** * A reactive version of the built-in [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) object. @@ -51,10 +54,10 @@ import { get, update_version } from '../internal/client/runtime.js'; * @extends {Map} */ export class SvelteMap extends Map { - /** @type {Map>} */ + /** @type {Map>} */ #sources = new Map(); - #version = state(0); - #size = state(0); + /** @type {Source>} */ + #items; #update_version = update_version || -1; /** @@ -63,19 +66,16 @@ export class SvelteMap extends Map { constructor(value) { super(); - if (DEV) { - // If the value is invalid then the native exception will fire here - value = new Map(value); + this.#items = state(new Map(value)); - tag(this.#version, 'SvelteMap version'); - tag(this.#size, 'SvelteMap.size'); + var sources = this.#sources; + + for (const [key, value] of this.#items.v) { + sources.set(key, this.#source(value)); } - if (value) { - for (var [key, v] of value) { - super.set(key, v); - } - this.#size.v = super.size; + if (DEV) { + tag(this.#items, 'SvelteMap items'); } } @@ -98,24 +98,27 @@ export class SvelteMap extends Map { var s = sources.get(key); if (s === undefined) { - if (super.has(key)) { - s = this.#source(0); - - if (DEV) { - tag(s, `SvelteMap get(${label(key)})`); - } - - 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); + if (!async_mode_flag) { + // 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.#items); return false; } + + var items = get(this.#items); + var value = /** @type {V} */ (items.has(key) ? items.get(key) : (MISSING)); + + s = this.#source(value); + + if (DEV) { + tag(s, `SvelteSet has(${label(value)})`); + } + + sources.set(key, s); } - get(s); - return true; + var value = get(s); + return value !== MISSING; } /** @@ -123,8 +126,9 @@ export class SvelteMap extends Map { * @param {any} [this_arg] */ forEach(callbackfn, this_arg) { - this.#read_all(); - super.forEach(callbackfn, this_arg); + for (const [key, value] of this.entries()) { + callbackfn(value, key, this); + } } /** @param {K} key */ @@ -133,24 +137,15 @@ export class SvelteMap extends Map { var s = sources.get(key); if (s === undefined) { - if (super.has(key)) { - s = this.#source(0); - - if (DEV) { - tag(s, `SvelteMap get(${label(key)})`); - } - - 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; - } + // We should always track the version in case + // the Set ever gets this value in the future. + var items = get(this.#items); + return items.get(key); } - get(s); - return super.get(key); + var value = get(s); + + return value === MISSING ? undefined : value; } /** @@ -160,107 +155,86 @@ export class SvelteMap extends Map { set(key, value) { var sources = this.#sources; var s = sources.get(key); - var prev_res = super.get(key); - var res = super.set(key, value); - var version = this.#version; - if (s === undefined) { - s = this.#source(0); + if (s !== undefined) { + set(s, value); + } else { + sources.set(key, this.#source(value)); + } - if (DEV) { - tag(s, `SvelteMap get(${label(key)})`); - } + var items = get(this.#items); - sources.set(key, s); - set(this.#size, super.size); - increment(version); - } else if (prev_res !== value) { - increment(s); - - // if not every reaction of s is a reaction of version we need to also include version - var v_reactions = version.reactions === null ? null : new Set(version.reactions); - var needs_version_increase = - v_reactions === null || - !s.reactions?.every((r) => - /** @type {NonNullable} */ (v_reactions).has(r) - ); - if (needs_version_increase) { - increment(version); - } + if (!items.has(key)) { + const clone = new Map(items); + clone.set(key, value); + + set(this.#items, clone); } - return res; + return this; } /** @param {K} key */ delete(key) { + var items = get(this.#items); + var has = items.has(key); var sources = this.#sources; var s = sources.get(key); - var res = super.delete(key); if (s !== undefined) { sources.delete(key); - set(s, -1); + set(s, /** @type {V} */ (MISSING)); } - if (res) { - set(this.#size, super.size); - increment(this.#version); + if (has) { + const clone = new Map(items); + clone.delete(key); + + set(this.#items, clone); } - return res; + return has; } clear() { - if (super.size === 0) { + if (get(this.#items).size === 0) { return; } + // Clear first, so we get nice console.log outputs with $inspect - super.clear(); + set(this.#items, new Map()); + var sources = this.#sources; - set(this.#size, 0); + for (var s of sources.values()) { - set(s, -1); + set(s, /** @type {V} */ (MISSING)); } - increment(this.#version); + sources.clear(); } - #read_all() { - get(this.#version); + keys() { + return get(this.#items).keys(); + } - var sources = this.#sources; - if (this.#size.v !== sources.size) { - for (var key of super.keys()) { - if (!sources.has(key)) { - var s = this.#source(0); - if (DEV) { - tag(s, `SvelteMap get(${label(key)})`); - } - - sources.set(key, s); - } - } - } + *values() { + for (const key of get(this.#items).keys()) { + const s = this.#sources.get(key); + if (s === undefined) continue; - for ([, s] of this.#sources) { - get(s); + const value = get(s); + if (value !== MISSING) yield value; } } - keys() { - get(this.#version); - return super.keys(); - } - - values() { - this.#read_all(); - return super.values(); - } + *entries() { + for (const key of get(this.#items).keys()) { + const s = this.#sources.get(key); + if (s === undefined) continue; - entries() { - this.#read_all(); - return super.entries(); + const value = get(s); + if (value !== MISSING) yield [key, value]; + } } [Symbol.iterator]() { @@ -268,7 +242,6 @@ export class SvelteMap extends Map { } get size() { - get(this.#size); - return super.size; + return get(this.#items).size; } }