Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tiny-poems-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": patch
---

feat: allow inspect reactivity map, set, date
1 change: 1 addition & 0 deletions packages/svelte/src/internal/client/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export const IS_ELSEIF = 1 << 13;
export const EFFECT_RAN = 1 << 14;

export const STATE_SYMBOL = Symbol('$state');
export const INSPECT_SYMBOL = Symbol('$inspect');
23 changes: 21 additions & 2 deletions packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,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';
Expand Down Expand Up @@ -1096,6 +1097,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<any>} 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).
Expand Down Expand Up @@ -1226,7 +1245,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();
Expand Down
5 changes: 5 additions & 0 deletions packages/svelte/src/reactivity/date.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { INSPECT_SYMBOL } from '../internal/client/constants.js';
import { source, set } from '../internal/client/reactivity/sources.js';
import { get } from '../internal/client/runtime.js';

Expand Down Expand Up @@ -99,4 +100,8 @@ export class ReactiveDate extends Date {
super(...values);
this.#init();
}

[INSPECT_SYMBOL]() {
get(this.#raw_time);
}
}
11 changes: 11 additions & 0 deletions packages/svelte/src/reactivity/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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';

/**
* @template K
Expand Down Expand Up @@ -155,4 +156,14 @@ export class ReactiveMap extends Map {
get size() {
return get(this.#size);
}

[INSPECT_SYMBOL]() {
// 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);
}
}
4 changes: 4 additions & 0 deletions packages/svelte/src/reactivity/set.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -150,4 +151,7 @@ export class ReactiveSet extends Set {
get size() {
return get(this.#size);
}
[INSPECT_SYMBOL]() {
get(this.#version);
}
}
5 changes: 5 additions & 0 deletions packages/svelte/src/reactivity/url.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { INSPECT_SYMBOL } from '../internal/client/constants.js';
import { source, set } from '../internal/client/reactivity/sources.js';
import { get } from '../internal/client/runtime.js';

Expand Down Expand Up @@ -150,6 +151,10 @@ export class ReactiveURL extends URL {
toJSON() {
return this.href;
}

[INSPECT_SYMBOL]() {
this.href;
}
}

export class ReactiveURLSearchParams extends URLSearchParams {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }
]);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script>
import { Map, Set, Date } from 'svelte/reactivity';
import { log } from './log';

const map = new Map();
const set = new Set();
const date = new Date('2024-04-13 00:00:00+0000');
let key = $state('key');
let value = $state('value');

$inspect(map).with((type, map) => {
log.push({ label: 'map', type, values: [...map] });
});
$inspect(set).with((type, set) => {
log.push({ label: 'set', type, values: [...set] });
});
$inspect(date).with((type, date) => {
log.push({ label: 'date', type, values: date.getTime() });
});
</script>

<input bind:value={key} />
<input bind:value={value} />

<button on:click={() => map.set(key, value)}>map</button>
<button on:click={() => set.add(key)}>set</button>
<button on:click={() => date.setMinutes(date.getMinutes() + 1)}>date</button>
4 changes: 4 additions & 0 deletions packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1972,6 +1972,7 @@ declare module 'svelte/reactivity' {
class ReactiveDate extends Date {

constructor(...values: any[]);
[INSPECT_SYMBOL](): void;
#private;
}
class ReactiveSet<T> extends Set<any> {
Expand All @@ -1987,6 +1988,7 @@ declare module 'svelte/reactivity' {
values(): IterableIterator<T>;
entries(): IterableIterator<[T, T]>;
[Symbol.iterator](): IterableIterator<T>;
[INSPECT_SYMBOL](): void;
#private;
}
class ReactiveMap<K, V> extends Map<any, any> {
Expand All @@ -2006,6 +2008,7 @@ declare module 'svelte/reactivity' {
values(): IterableIterator<V>;
entries(): IterableIterator<[K, V]>;
[Symbol.iterator](): IterableIterator<[K, V]>;
[INSPECT_SYMBOL](): void;
#private;
}
class ReactiveURL extends URL {
Expand All @@ -2018,6 +2021,7 @@ declare module 'svelte/reactivity' {
#private;
}
const REPLACE: unique symbol;
const INSPECT_SYMBOL: unique symbol;

export { ReactiveDate as Date, ReactiveSet as Set, ReactiveMap as Map, ReactiveURL as URL, ReactiveURLSearchParams as URLSearchParams };
}
Expand Down