chore: refactor `$inspect` (#11226)

* chore: move inspect logic into its own module

* better error

* remove unused imports
pull/11227/head
Rich Harris 1 year ago committed by GitHub
parent 5fce00f06e
commit 307f15d5f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,98 @@
import { snapshot } from '../proxy.js';
import { render_effect } from '../reactivity/effects.js';
import { current_effect, deep_read } from '../runtime.js';
import { array_prototype, get_prototype_of, object_prototype } from '../utils.js';
/** @type {Function | null} */
export let inspect_fn = null;
/** @param {Function | null} fn */
export function set_inspect_fn(fn) {
inspect_fn = fn;
}
/** @type {Array<import('#client').ValueDebug>} */
export let inspect_captured_signals = [];
/**
* @param {() => any[]} get_value
* @param {Function} [inspector]
*/
// eslint-disable-next-line no-console
export function inspect(get_value, inspector = console.log) {
if (!current_effect) {
throw new Error(
'$inspect can only be used inside an effect (e.g. during component initialisation)'
);
}
let initial = true;
// we assign the function directly to signals, rather than just
// calling `inspector` directly inside the effect, so that
// we get useful stack traces
var fn = () => {
const value = deep_snapshot(get_value());
inspector(initial ? 'init' : 'update', ...value);
};
render_effect(() => {
inspect_fn = fn;
deep_read(get_value());
inspect_fn = null;
const signals = inspect_captured_signals.slice();
inspect_captured_signals = [];
if (initial) {
fn();
initial = false;
}
return () => {
for (const s of signals) {
s.inspect.delete(fn);
}
};
});
}
/**
* Like `snapshot`, but recursively traverses into normal arrays/objects to find potential states in them.
* @param {any} value
* @param {Map<any, any>} visited
* @returns {any}
*/
function deep_snapshot(value, visited = new Map()) {
if (typeof value === 'object' && value !== null && !visited.has(value)) {
const unstated = snapshot(value);
if (unstated !== value) {
visited.set(value, unstated);
return unstated;
}
const prototype = get_prototype_of(value);
// Only deeply snapshot plain objects and arrays
if (prototype === object_prototype || prototype === array_prototype) {
let contains_unstated = false;
/** @type {any} */
const nested_unstated = Array.isArray(value) ? [] : {};
for (let key in value) {
const result = deep_snapshot(value[key], visited);
nested_unstated[key] = result;
if (result !== value[key]) {
contains_unstated = true;
}
}
visited.set(value, contains_unstated ? nested_unstated : value);
} else {
visited.set(value, value);
}
}
return visited.get(value) ?? value;
}

@ -1,5 +1,6 @@
export { hmr } from './dev/hmr.js';
export { ADD_OWNER, add_owner, mark_module_start, mark_module_end } from './dev/ownership.js';
export { inspect } from './dev/inspect.js';
export { await_block as await } from './dom/blocks/await.js';
export { if_block as if } from './dom/blocks/if.js';
export { key_block as key } from './dom/blocks/key.js';
@ -111,7 +112,6 @@ export {
exclude_from_object,
pop,
push,
inspect,
unwrap,
freeze,
deep_read,

@ -8,8 +8,9 @@ import {
import { get_descriptor, is_function } from '../utils.js';
import { mutable_source, set } from './sources.js';
import { derived } from './deriveds.js';
import { get, inspect_fn, is_signals_recorded, untrack } from '../runtime.js';
import { get, is_signals_recorded, untrack } from '../runtime.js';
import { safe_equals } from './equality.js';
import { inspect_fn } from '../dev/inspect.js';
/**
* @param {((value?: number) => number)} fn

@ -1,19 +1,7 @@
import { DEV } from 'esm-env';
import {
array_prototype,
get_descriptors,
get_prototype_of,
is_frozen,
object_freeze,
object_prototype
} from './utils.js';
import { get_descriptors, get_prototype_of, is_frozen, object_freeze } from './utils.js';
import { snapshot } from './proxy.js';
import {
destroy_effect,
effect,
execute_effect_teardown,
user_pre_effect
} from './reactivity/effects.js';
import { destroy_effect, effect, execute_effect_teardown } from './reactivity/effects.js';
import {
EFFECT,
RENDER_EFFECT,
@ -33,6 +21,7 @@ import { flush_tasks } from './dom/task.js';
import { add_owner } from './dev/ownership.js';
import { mutate, set, source } from './reactivity/sources.js';
import { update_derived } from './reactivity/deriveds.js';
import { inspect_captured_signals, inspect_fn, set_inspect_fn } from './dev/inspect.js';
const FLUSH_MICROTASK = 0;
const FLUSH_SYNC = 1;
@ -115,12 +104,6 @@ export let current_skip_reaction = false;
export let is_signals_recorded = false;
let captured_signals = new Set();
/** @type {Function | null} */
export let inspect_fn = null;
/** @type {Array<import('./types.js').ValueDebug>} */
let inspect_captured_signals = [];
// Handling runtime component context
/** @type {import('./types.js').ComponentContext | null} */
export let current_component_context = null;
@ -700,11 +683,10 @@ export async function tick() {
* @returns {V}
*/
export function get(signal) {
// @ts-expect-error
if (DEV && signal.inspect && inspect_fn) {
/** @type {import('./types.js').ValueDebug} */ (signal).inspect.add(inspect_fn);
// @ts-expect-error
inspect_captured_signals.push(signal);
if (DEV && inspect_fn) {
var s = /** @type {import('#client').ValueDebug} */ (signal);
s.inspect.add(inspect_fn);
inspect_captured_signals.push(s);
}
const flags = signal.f;
@ -761,9 +743,9 @@ export function get(signal) {
if (DEV) {
// we want to avoid tracking indirect dependencies
const previous_inspect_fn = inspect_fn;
inspect_fn = null;
set_inspect_fn(null);
update_derived(/** @type {import('./types.js').Derived} **/ (signal), false);
inspect_fn = previous_inspect_fn;
set_inspect_fn(previous_inspect_fn);
} else {
update_derived(/** @type {import('./types.js').Derived} **/ (signal), false);
}
@ -1186,86 +1168,6 @@ export function deep_read(value, visited = new Set()) {
}
}
/**
* Like `snapshot`, but recursively traverses into normal arrays/objects to find potential states in them.
* @param {any} value
* @param {Map<any, any>} visited
* @returns {any}
*/
function deep_snapshot(value, visited = new Map()) {
if (typeof value === 'object' && value !== null && !visited.has(value)) {
const unstated = snapshot(value);
if (unstated !== value) {
visited.set(value, unstated);
return unstated;
}
const prototype = get_prototype_of(value);
// Only deeply snapshot plain objects and arrays
if (prototype === object_prototype || prototype === array_prototype) {
let contains_unstated = false;
/** @type {any} */
const nested_unstated = Array.isArray(value) ? [] : {};
for (let key in value) {
const result = deep_snapshot(value[key], visited);
nested_unstated[key] = result;
if (result !== value[key]) {
contains_unstated = true;
}
}
visited.set(value, contains_unstated ? nested_unstated : value);
} else {
visited.set(value, value);
}
}
return visited.get(value) ?? value;
}
// TODO remove in a few versions, before 5.0 at the latest
let warned_inspect_changed = false;
/**
* @param {() => any[]} get_value
* @param {Function} [inspect]
*/
// eslint-disable-next-line no-console
export function inspect(get_value, inspect = console.log) {
let initial = true;
user_pre_effect(() => {
const fn = () => {
const value = untrack(() => get_value().map((v) => deep_snapshot(v)));
if (value.length === 2 && typeof value[1] === 'function' && !warned_inspect_changed) {
// eslint-disable-next-line no-console
console.warn(
'$inspect() API has changed. See https://svelte-5-preview.vercel.app/docs/runes#$inspect for more information.'
);
warned_inspect_changed = true;
}
inspect(initial ? 'init' : 'update', ...value);
};
inspect_fn = fn;
const value = get_value();
deep_read(value);
inspect_fn = null;
const signals = inspect_captured_signals.slice();
inspect_captured_signals = [];
if (initial) {
fn();
initial = false;
}
return () => {
for (const s of signals) {
s.inspect.delete(fn);
}
};
});
}
/**
* @template V
* @param {V | import('#client').Value<V>} value

Loading…
Cancel
Save