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: add $inspect.trace rune #14290

Merged
merged 59 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
8e320fe
feat: add $trace rune
trueadm Nov 12, 2024
863ee8f
lint
trueadm Nov 13, 2024
3084911
fix
trueadm Nov 13, 2024
0c584a0
fix
trueadm Nov 13, 2024
1e8fe64
fix
trueadm Nov 13, 2024
b7f485d
fix
trueadm Nov 13, 2024
0983978
fix
trueadm Nov 13, 2024
7322128
fix
trueadm Nov 13, 2024
0c8575f
fix
trueadm Nov 13, 2024
3470d4d
more tweaks
trueadm Nov 13, 2024
602a919
lint
trueadm Nov 13, 2024
e35334c
improve label for derived cached
trueadm Nov 13, 2024
4735d64
improve label for derived cached
trueadm Nov 13, 2024
e88150f
lint
trueadm Nov 13, 2024
87d5bc2
better stacks
trueadm Nov 13, 2024
554cd74
complete redesign
trueadm Nov 14, 2024
e8997c3
fixes
trueadm Nov 14, 2024
6ac0779
dead code
trueadm Nov 14, 2024
ffab346
dead code
trueadm Nov 14, 2024
0c97306
improve change detection
trueadm Nov 14, 2024
f6ebd00
rename rune
trueadm Nov 14, 2024
66482c4
lint
trueadm Nov 14, 2024
9589f53
lint
trueadm Nov 14, 2024
8e33cd9
Merge branch 'main' into trace-rune
trueadm Nov 16, 2024
dad777a
fix bug
trueadm Nov 16, 2024
3393409
tweaks
Rich-Harris Nov 21, 2024
55b7549
Update packages/svelte/src/internal/client/dev/tracing.js
trueadm Nov 21, 2024
8c103b2
Update packages/svelte/src/internal/client/dev/tracing.js
trueadm Nov 21, 2024
3da9dac
Update packages/svelte/src/internal/client/dev/tracing.js
trueadm Nov 21, 2024
a078e25
Update packages/svelte/src/internal/client/dev/tracing.js
trueadm Nov 21, 2024
82e6cb9
add labels, remove grouping
Rich-Harris Nov 21, 2024
4d1bd54
conflicts
trueadm Dec 5, 2024
f8eee24
todos
trueadm Dec 5, 2024
74ceb6d
add test + some docs
trueadm Dec 5, 2024
c3428fc
changeset
trueadm Dec 5, 2024
e69c992
merge main
Rich-Harris Dec 14, 2024
9b69a3b
update messages
Rich-Harris Dec 14, 2024
1cc6bc8
address feedback
trueadm Dec 14, 2024
fa941f7
address feedback
trueadm Dec 14, 2024
c02bab8
limit to first statement of function
trueadm Dec 14, 2024
8461ca9
remove unreachable trace_rune_duplicate error
Rich-Harris Dec 14, 2024
464b22b
tweak message
Rich-Harris Dec 14, 2024
58ee498
remove the expression statement, not the expression
Rich-Harris Dec 14, 2024
457619f
revert
Rich-Harris Dec 15, 2024
7778e99
make label optional
Rich-Harris Dec 15, 2024
28e05ed
relax restriction on label - no longer necessary with new design
Rich-Harris Dec 15, 2024
b423b5c
update errors
Rich-Harris Dec 15, 2024
fd49190
newline
Rich-Harris Dec 15, 2024
d42cade
tweak
Rich-Harris Dec 15, 2024
3900db0
add some docs
Rich-Harris Dec 15, 2024
9c3db38
fix playground
trueadm Dec 15, 2024
17c223f
fix playground
trueadm Dec 15, 2024
702c704
tweak message when function runs outside an effect
Rich-Harris Dec 15, 2024
77f90e5
unused
Rich-Harris Dec 15, 2024
e6bb46a
tweak
Rich-Harris Dec 15, 2024
d92bd8a
handle async functions
Rich-Harris Dec 15, 2024
4cb3335
fail on generators
Rich-Harris Dec 15, 2024
86eab1c
regenerate, update docs
Rich-Harris Dec 15, 2024
ae3d8a1
better labelling
Rich-Harris Dec 15, 2024
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
4 changes: 4 additions & 0 deletions packages/svelte/src/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,10 @@ declare function $inspect<T extends any[]>(
...values: T
): { with: (fn: (type: 'init' | 'update', ...values: T) => void) => void };

declare namespace $inspect {
export function trace(name: string): void;
}

/**
* Retrieves the `this` reference of the custom element that contains this component. Example:
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { get_rune } from '../../scope.js';
import * as e from '../../../errors.js';
import { get_parent, unwrap_optional } from '../../../utils/ast.js';
import { is_pure, is_safe_identifier } from './shared/utils.js';
import { dev } from '../../../state.js';

/**
* @param {CallExpression} node
Expand Down Expand Up @@ -135,6 +136,28 @@ export function CallExpression(node, context) {

break;

case '$inspect.trace':
if (node.arguments.length !== 1) {
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
}
if (node.arguments[0].type !== 'Literal' || typeof node.arguments[0].value !== 'string') {
throw new Error('TODO: $track requires a string argument');
trueadm marked this conversation as resolved.
Show resolved Hide resolved
}
if (parent.type !== 'ExpressionStatement' || context.path.at(-2)?.type !== 'BlockStatement') {
throw new Error('TODO: $track must be inside a block statement');
trueadm marked this conversation as resolved.
Show resolved Hide resolved
}

if (context.state.scope.tracing) {
throw new Error('TODO: $track must only be used once within the same block statement');
trueadm marked this conversation as resolved.
Show resolved Hide resolved
}

if (dev) {
// TODO should we validate if tracing is already enabled in this or a parent scope?
trueadm marked this conversation as resolved.
Show resolved Hide resolved
context.state.scope.tracing = node.arguments[0].value;
}

break;

case '$state.snapshot':
if (node.arguments.length !== 1) {
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
/** @import { BlockStatement } from 'estree' */
/** @import { BlockStatement, Statement } from 'estree' */
/** @import { ComponentContext } from '../types' */
import { add_state_transformers } from './shared/declarations.js';
import * as b from '../../../../utils/builders.js';

