From 43d13e92a026b33a4e2470c637b6cf12ce785dab Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 18 Apr 2024 18:12:05 +0800 Subject: [PATCH] feat: allow $inspect reactivity map, set, date (#11164) * feat: allow $inspect reactivity map, set, date * feat: inspect map without adding new data source * feat: add inspect * feat: define inspect on dev mode only * fix: lint error --- .changeset/tiny-poems-doubt.md | 5 ++ .../svelte/src/internal/client/constants.js | 1 + .../svelte/src/internal/client/runtime.js | 23 +++++- packages/svelte/src/reactivity/date.js | 9 +++ packages/svelte/src/reactivity/map.js | 19 +++++ packages/svelte/src/reactivity/set.js | 8 ++ packages/svelte/src/reactivity/url.js | 23 ++++++ .../samples/inspect-reactivity/_config.js | 81 +++++++++++++++++++ .../samples/inspect-reactivity/log.js | 2 + .../samples/inspect-reactivity/main.svelte | 27 +++++++ packages/svelte/types/index.d.ts | 1 + 11 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 .changeset/tiny-poems-doubt.md create mode 100644 packages/svelte/tests/runtime-runes/samples/inspect-reactivity/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/inspect-reactivity/log.js create mode 100644 packages/svelte/tests/runtime-runes/samples/inspect-reactivity/main.svelte diff --git a/.changeset/tiny-poems-doubt.md b/.changeset/tiny-poems-doubt.md new file mode 100644 index 0000000000..b908d95df2 --- /dev/null +++ b/.changeset/tiny-poems-doubt.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +feat: allow inspect reactivity map, set, date diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index b9953b2ec6..7a6e65c068 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -16,3 +16,4 @@ export const EFFECT_RAN = 1 << 13; export const EFFECT_TRANSPARENT = 1 << 14; export const STATE_SYMBOL = Symbol('$state'); +export const INSPECT_SYMBOL = Symbol('$inspect'); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index fe93eb47e4..aa59baf94c 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -27,7 +27,8 @@ import { BRANCH_EFFECT, STATE_SYMBOL, BLOCK_EFFECT, - ROOT_EFFECT + ROOT_EFFECT, + INSPECT_SYMBOL } from './constants.js'; import { flush_tasks } from './dom/task.js'; import { add_owner } from './dev/ownership.js'; @@ -1117,6 +1118,24 @@ export function pop(component) { return component || /** @type {T} */ ({}); } +/** + * + * This is called from the inspect. + * Deeply traverse every item in the array with `deep_read` to register for inspect callback + * If the item implements INSPECT_SYMBOL, will use that instead + * @param {Array} value + * @returns {void} + */ +function deep_read_inpect(value) { + for (const item of value) { + if (item && typeof item[INSPECT_SYMBOL] === 'function') { + item[INSPECT_SYMBOL](); + } else { + deep_read(item); + } + } +} + /** * Possibly traverse an object and read all its properties so that they're all reactive in case this is `$state`. * Does only check first level of an object for performance reasons (heuristic should be good for 99% of all cases). @@ -1247,7 +1266,7 @@ export function inspect(get_value, inspect = console.log) { inspect_fn = fn; const value = get_value(); - deep_read(value); + deep_read_inpect(value); inspect_fn = null; const signals = inspect_captured_signals.slice(); diff --git a/packages/svelte/src/reactivity/date.js b/packages/svelte/src/reactivity/date.js index cf43dfe4ab..302f58ab17 100644 --- a/packages/svelte/src/reactivity/date.js +++ b/packages/svelte/src/reactivity/date.js @@ -1,3 +1,5 @@ +import { DEV } from 'esm-env'; +import { INSPECT_SYMBOL } from '../internal/client/constants.js'; import { source, set } from '../internal/client/reactivity/sources.js'; import { get } from '../internal/client/runtime.js'; @@ -88,6 +90,13 @@ export class ReactiveDate extends Date { return v; }; } + + if (DEV) { + // @ts-ignore + proto[INSPECT_SYMBOL] = function () { + get(this.#raw_time); + }; + } } } diff --git a/packages/svelte/src/reactivity/map.js b/packages/svelte/src/reactivity/map.js index b2b3ab605b..873a7a1f7a 100644 --- a/packages/svelte/src/reactivity/map.js +++ b/packages/svelte/src/reactivity/map.js @@ -3,6 +3,9 @@ import { source, set } from '../internal/client/reactivity/sources.js'; import { get } from '../internal/client/runtime.js'; import { UNINITIALIZED } from '../constants.js'; import { map } from './utils.js'; +import { INSPECT_SYMBOL } from '../internal/client/constants.js'; + +var inited = false; /** * @template K @@ -20,6 +23,22 @@ export class ReactiveMap extends Map { constructor(value) { super(); + if (DEV) { + if (!inited) { + inited = true; + // @ts-ignore + ReactiveMap.prototype[INSPECT_SYMBOL] = function () { + // changes could either introduced by + // - modifying the value, or + // - add / remove entries to the map + for (const [, source] of this.#sources) { + get(source); + } + get(this.#size); + }; + } + } + // If the value is invalid then the native exception will fire here if (DEV) new Map(value); diff --git a/packages/svelte/src/reactivity/set.js b/packages/svelte/src/reactivity/set.js index 1855090cdf..d3eb1cccaf 100644 --- a/packages/svelte/src/reactivity/set.js +++ b/packages/svelte/src/reactivity/set.js @@ -2,6 +2,7 @@ import { DEV } from 'esm-env'; import { source, set } from '../internal/client/reactivity/sources.js'; import { get } from '../internal/client/runtime.js'; import { map } from './utils.js'; +import { INSPECT_SYMBOL } from '../internal/client/constants.js'; var read_methods = ['forEach', 'isDisjointFrom', 'isSubsetOf', 'isSupersetOf']; var set_like_methods = ['difference', 'intersection', 'symmetricDifference', 'union']; @@ -65,6 +66,13 @@ export class ReactiveSet extends Set { return new ReactiveSet(set); }; } + + if (DEV) { + // @ts-ignore + proto[INSPECT_SYMBOL] = function () { + get(this.#version); + }; + } } #increment_version() { diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js index 3f11a4af37..df0551fe06 100644 --- a/packages/svelte/src/reactivity/url.js +++ b/packages/svelte/src/reactivity/url.js @@ -1,7 +1,11 @@ +import { DEV } from 'esm-env'; +import { INSPECT_SYMBOL } from '../internal/client/constants.js'; import { source, set } from '../internal/client/reactivity/sources.js'; import { get } from '../internal/client/runtime.js'; const REPLACE = Symbol(); +var inited_url = false; +var inited_search_params = false; export class ReactiveURL extends URL { #protocol = source(super.protocol); @@ -21,6 +25,14 @@ export class ReactiveURL extends URL { url = new URL(url, base); super(url); this.#searchParams[REPLACE](url.searchParams); + + if (DEV && !inited_url) { + inited_url = true; + // @ts-ignore + ReactiveURL.prototype[INSPECT_SYMBOL] = function () { + this.href; + }; + } } get hash() { @@ -159,6 +171,17 @@ export class ReactiveURLSearchParams extends URLSearchParams { set(this.#version, this.#version.v + 1); } + constructor() { + super(); + if (DEV && !inited_search_params) { + inited_search_params = true; + // @ts-ignore + ReactiveURLSearchParams.prototype[INSPECT_SYMBOL] = function () { + get(this.#version); + }; + } + } + /** * @param {URLSearchParams} params */ diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-reactivity/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-reactivity/_config.js new file mode 100644 index 0000000000..89dfc1c729 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-reactivity/_config.js @@ -0,0 +1,81 @@ +import { test } from '../../test'; +import { flushSync } from 'svelte'; +import { log } from './log'; + +export default test({ + compileOptions: { + dev: true + }, + before_test() { + log.length = 0; + }, + async test({ assert, target }) { + const [in1, in2] = target.querySelectorAll('input'); + const [b1, b2, b3] = target.querySelectorAll('button'); + + assert.deepEqual(log, [ + { label: 'map', type: 'init', values: [] }, + { label: 'set', type: 'init', values: [] }, + { label: 'date', type: 'init', values: 1712966400000 } + ]); + log.length = 0; + + flushSync(() => b1.click()); // map.set('key', 'value') + + in1.value = 'name'; + in2.value = 'Svelte'; + in1.dispatchEvent(new window.Event('input', { bubbles: true })); + in2.dispatchEvent(new window.Event('input', { bubbles: true })); + flushSync(() => b1.click()); // map.set('name', 'Svelte') + + in2.value = 'World'; + in2.dispatchEvent(new window.Event('input', { bubbles: true })); + flushSync(() => b1.click()); // map.set('name', 'World') + flushSync(() => b1.click()); // map.set('name', 'World') + + assert.deepEqual(log, [ + { label: 'map', type: 'update', values: [['key', 'value']] }, + { + label: 'map', + type: 'update', + values: [ + ['key', 'value'], + ['name', 'Svelte'] + ] + }, + { + label: 'map', + type: 'update', + values: [ + ['key', 'value'], + ['name', 'World'] + ] + } + ]); + log.length = 0; + + flushSync(() => b2.click()); // set.add('name'); + + in1.value = 'Svelte'; + in1.dispatchEvent(new window.Event('input', { bubbles: true })); + flushSync(() => b2.click()); // set.add('Svelte'); + + flushSync(() => b2.click()); // set.add('Svelte'); + + assert.deepEqual(log, [ + { label: 'set', type: 'update', values: ['name'] }, + { label: 'set', type: 'update', values: ['name', 'Svelte'] } + ]); + log.length = 0; + + flushSync(() => b3.click()); // date.minutes++ + flushSync(() => b3.click()); // date.minutes++ + flushSync(() => b3.click()); // date.minutes++ + + assert.deepEqual(log, [ + { label: 'date', type: 'update', values: 1712966460000 }, + { label: 'date', type: 'update', values: 1712966520000 }, + { label: 'date', type: 'update', values: 1712966580000 } + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-reactivity/log.js b/packages/svelte/tests/runtime-runes/samples/inspect-reactivity/log.js new file mode 100644 index 0000000000..d3df521f4d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-reactivity/log.js @@ -0,0 +1,2 @@ +/** @type {any[]} */ +export const log = []; diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-reactivity/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-reactivity/main.svelte new file mode 100644 index 0000000000..3bbe39a46e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-reactivity/main.svelte @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index a47745a63a..68c992efbe 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2021,6 +2021,7 @@ declare module 'svelte/reactivity' { #private; } class ReactiveURLSearchParams extends URLSearchParams { + constructor(); [REPLACE](params: URLSearchParams): void; #private;