Skip to content

Commit 307f15d

Browse files
authored
chore: refactor $inspect (#11226)
* chore: move inspect logic into its own module * better error * remove unused imports
1 parent 5fce00f commit 307f15d

File tree

4 files changed

+110
-109
lines changed

4 files changed

+110
-109
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { snapshot } from '../proxy.js';
2+
import { render_effect } from '../reactivity/effects.js';
3+
import { current_effect, deep_read } from '../runtime.js';
4+
import { array_prototype, get_prototype_of, object_prototype } from '../utils.js';
5+
6+
/** @type {Function | null} */
7+
export let inspect_fn = null;
8+
9+
/** @param {Function | null} fn */
10+
export function set_inspect_fn(fn) {
11+
inspect_fn = fn;
12+
}
13+
14+
/** @type {Array<import('#client').ValueDebug>} */
15+
export let inspect_captured_signals = [];
16+
17+
/**
18+
* @param {() => any[]} get_value
19+
* @param {Function} [inspector]
20+
*/
21+
// eslint-disable-next-line no-console
22+
export function inspect(get_value, inspector = console.log) {
23+
if (!current_effect) {
24+
throw new Error(
25+
'$inspect can only be used inside an effect (e.g. during component initialisation)'
26+
);
27+
}
28+
29+
let initial = true;
30+
31+
// we assign the function directly to signals, rather than just
32+
// calling `inspector` directly inside the effect, so that
33+
// we get useful stack traces
34+
var fn = () => {
35+
const value = deep_snapshot(get_value());
36+
inspector(initial ? 'init' : 'update', ...value);
37+
};
38+
39+
render_effect(() => {
40+
inspect_fn = fn;
41+
deep_read(get_value());
42+
inspect_fn = null;
43+
44+
const signals = inspect_captured_signals.slice();
45+
inspect_captured_signals = [];
46+
47+
if (initial) {
48+
fn();
49+
initial = false;
50+
}
51+
52+
return () => {
53+
for (const s of signals) {
54+
s.inspect.delete(fn);
55+
}
56+
};
57+
});
58+
}
59+
60+
/**
61+
* Like `snapshot`, but recursively traverses into normal arrays/objects to find potential states in them.
62+
* @param {any} value
63+
* @param {Map<any, any>} visited
64+
* @returns {any}
65+
*/
66+
function deep_snapshot(value, visited = new Map()) {
67+
if (typeof value === 'object' && value !== null && !visited.has(value)) {
68+
const unstated = snapshot(value);
69+
70+
if (unstated !== value) {
71+
visited.set(value, unstated);
72+
return unstated;
73+
}
74+
75+
const prototype = get_prototype_of(value);
76+
77+
// Only deeply snapshot plain objects and arrays
78+
if (prototype === object_prototype || prototype === array_prototype) {
79+
let contains_unstated = false;
80+
/** @type {any} */
81+
const nested_unstated = Array.isArray(value) ? [] : {};
82+
83+
for (let key in value) {
84+
const result = deep_snapshot(value[key], visited);
85+
nested_unstated[key] = result;
86+
if (result !== value[key]) {
87+
contains_unstated = true;
88+
}
89+
}
90+
91+
visited.set(value, contains_unstated ? nested_unstated : value);
92+
} else {
93+
visited.set(value, value);
94+
}
95+
}
96+
97+
return visited.get(value) ?? value;
98+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { hmr } from './dev/hmr.js';
22
export { ADD_OWNER, add_owner, mark_module_start, mark_module_end } from './dev/ownership.js';
3+
export { inspect } from './dev/inspect.js';
34
export { await_block as await } from './dom/blocks/await.js';
45
export { if_block as if } from './dom/blocks/if.js';
56
export { key_block as key } from './dom/blocks/key.js';
@@ -111,7 +112,6 @@ export {
111112
exclude_from_object,
112113
pop,
113114
push,
114-
inspect,
115115
unwrap,
116116
freeze,
117117
deep_read,

packages/svelte/src/internal/client/reactivity/props.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import {
88
import { get_descriptor, is_function } from '../utils.js';
99
import { mutable_source, set } from './sources.js';
1010
import { derived } from './deriveds.js';
11-
import { get, inspect_fn, is_signals_recorded, untrack } from '../runtime.js';
11+
import { get, is_signals_recorded, untrack } from '../runtime.js';
1212
import { safe_equals } from './equality.js';
13+
import { inspect_fn } from '../dev/inspect.js';
1314

1415
/**
1516
* @param {((value?: number) => number)} fn

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

Lines changed: 9 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,7 @@
11
import { DEV } from 'esm-env';
2-
import {
3-
array_prototype,
4-
get_descriptors,
5-
get_prototype_of,
6-
is_frozen,
7-
object_freeze,
8-
object_prototype
9-
} from './utils.js';
2+
import { get_descriptors, get_prototype_of, is_frozen, object_freeze } from './utils.js';
103
import { snapshot } from './proxy.js';
11-
import {
12-
destroy_effect,
13-
effect,
14-
execute_effect_teardown,
15-
user_pre_effect
16-
} from './reactivity/effects.js';
4+
import { destroy_effect, effect, execute_effect_teardown } from './reactivity/effects.js';
175
import {
186
EFFECT,
197
RENDER_EFFECT,
@@ -33,6 +21,7 @@ import { flush_tasks } from './dom/task.js';
3321
import { add_owner } from './dev/ownership.js';
3422
import { mutate, set, source } from './reactivity/sources.js';
3523
import { update_derived } from './reactivity/deriveds.js';
24+
import { inspect_captured_signals, inspect_fn, set_inspect_fn } from './dev/inspect.js';
3625

3726
const FLUSH_MICROTASK = 0;
3827
const FLUSH_SYNC = 1;
@@ -115,12 +104,6 @@ export let current_skip_reaction = false;
115104
export let is_signals_recorded = false;
116105
let captured_signals = new Set();
117106

118-
/** @type {Function | null} */
119-
export let inspect_fn = null;
120-
121-
/** @type {Array<import('./types.js').ValueDebug>} */
122-
let inspect_captured_signals = [];
123-
124107
// Handling runtime component context
125108
/** @type {import('./types.js').ComponentContext | null} */
126109
export let current_component_context = null;
@@ -700,11 +683,10 @@ export async function tick() {
700683
* @returns {V}
701684
*/
702685
export function get(signal) {
703-
// @ts-expect-error
704-
if (DEV && signal.inspect && inspect_fn) {
705-
/** @type {import('./types.js').ValueDebug} */ (signal).inspect.add(inspect_fn);
706-
// @ts-expect-error
707-
inspect_captured_signals.push(signal);
686+
if (DEV && inspect_fn) {
687+
var s = /** @type {import('#client').ValueDebug} */ (signal);
688+
s.inspect.add(inspect_fn);
689+
inspect_captured_signals.push(s);
708690
}
709691

710692
const flags = signal.f;
@@ -761,9 +743,9 @@ export function get(signal) {
761743
if (DEV) {
762744
// we want to avoid tracking indirect dependencies
763745
const previous_inspect_fn = inspect_fn;
764-
inspect_fn = null;
746+
set_inspect_fn(null);
765747
update_derived(/** @type {import('./types.js').Derived} **/ (signal), false);
766-
inspect_fn = previous_inspect_fn;
748+
set_inspect_fn(previous_inspect_fn);
767749
} else {
768750
update_derived(/** @type {import('./types.js').Derived} **/ (signal), false);
769751
}
@@ -1186,86 +1168,6 @@ export function deep_read(value, visited = new Set()) {
11861168
}
11871169
}
11881170

1189-
/**
1190-
* Like `snapshot`, but recursively traverses into normal arrays/objects to find potential states in them.
1191-
* @param {any} value
1192-
* @param {Map<any, any>} visited
1193-
* @returns {any}
1194-
*/
1195-
function deep_snapshot(value, visited = new Map()) {
1196-
if (typeof value === 'object' && value !== null && !visited.has(value)) {
1197-
const unstated = snapshot(value);
1198-
if (unstated !== value) {
1199-
visited.set(value, unstated);
1200-
return unstated;
1201-
}
1202-
const prototype = get_prototype_of(value);
1203-
// Only deeply snapshot plain objects and arrays
1204-
if (prototype === object_prototype || prototype === array_prototype) {
1205-
let contains_unstated = false;
1206-
/** @type {any} */
1207-
const nested_unstated = Array.isArray(value) ? [] : {};
1208-
for (let key in value) {
1209-
const result = deep_snapshot(value[key], visited);
1210-
nested_unstated[key] = result;
1211-
if (result !== value[key]) {
1212-
contains_unstated = true;
1213-
}
1214-
}
1215-
visited.set(value, contains_unstated ? nested_unstated : value);
1216-
} else {
1217-
visited.set(value, value);
1218-
}
1219-
}
1220-
1221-
return visited.get(value) ?? value;
1222-
}
1223-
1224-
// TODO remove in a few versions, before 5.0 at the latest
1225-
let warned_inspect_changed = false;
1226-
1227-
/**
1228-
* @param {() => any[]} get_value
1229-
* @param {Function} [inspect]
1230-
*/
1231-
// eslint-disable-next-line no-console
1232-
export function inspect(get_value, inspect = console.log) {
1233-
let initial = true;
1234-
1235-
user_pre_effect(() => {
1236-
const fn = () => {
1237-
const value = untrack(() => get_value().map((v) => deep_snapshot(v)));
1238-
if (value.length === 2 && typeof value[1] === 'function' && !warned_inspect_changed) {
1239-
// eslint-disable-next-line no-console
1240-
console.warn(
1241-
'$inspect() API has changed. See https://svelte-5-preview.vercel.app/docs/runes#$inspect for more information.'
1242-
);
1243-
warned_inspect_changed = true;
1244-
}
1245-
inspect(initial ? 'init' : 'update', ...value);
1246-
};
1247-
1248-
inspect_fn = fn;
1249-
const value = get_value();
1250-
deep_read(value);
1251-
inspect_fn = null;
1252-
1253-
const signals = inspect_captured_signals.slice();
1254-
inspect_captured_signals = [];
1255-
1256-
if (initial) {
1257-
fn();
1258-
initial = false;
1259-
}
1260-
1261-
return () => {
1262-
for (const s of signals) {
1263-
s.inspect.delete(fn);
1264-
}
1265-
};
1266-
});
1267-
}
1268-
12691171
/**
12701172
* @template V
12711173
* @param {V | import('#client').Value<V>} value

0 commit comments

Comments
 (0)