/**
* @param {BlockStatement} node
* @param {ComponentContext} context
*/
export function BlockStatement(node, context) {
add_state_transformers(context);
const tracing = context.state.scope.tracing;

if (tracing !== null) {
return b.block([
b.return(
b.call(
'$.trace',
b.thunk(b.block(node.body.map((n) => /** @type {Statement} */ (context.visit(n))))),
b.literal(tracing)
)
)
]);
}

context.next();
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export function CallExpression(node, context) {
case '$inspect':
case '$inspect().with':
return transform_inspect_rune(node, context);

case '$inspect.trace':
return b.empty;
}

if (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @import { MemberExpression } from 'estree' */
/** @import { MemberExpression, Expression, Super, PrivateIdentifier } from 'estree' */
/** @import { Context } from '../types' */
import * as b from '../../../../utils/builders.js';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function VariableDeclaration(node, context) {
rune === '$effect.tracking' ||
rune === '$effect.root' ||
rune === '$inspect' ||
rune === '$inspect.trace' ||
rune === '$state.snapshot' ||
rune === '$host'
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,9 @@ export function CallExpression(node, context) {
return transform_inspect_rune(node, context);
}

if (rune === '$inspect.trace') {
return b.empty;
}

context.next();
}
6 changes: 6 additions & 0 deletions packages/svelte/src/compiler/phases/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export class Scope {
*/
function_depth = 0;

/**
* If tracing of reactive dependencies is enabled for this scope
* @type {null | string}
*/
tracing = null;

/**
*
* @param {ScopeRoot} root
Expand Down
172 changes: 172 additions & 0 deletions packages/svelte/src/internal/client/dev/tracing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/** @import { Derived, Reaction, Signal, Value } from '#client' */
import { UNINITIALIZED } from '../../../constants.js';
import { snapshot } from '../../shared/clone.js';
import { define_property } from '../../shared/utils.js';
import { DERIVED, STATE_SYMBOL } from '../constants.js';
import { active_reaction, captured_signals, set_captured_signals, untrack } from '../runtime.js';

/** @type { any } */
export let tracing_expressions = null;

/**
* @param { Value } signal
* @param { { read: Error[] } } [entry]
*/
function log_entry(signal, entry) {
const debug = signal.debug;
const value = signal.v;

if (value === UNINITIALIZED) {
return;
}

if (debug) {
var previous_captured_signals = captured_signals;
var captured = new Set();
set_captured_signals(captured);
try {
untrack(() => {
debug();
});
} finally {
set_captured_signals(previous_captured_signals);
}
if (captured.size > 0) {
for (const dep of captured) {
log_entry(dep);
}
return;
}
}

const type = (signal.f & DERIVED) !== 0 ? '$derived' : '$state';
const current_reaction = /** @type {Reaction} */ (active_reaction);
const status =
signal.version > current_reaction.version || current_reaction.version === 0 ? 'dirty' : 'clean';

// eslint-disable-next-line no-console
console.groupCollapsed(
`%c${type}`,
status !== 'clean'
? 'color: CornflowerBlue; font-weight: bold'
: 'color: grey; font-weight: bold',
typeof value === 'object' && 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 (signal.updated) {
// eslint-disable-next-line no-console
console.log(signal.updated);
}

const read = entry?.read;

if (read && read.length > 0) {
for (var stack of read) {
// eslint-disable-next-line no-console
console.log(stack);
}
}

// eslint-disable-next-line no-console
console.groupEnd();
}

/**
* @template T
* @param {() => T} fn
* @param {string} label
*/
export function trace(fn, label) {
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);

if (tracing_expressions.entries.size === 0) {
// eslint-disable-next-line no-console
console.log(`${label} %cno reactive dependencies (${time}ms)`, 'color: grey');
} else {
// eslint-disable-next-line no-console
console.group(`${label} %c(${time}ms)`, 'color: grey');

var entries = tracing_expressions.entries;

tracing_expressions = null;

for (const [signal, entry] of entries) {
log_entry(signal, entry);
}
// eslint-disable-next-line no-console
console.groupEnd();
}

if (previously_tracing_expressions !== null) {
for (const [signal, entry] of tracing_expressions.entries) {
var prev_entry = previously_tracing_expressions.get(signal);

if (prev_entry === undefined) {
previously_tracing_expressions.set(signal, entry);
} else {
prev_entry.read.push(...entry.read);
}
}
}

return value;
} finally {
tracing_expressions = previously_tracing_expressions;
}
}

/**
* @param {string} label
*/
export function get_stack(label) {
let error = Error();
const stack = error.stack;

if (stack) {
const lines = stack.split('\n');
const new_lines = ['\n'];

for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('validate_each_keys')) {
return null;
}
if (line.includes('svelte/src/internal') || !line.includes('.svelte')) {
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 error;
}
Loading
Loading