mirror of https://github.com/sveltejs/svelte
chore: refactor `$inspect` (#11226)
* chore: move inspect logic into its own module * better error * remove unused importspull/11227/head
parent
5fce00f06e
commit
307f15d5f7
@ -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;
|
||||||
|
}
|
Loading…
Reference in new issue