Skip to content

Commit 43d13e9

Browse files
authored
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
1 parent 70b47de commit 43d13e9

File tree

11 files changed

+197
-2
lines changed

11 files changed

+197
-2
lines changed

.changeset/tiny-poems-doubt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
feat: allow inspect reactivity map, set, date

packages/svelte/src/internal/client/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export const EFFECT_RAN = 1 << 13;
1616
export const EFFECT_TRANSPARENT = 1 << 14;
1717

1818
export const STATE_SYMBOL = Symbol('$state');
19+
export const INSPECT_SYMBOL = Symbol('$inspect');

packages/svelte/src/internal/client/runtime.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import {
2727
BRANCH_EFFECT,
2828
STATE_SYMBOL,
2929
BLOCK_EFFECT,
30-
ROOT_EFFECT
30+
ROOT_EFFECT,
31+
INSPECT_SYMBOL
3132
} from './constants.js';
3233
import { flush_tasks } from './dom/task.js';
3334
import { add_owner } from './dev/ownership.js';
@@ -1117,6 +1118,24 @@ export function pop(component) {
11171118
return component || /** @type {T} */ ({});
11181119
}
11191120

1121+
/**
1122+
*
1123+
* This is called from the inspect.
1124+
* Deeply traverse every item in the array with `deep_read` to register for inspect callback
1125+
* If the item implements INSPECT_SYMBOL, will use that instead
1126+
* @param {Array<any>} value
1127+
* @returns {void}
1128+
*/
1129+
function deep_read_inpect(value) {
1130+
for (const item of value) {
1131+
if (item && typeof item[INSPECT_SYMBOL] === 'function') {
1132+
item[INSPECT_SYMBOL]();
1133+
} else {
1134+
deep_read(item);
1135+
}
1136+
}
1137+
}
1138+
11201139
/**
11211140
* Possibly traverse an object and read all its properties so that they're all reactive in case this is `$state`.
11221141
* 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) {
12471266

12481267
inspect_fn = fn;
12491268
const value = get_value();
1250-
deep_read(value);
1269+
deep_read_inpect(value);
12511270
inspect_fn = null;
12521271

12531272
const signals = inspect_captured_signals.slice();

packages/svelte/src/reactivity/date.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { DEV } from 'esm-env';
2+
import { INSPECT_SYMBOL } from '../internal/client/constants.js';
13
import { source, set } from '../internal/client/reactivity/sources.js';
24
import { get } from '../internal/client/runtime.js';
35

@@ -88,6 +90,13 @@ export class ReactiveDate extends Date {
8890
return v;
8991
};
9092
}
93+
94+
if (DEV) {
95+
// @ts-ignore
96+
proto[INSPECT_SYMBOL] = function () {
97+
get(this.#raw_time);
98+
};
99+
}
91100
}
92101
}
93102

packages/svelte/src/reactivity/map.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { source, set } from '../internal/client/reactivity/sources.js';
33
import { get } from '../internal/client/runtime.js';
44
import { UNINITIALIZED } from '../constants.js';
55
import { map } from './utils.js';
6+
import { INSPECT_SYMBOL } from '../internal/client/constants.js';
7+
8+
var inited = false;
69

710
/**
811
* @template K
@@ -20,6 +23,22 @@ export class ReactiveMap extends Map {
2023
constructor(value) {
2124
super();
2225

26+
if (DEV) {
27+
if (!inited) {
28+
inited = true;
29+
// @ts-ignore
30+
ReactiveMap.prototype[INSPECT_SYMBOL] = function () {
31+
// changes could either introduced by
32+
// - modifying the value, or
33+
// - add / remove entries to the map
34+
for (const [, source] of this.#sources) {
35+
get(source);
36+
}
37+
get(this.#size);
38+
};
39+
}
40+
}
41+
2342
// If the value is invalid then the native exception will fire here
2443
if (DEV) new Map(value);
2544

packages/svelte/src/reactivity/set.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DEV } from 'esm-env';
22
import { source, set } from '../internal/client/reactivity/sources.js';
33
import { get } from '../internal/client/runtime.js';
44
import { map } from './utils.js';
5+
import { INSPECT_SYMBOL } from '../internal/client/constants.js';
56

67
var read_methods = ['forEach', 'isDisjointFrom', 'isSubsetOf', 'isSupersetOf'];
78
var set_like_methods = ['difference', 'intersection', 'symmetricDifference', 'union'];
@@ -65,6 +66,13 @@ export class ReactiveSet extends Set {
6566
return new ReactiveSet(set);
6667
};
6768
}
69+
70+
if (DEV) {
71+
// @ts-ignore
72+
proto[INSPECT_SYMBOL] = function () {
73+
get(this.#version);
74+
};
75+
}
6876
}
6977

7078
#increment_version() {

packages/svelte/src/reactivity/url.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import { DEV } from 'esm-env';
2+
import { INSPECT_SYMBOL } from '../internal/client/constants.js';
13
import { source, set } from '../internal/client/reactivity/sources.js';
24
import { get } from '../internal/client/runtime.js';
35

46
const REPLACE = Symbol();
7+
var inited_url = false;
8+
var inited_search_params = false;
59

610
export class ReactiveURL extends URL {
711
#protocol = source(super.protocol);
@@ -21,6 +25,14 @@ export class ReactiveURL extends URL {
2125
url = new URL(url, base);
2226
super(url);
2327
this.#searchParams[REPLACE](url.searchParams);
28+
29+
if (DEV && !inited_url) {
30+
inited_url = true;
31+
// @ts-ignore
32+
ReactiveURL.prototype[INSPECT_SYMBOL] = function () {
33+
this.href;
34+
};
35+
}
2436
}
2537

2638
get hash() {
@@ -159,6 +171,17 @@ export class ReactiveURLSearchParams extends URLSearchParams {
159171
set(this.#version, this.#version.v + 1);
160172
}
161173

174+
constructor() {
175+
super();
176+
if (DEV && !inited_search_params) {
177+
inited_search_params = true;
178+
// @ts-ignore
179+
ReactiveURLSearchParams.prototype[INSPECT_SYMBOL] = function () {
180+
get(this.#version);
181+
};
182+
}
183+
}
184+
162185
/**
163186
* @param {URLSearchParams} params
164187
*/
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { test } from '../../test';
2+
import { flushSync } from 'svelte';
3+
import { log } from './log';
4+
5+
export default test({
6+
compileOptions: {
7+
dev: true
8+
},
9+
before_test() {
10+
log.length = 0;
11+
},
12+
async test({ assert, target }) {
13+
const [in1, in2] = target.querySelectorAll('input');
14+
const [b1, b2, b3] = target.querySelectorAll('button');
15+
16+
assert.deepEqual(log, [
17+
{ label: 'map', type: 'init', values: [] },
18+
{ label: 'set', type: 'init', values: [] },
19+
{ label: 'date', type: 'init', values: 1712966400000 }
20+
]);
21+
log.length = 0;
22+
23+
flushSync(() => b1.click()); // map.set('key', 'value')
24+
25+
in1.value = 'name';
26+
in2.value = 'Svelte';
27+
in1.dispatchEvent(new window.Event('input', { bubbles: true }));
28+
in2.dispatchEvent(new window.Event('input', { bubbles: true }));
29+
flushSync(() => b1.click()); // map.set('name', 'Svelte')
30+
31+
in2.value = 'World';
32+
in2.dispatchEvent(new window.Event('input', { bubbles: true }));
33+
flushSync(() => b1.click()); // map.set('name', 'World')
34+
flushSync(() => b1.click()); // map.set('name', 'World')
35+
36+
assert.deepEqual(log, [
37+
{ label: 'map', type: 'update', values: [['key', 'value']] },
38+
{
39+
label: 'map',
40+
type: 'update',
41+
values: [
42+
['key', 'value'],
43+
['name', 'Svelte']
44+
]
45+
},
46+
{
47+
label: 'map',
48+
type: 'update',
49+
values: [
50+
['key', 'value'],
51+
['name', 'World']
52+
]
53+
}
54+
]);
55+
log.length = 0;
56+
57+
flushSync(() => b2.click()); // set.add('name');
58+
59+
in1.value = 'Svelte';
60+
in1.dispatchEvent(new window.Event('input', { bubbles: true }));
61+
flushSync(() => b2.click()); // set.add('Svelte');
62+
63+
flushSync(() => b2.click()); // set.add('Svelte');
64+
65+
assert.deepEqual(log, [
66+
{ label: 'set', type: 'update', values: ['name'] },
67+
{ label: 'set', type: 'update', values: ['name', 'Svelte'] }
68+
]);
69+
log.length = 0;
70+
71+
flushSync(() => b3.click()); // date.minutes++
72+
flushSync(() => b3.click()); // date.minutes++
73+
flushSync(() => b3.click()); // date.minutes++
74+
75+
assert.deepEqual(log, [
76+
{ label: 'date', type: 'update', values: 1712966460000 },
77+
{ label: 'date', type: 'update', values: 1712966520000 },
78+
{ label: 'date', type: 'update', values: 1712966580000 }
79+
]);
80+
}
81+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/** @type {any[]} */
2+
export const log = [];
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script>
2+
import { Map, Set, Date } from 'svelte/reactivity';
3+
import { log } from './log';
4+
5+
const map = new Map();
6+
const set = new Set();
7+
const date = new Date('2024-04-13 00:00:00+0000');
8+
let key = $state('key');
9+
let value = $state('value');
10+
11+
$inspect(map).with((type, map) => {
12+
log.push({ label: 'map', type, values: [...map] });
13+
});
14+
$inspect(set).with((type, set) => {
15+
log.push({ label: 'set', type, values: [...set] });
16+
});
17+
$inspect(date).with((type, date) => {
18+
log.push({ label: 'date', type, values: date.getTime() });
19+
});
20+
</script>
21+
22+
<input bind:value={key} />
23+
<input bind:value={value} />
24+
25+
<button on:click={() => map.set(key, value)}>map</button>
26+
<button on:click={() => set.add(key)}>set</button>
27+
<button on:click={() => date.setMinutes(date.getMinutes() + 1)}>date</button>

packages/svelte/types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2021,6 +2021,7 @@ declare module 'svelte/reactivity' {
20212021
#private;
20222022
}
20232023
class ReactiveURLSearchParams extends URLSearchParams {
2024+
constructor();
20242025

20252026
[REPLACE](params: URLSearchParams): void;
20262027
#private;

0 commit comments

Comments
 (0)