svelte
Version:
Cybernetically enhanced web apps
197 lines (162 loc) • 4.7 kB
JavaScript
/** @import { Derived, Reaction, Value } from '#client' */
import { UNINITIALIZED } from '../../../constants.js';
import { snapshot } from '../../shared/clone.js';
import { define_property } from '../../shared/utils.js';
import { DERIVED, ASYNC, PROXY_PATH_SYMBOL, STATE_SYMBOL } from '#client/constants';
import { effect_tracking } from '../reactivity/effects.js';
import { active_reaction, captured_signals, set_captured_signals, untrack } from '../runtime.js';
/**
* @typedef {{
* traces: Error[];
* }} TraceEntry
*/
/** @type {{ reaction: Reaction | null, entries: Map<Value, TraceEntry> } | null} */
export let tracing_expressions = null;
/**
* @param {Value} signal
* @param {TraceEntry} [entry]
*/
function log_entry(signal, entry) {
const value = signal.v;
if (value === UNINITIALIZED) {
return;
}
const type = (signal.f & (DERIVED | ASYNC)) !== 0 ? '$derived' : '$state';
const current_reaction = /** @type {Reaction} */ (active_reaction);
const dirty = signal.wv > current_reaction.wv || current_reaction.wv === 0;
const style = dirty
? 'color: CornflowerBlue; font-weight: bold'
: 'color: grey; font-weight: normal';
// eslint-disable-next-line no-console
console.groupCollapsed(
signal.label ? `%c${type}%c ${signal.label}` : `%c${type}%c`,
style,
dirty ? 'font-weight: normal' : style,
typeof value === 'object' && value !== null && STATE_SYMBOL in value
? snapshot(value, true)
: value
);
if (type === '$derived') {
const deps = new Set(/** @type {Derived} */ (signal).deps);
for (const dep of deps) {
log_entry(dep);
}
}
if (signal.created) {
// eslint-disable-next-line no-console
console.log(signal.created);
}
if (dirty && signal.updated) {
for (const updated of signal.updated.values()) {
// eslint-disable-next-line no-console
console.log(updated.error);
}
}
if (entry) {
for (var trace of entry.traces) {
// eslint-disable-next-line no-console
console.log(trace);
}
}
// eslint-disable-next-line no-console
console.groupEnd();
}
/**
* @template T
* @param {() => string} label
* @param {() => T} fn
*/
export function trace(label, fn) {
var previously_tracing_expressions = tracing_expressions;
try {
tracing_expressions = { entries: new Map(), reaction: active_reaction };
var start = performance.now();
var value = fn();
var time = (performance.now() - start).toFixed(2);
var prefix = untrack(label);
if (!effect_tracking()) {
// eslint-disable-next-line no-console
console.log(`${prefix} %cran outside of an effect (${time}ms)`, 'color: grey');
} else if (tracing_expressions.entries.size === 0) {
// eslint-disable-next-line no-console
console.log(`${prefix} %cno reactive dependencies (${time}ms)`, 'color: grey');
} else {
// eslint-disable-next-line no-console
console.group(`${prefix} %c(${time}ms)`, 'color: grey');
var entries = tracing_expressions.entries;
untrack(() => {
for (const [signal, traces] of entries) {
log_entry(signal, traces);
}
});
tracing_expressions = null;
// eslint-disable-next-line no-console
console.groupEnd();
}
return value;
} finally {
tracing_expressions = previously_tracing_expressions;
}
}
/**
* @param {string} label
* @returns {Error & { stack: string } | null}
*/
export function get_stack(label) {
let error = Error();
const stack = error.stack;
if (!stack) return null;
const lines = stack.split('\n');
const new_lines = ['\n'];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line === 'Error') {
continue;
}
if (line.includes('validate_each_keys')) {
return null;
}
if (line.includes('svelte/src/internal')) {
continue;
}
new_lines.push(line);
}
if (new_lines.length === 1) {
return null;
}
define_property(error, 'stack', {
value: new_lines.join('\n')
});
define_property(error, 'name', {
// 'Error' suffix is required for stack traces to be rendered properly
value: `${label}Error`
});
return /** @type {Error & { stack: string }} */ (error);
}
/**
* @param {Value} source
* @param {string} label
*/
export function tag(source, label) {
source.label = label;
tag_proxy(source.v, label);
return source;
}
/**
* @param {unknown} value
* @param {string} label
*/
export function tag_proxy(value, label) {
// @ts-expect-error
value?.[PROXY_PATH_SYMBOL]?.(label);
return value;
}
/**
* @param {unknown} value
*/
export function label(value) {
if (typeof value === 'symbol') return `Symbol(${value.description})`;
if (typeof value === 'function') return '<function>';
if (typeof value === 'object' && value) return '<object>';
return String(value);
}