Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow $inspect reactivity map, set, date #11164

Merged
merged 5 commits into from
Apr 18, 2024
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') {
trueadm marked this conversation as resolved.
Show resolved Hide resolved
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]() {
trueadm marked this conversation as resolved.
Show resolved Hide resolved
// 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]() {
trueadm marked this conversation as resolved.
Show resolved Hide resolved
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]() {
trueadm marked this conversation as resolved.
Show resolved Hide resolved
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
Loading