From 8e320fe17860ae464e8afd8c20f7cd35eb4ac89f Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 12 Nov 2024 12:54:25 +0000 Subject: [PATCH 01/55] feat: add $trace rune WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP --- packages/svelte/src/ambient.d.ts | 2 + .../2-analyze/visitors/CallExpression.js | 23 ++ .../3-transform/client/transform-client.js | 14 +- .../phases/3-transform/client/types.d.ts | 4 + .../phases/3-transform/client/utils.js | 47 +++++ .../client/visitors/BlockStatement.js | 26 ++- .../client/visitors/CallExpression.js | 16 ++ .../3-transform/client/visitors/Identifier.js | 16 +- .../client/visitors/MemberExpression.js | 31 ++- .../client/visitors/NewExpression.js | 24 +++ .../client/visitors/VariableDeclaration.js | 3 +- .../src/compiler/phases/3-transform/index.js | 4 +- .../server/visitors/CallExpression.js | 4 + packages/svelte/src/compiler/phases/scope.js | 6 + .../svelte/src/internal/client/dev/tracing.js | 197 ++++++++++++++++++ packages/svelte/src/internal/client/index.js | 1 + .../src/internal/client/reactivity/sources.js | 14 +- .../src/internal/client/reactivity/types.d.ts | 2 + .../svelte/src/internal/client/runtime.js | 27 ++- packages/svelte/src/utils.js | 3 +- packages/svelte/types/index.d.ts | 2 + playgrounds/sandbox/run.js | 12 +- 22 files changed, 457 insertions(+), 21 deletions(-) create mode 100644 packages/svelte/src/compiler/phases/3-transform/client/visitors/NewExpression.js create mode 100644 packages/svelte/src/internal/client/dev/tracing.js diff --git a/packages/svelte/src/ambient.d.ts b/packages/svelte/src/ambient.d.ts index 8a20a884ff1d..9d37d9a3f785 100644 --- a/packages/svelte/src/ambient.d.ts +++ b/packages/svelte/src/ambient.d.ts @@ -399,3 +399,5 @@ declare function $inspect( * https://svelte.dev/docs/svelte/$host */ declare function $host(): El; + +declare function $trace(name: string): void; diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 957b27ae9bc8..27e9964d93e8 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -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 @@ -135,6 +136,28 @@ export function CallExpression(node, context) { break; + case '$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'); + } + if (parent.type !== 'ExpressionStatement' || context.path.at(-2)?.type !== 'BlockStatement') { + throw new Error('TODO: $track must be inside a block statement'); + } + + if (context.state.scope.tracing) { + throw new Error('TODO: $track must only be used once within the same block statement'); + } + + if (dev) { + // TODO should we validate if tracing is already enabled in this or a parent scope? + 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'); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 5349f6025533..809ae0851aea 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -17,6 +17,7 @@ import { BindDirective } from './visitors/BindDirective.js'; import { BlockStatement } from './visitors/BlockStatement.js'; import { BreakStatement } from './visitors/BreakStatement.js'; import { CallExpression } from './visitors/CallExpression.js'; +import { NewExpression } from './visitors/NewExpression.js'; import { ClassBody } from './visitors/ClassBody.js'; import { Comment } from './visitors/Comment.js'; import { Component } from './visitors/Component.js'; @@ -91,6 +92,7 @@ const visitors = { BlockStatement, BreakStatement, CallExpression, + NewExpression, ClassBody, Comment, Component, @@ -134,14 +136,16 @@ const visitors = { /** * @param {ComponentAnalysis} analysis + * @param {string} source * @param {ValidatedCompileOptions} options * @returns {ESTree.Program} */ -export function client_component(analysis, options) { +export function client_component(analysis, source, options) { /** @type {ComponentClientTransformState} */ const state = { analysis, options, + source: source.split('\n'), scope: analysis.module.scope, scopes: analysis.module.scopes, is_instance: false, @@ -163,6 +167,7 @@ export function client_component(analysis, options) { private_state: new Map(), transform: {}, in_constructor: false, + trace_dependencies: false, // these are set inside the `Fragment` visitor, and cannot be used until then before_init: /** @type {any} */ (null), @@ -643,20 +648,23 @@ export function client_component(analysis, options) { /** * @param {Analysis} analysis + * @param {string} source * @param {ValidatedModuleCompileOptions} options * @returns {ESTree.Program} */ -export function client_module(analysis, options) { +export function client_module(analysis, source, options) { /** @type {ClientTransformState} */ const state = { analysis, options, + source: source.split('\n'), scope: analysis.module.scope, scopes: analysis.module.scopes, public_state: new Map(), private_state: new Map(), transform: {}, - in_constructor: false + in_constructor: false, + trace_dependencies: false }; const module = /** @type {ESTree.Program} */ ( diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts index 0b54de538419..37beea6b54aa 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts @@ -23,6 +23,10 @@ export interface ClientTransformState extends TransformState { */ readonly in_constructor: boolean; + readonly source: string[]; + + readonly trace_dependencies: boolean; + readonly transform: Record< string, { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index c46090597709..60e939373b4c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -351,3 +351,50 @@ export function is_inlinable_expression(node_or_nodes, state) { } return has_expression_tag; } + +/** + * @param {Expression} node + * @param {Expression} expression + * @param {ClientTransformState} state + */ +export function trace(node, expression, state) { + const loc = node.loc; + const source = state.source; + let code = ''; + + if (loc) { + const start = loc.start; + const end = loc.end; + + if (start.line === end.line) { + code = source[start.line - 1].slice(start.column, end.column); + } else { + for (let i = start.line; i < end.line + 1; i++) { + const loc = source[i - 1]; + + if (i === start.line) { + code += loc.slice(start.column) + '\n'; + } else if (i === end.line) { + code += loc.slice(0, end.column); + } else { + code += loc + '\n'; + } + } + } + } else if (node.start !== undefined && node.end !== undefined) { + code = source.join('\n').slice(node.start, node.end); + } else { + return expression; + } + + return b.call( + '$.trace', + b.thunk(expression), + b.literal(code), + node.type === 'CallExpression' || + node.type === 'MemberExpression' || + node.type === 'NewExpression' + ? b.literal(true) + : undefined + ); +} diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js index 502fbd471e6a..e5bbe4cde59a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js @@ -1,6 +1,7 @@ -/** @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 @@ -8,5 +9,28 @@ import { add_state_transformers } from './shared/declarations.js'; */ 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( + '$.log_trace', + b.thunk( + b.block( + node.body.map( + (n) => + /** @type {Statement} */ ( + context.visit(n, { ...context.state, trace_dependencies: true }) + ) + ) + ) + ), + b.literal(tracing) + ) + ) + ]); + } + context.next(); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js index 7a3057451aa1..1e90c69a99c8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js @@ -4,6 +4,7 @@ import { dev, is_ignored } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; import { get_rune } from '../../../scope.js'; import { transform_inspect_rune } from '../../utils.js'; +import { trace } from '../utils.js'; /** * @param {CallExpression} node @@ -33,6 +34,9 @@ export function CallExpression(node, context) { case '$inspect': case '$inspect().with': return transform_inspect_rune(node, context); + + case '$trace': + return b.empty; } if ( @@ -58,5 +62,17 @@ export function CallExpression(node, context) { ); } + if (dev) { + return trace( + node, + { + ...node, + callee: /** @type {Expression} */ (context.visit(node.callee)), + arguments: node.arguments.map((arg) => /** @type {Expression} */ (context.visit(arg))) + }, + context.state + ); + } + context.next(); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js index ae62909eff8a..53ae7a21fff3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js @@ -2,7 +2,8 @@ /** @import { Context } from '../types' */ import is_reference from 'is-reference'; import * as b from '../../../../utils/builders.js'; -import { build_getter } from '../utils.js'; +import { build_getter, trace } from '../utils.js'; +import { dev } from '../../../../state.js'; /** * @param {Identifier} node @@ -10,6 +11,7 @@ import { build_getter } from '../utils.js'; */ export function Identifier(node, context) { const parent = /** @type {Node} */ (context.path.at(-1)); + let transformed; if (is_reference(node, parent)) { if (node.name === '$$props') { @@ -32,10 +34,18 @@ export function Identifier(node, context) { grand_parent?.type !== 'AssignmentExpression' && grand_parent?.type !== 'UpdateExpression' ) { - return b.id('$$props'); + transformed = b.id('$$props'); } } - return build_getter(node, context.state); + if (!transformed) { + transformed = build_getter(node, context.state); + } + } + + if (transformed && transformed !== node && dev) { + return trace(node, transformed, context.state); } + + return transformed; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js index 501ecda5557d..ebfe61424bc6 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js @@ -1,19 +1,46 @@ -/** @import { MemberExpression } from 'estree' */ +/** @import { MemberExpression, Expression, Super, PrivateIdentifier } from 'estree' */ /** @import { Context } from '../types' */ +import { dev } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; +import { trace } from '../utils.js'; /** * @param {MemberExpression} node * @param {Context} context */ export function MemberExpression(node, context) { + let transformed; // rewrite `this.#foo` as `this.#foo.v` inside a constructor if (node.property.type === 'PrivateIdentifier') { const field = context.state.private_state.get(node.property.name); if (field) { - return context.state.in_constructor ? b.member(node, 'v') : b.call('$.get', node); + transformed = context.state.in_constructor ? b.member(node, 'v') : b.call('$.get', node); } } + const parent = context.path.at(-1); + + if ( + dev && + // Bail out of tracing members if they're used as calees to avoid context issues + (parent?.type !== 'CallExpression' || parent.callee !== node) && + parent?.type !== 'BindDirective' && + parent?.type !== 'AssignmentExpression' && + parent?.type !== 'UpdateExpression' && + parent?.type !== 'Component' + ) { + return trace( + node, + transformed || { + ...node, + object: /** @type {Expression | Super} */ (context.visit(node.object)), + property: /** @type {Expression | PrivateIdentifier} */ (context.visit(node.property)) + }, + context.state + ); + } else if (transformed) { + return transformed; + } + context.next(); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/NewExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/NewExpression.js new file mode 100644 index 000000000000..1a3ad56488b3 --- /dev/null +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/NewExpression.js @@ -0,0 +1,24 @@ +/** @import { NewExpression, Expression } from 'estree' */ +/** @import { Context } from '../types' */ +import { dev } from '../../../../state.js'; +import { trace } from '../utils.js'; + +/** + * @param {NewExpression} node + * @param {Context} context + */ +export function NewExpression(node, context) { + if (dev) { + return trace( + node, + { + ...node, + callee: /** @type {Expression} */ (context.visit(node.callee)), + arguments: node.arguments.map((arg) => /** @type {Expression} */ (context.visit(arg))) + }, + context.state + ); + } + + context.next(); +} diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js index 04685b66bd0c..d04507372901 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js @@ -28,7 +28,8 @@ export function VariableDeclaration(node, context) { rune === '$effect.root' || rune === '$inspect' || rune === '$state.snapshot' || - rune === '$host' + rune === '$host' || + rune === '$trace' ) { if (init != null && is_hoisted_function(init)) { context.state.hoisted.push( diff --git a/packages/svelte/src/compiler/phases/3-transform/index.js b/packages/svelte/src/compiler/phases/3-transform/index.js index 8f6597ee1298..b1a59e6f1502 100644 --- a/packages/svelte/src/compiler/phases/3-transform/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/index.js @@ -30,7 +30,7 @@ export function transform_component(analysis, source, options) { const program = options.generate === 'server' ? server_component(analysis, options) - : client_component(analysis, options); + : client_component(analysis, source, options); const js_source_name = get_source_name(options.filename, options.outputFilename, 'input.svelte'); const js = print(program, { @@ -79,7 +79,7 @@ export function transform_module(analysis, source, options) { const program = options.generate === 'server' ? server_module(analysis, options) - : client_module(analysis, options); + : client_module(analysis, source, options); const basename = options.filename.split(/[/\\]/).at(-1); if (program.body.length > 0) { diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js index 386c6b6ff393..308aec56254b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js @@ -37,5 +37,9 @@ export function CallExpression(node, context) { return transform_inspect_rune(node, context); } + if (rune === '$trace') { + return b.empty; + } + context.next(); } diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 0542a20708e9..cf999675d00b 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -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 diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js new file mode 100644 index 000000000000..beccfae54aec --- /dev/null +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -0,0 +1,197 @@ +import { snapshot } from '../../shared/clone.js'; +import { define_property } from '../../shared/utils.js'; +import { STATE_SYMBOL } from '../constants.js'; +import { captured_signals, set_captured_signals } from '../runtime.js'; + +export const NOT_REACTIVE = 0; +export const REACTIVE_UNCHANGED = 1; +export const REACTIVE_CHANGED = 2; + +/** @type { { changed: boolean, label: string, time: number, sub: any, stacks: any[], value: any }[] | null } */ +export let tracing_expressions = null; +/** @type { 0 | 1 | 2 } */ +export let tracing_expression_reactive = NOT_REACTIVE; + +/** + * @param {any} expressions + */ +function log_expressions(expressions) { + for (let expression of expressions) { + const val = expression.value; + const label = expression.label; + const time = expression.time; + const changed = expression.changed; + + if (time) { + // eslint-disable-next-line no-console + console.groupCollapsed( + `%c${label} %c(${time.toFixed(2)}ms)`, + changed ? 'color: CornflowerBlue; font-weight: bold' : 'color: grey; font-weight: bold', + 'color: grey', + val && typeof val === 'object' && STATE_SYMBOL in val ? snapshot(val, true) : val + ); + } else { + // eslint-disable-next-line no-console + console.groupCollapsed( + `%c${label}`, + changed ? 'color: CornflowerBlue; font-weight: bold' : 'color: grey; font-weight: bold', + val && typeof val === 'object' && STATE_SYMBOL in val ? snapshot(val, true) : val + ); + } + + if (expression.sub) { + log_expressions(expression.sub); + } + + for (var [name, stack] of expression.stacks) { + // eslint-disable-next-line no-console + console.groupCollapsed('%c' + name + ' stack', 'color: white; font-weight: normal;'); + // eslint-disable-next-line no-console + console.log(stack); + // eslint-disable-next-line no-console + console.groupEnd(); + } + + // eslint-disable-next-line no-console + console.groupEnd(); + } +} + +/** + * @template T + * @param {() => T} fn + * @param {string} label + */ +export function log_trace(fn, label) { + var previously_tracing_expressions = tracing_expressions; + try { + tracing_expressions = []; + + var start = performance.now(); + var value = fn(); + var time = (performance.now() - start).toFixed(2); + if (tracing_expressions.length > 0) { + // eslint-disable-next-line no-console + console.group(`${label} %c(${time}ms)`, 'color: grey'); + log_expressions(tracing_expressions); + // eslint-disable-next-line no-console + console.groupEnd(); + } else { + // eslint-disable-next-line no-console + console.log(`${label} %cno reactive dependencies (${time}ms)`, 'color: grey'); + } + + if (previously_tracing_expressions !== null) { + previously_tracing_expressions.push(...tracing_expressions); + } + + return value; + } finally { + tracing_expressions = previously_tracing_expressions; + } +} + +/** + * @template T + * @param {() => T} fn + * @param {boolean} [computed] + * @param {string} label + */ +export function trace(fn, label, computed) { + // If we aren't capturing the trace, just return the value + if (tracing_expressions === null) { + return fn(); + } + var previously_tracing_expressions = tracing_expressions; + var previously_tracing_expression_reactive = tracing_expression_reactive; + var previous_captured_signals = captured_signals; + var signals = new Set(); + set_captured_signals(signals); + + try { + tracing_expression_reactive = NOT_REACTIVE; + tracing_expressions = []; + var value, + time = 0; + + if (computed) { + var start = performance.now(); + value = fn(); + time = performance.now() - start; + } else { + value = fn(); + } + + if (tracing_expressions !== null) { + var read_stack = ['read', get_stack()]; + + if (tracing_expression_reactive !== NOT_REACTIVE) { + var write_stack; + if (signals.size === 1) { + write_stack = Array.from(signals)[0].stack; + } + tracing_expressions.push({ + changed: tracing_expression_reactive === REACTIVE_CHANGED, + label, + value, + time, + stacks: [read_stack, write_stack], + sub: null + }); + + if (previously_tracing_expressions !== null) { + previously_tracing_expressions.push(...tracing_expressions); + } + } else if (tracing_expressions.length !== 0) { + previously_tracing_expressions.push({ + changed: tracing_expressions.some((e) => e.changed), + label, + value, + time, + stacks: [read_stack], + sub: tracing_expressions + }); + } + } + + return value; + } finally { + tracing_expressions = previously_tracing_expressions; + tracing_expression_reactive = previously_tracing_expression_reactive; + set_captured_signals(previous_captured_signals); + } +} + +/** + * @param {0 | 1 | 2} value + */ +export function set_tracing_expression_reactive(value) { + tracing_expression_reactive = value; +} + +export function get_stack() { + let error = Error() + const stack = error.stack; + + if (stack) { + const lines = stack.split('\n'); + const new_lines = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.includes('svelte/src/internal')) { + continue; + } + new_lines.push(line); + } + + define_property(error, 'stack', { + value: new_lines.join('\n') + }); + + define_property(error, 'name', { + value: 'TraceInvokedError' + }); + } + return error; +} diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index c401867a0f0b..b300fd31ff6c 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -11,6 +11,7 @@ export { skip_ownership_validation } from './dev/ownership.js'; export { check_target, legacy_api } from './dev/legacy.js'; +export { log_trace, trace } from './dev/tracing.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'; diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 4bbd470d08c8..333ea4fcebb3 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -33,6 +33,7 @@ import { } from '../constants.js'; import * as e from '../errors.js'; import { legacy_mode_flag } from '../../flags/index.js'; +import { get_stack } from '../dev/tracing.js'; export let inspect_effects = new Set(); @@ -49,13 +50,20 @@ export function set_inspect_effects(v) { * @returns {Source} */ export function source(v) { - return { + /** @type {Value} */ + var signal = { f: 0, // TODO ideally we could skip this altogether, but it causes type errors v, reactions: null, equals, version: 0 }; + + if (DEV) { + signal.stack = ['created', get_stack()]; + } + + return signal; } /** @@ -160,6 +168,10 @@ export function internal_set(source, value) { source.v = value; source.version = increment_version(); + if (DEV) { + source.stack = ['updated', get_stack()]; + } + mark_reactions(source, DIRTY); // If the current signal is running for the first time, it won't have any diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 2cef49eb2d80..71cf8952c369 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -14,6 +14,8 @@ export interface Value extends Signal { equals: Equals; /** The latest value for this signal */ v: V; + /** Dev only */ + stack?: [string, Error]; } export interface Reaction extends Signal { diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 5b5b488824e6..a03de203d6a6 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -34,6 +34,13 @@ import * as e from './errors.js'; import { lifecycle_outside_component } from '../shared/errors.js'; import { FILENAME } from '../../constants.js'; import { legacy_mode_flag } from '../flags/index.js'; +import { + tracing_expressions, + set_tracing_expression_reactive, + REACTIVE_UNCHANGED, + REACTIVE_CHANGED, + tracing_expression_reactive +} from './dev/tracing.js'; const FLUSH_MICROTASK = 0; const FLUSH_SYNC = 1; @@ -131,6 +138,11 @@ export let skip_reaction = false; /** @type {Set | null} */ export let captured_signals = null; +/** @param {Set | null} value */ +export function set_captured_signals(value) { + captured_signals = value; +} + // Handling runtime component context /** @type {ComponentContext | null} */ export let component_context = null; @@ -283,7 +295,7 @@ function handle_error(error, effect, component_context) { new_lines.push(line); } define_property(error, 'stack', { - value: error.stack + new_lines.join('\n') + value: new_lines.join('\n') }); } @@ -781,6 +793,19 @@ export function get(signal) { } } + if ( + DEV && + active_reaction !== null && + tracing_expressions !== null && + tracing_expression_reactive !== REACTIVE_UNCHANGED + ) { + set_tracing_expression_reactive( + signal.version > active_reaction.version || active_reaction.version === current_version + ? REACTIVE_CHANGED + : REACTIVE_UNCHANGED + ); + } + return signal.v; } diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index 60ec364e6fdd..d3c6bcb6f167 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -414,7 +414,8 @@ const RUNES = /** @type {const} */ ([ '$effect.root', '$inspect', '$inspect().with', - '$host' + '$host', + '$trace' ]); /** diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 00ba2556d9a1..0e29e3e7258b 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2707,4 +2707,6 @@ declare function $inspect( */ declare function $host(): El; +declare function $trace(name: string): void; + //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/playgrounds/sandbox/run.js b/playgrounds/sandbox/run.js index 4531e7182da8..975613b5f454 100644 --- a/playgrounds/sandbox/run.js +++ b/playgrounds/sandbox/run.js @@ -48,12 +48,12 @@ for (const generate of /** @type {const} */ (['client', 'server'])) { fs.writeFileSync(`${cwd}/output/${file}.json`, JSON.stringify(ast, null, '\t')); - try { - const migrated = migrate(source); - fs.writeFileSync(`${cwd}/output/${file}.migrated.svelte`, migrated.code); - } catch (e) { - console.warn(`Error migrating ${file}`, e); - } + // try { + // const migrated = migrate(source); + // fs.writeFileSync(`${cwd}/output/${file}.migrated.svelte`, migrated.code); + // } catch (e) { + // console.warn(`Error migrating ${file}`, e); + // } } const compiled = compile(source, { From 863ee8f9ebad319c09685a9485663ab7f55f301e Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 15:19:38 +0000 Subject: [PATCH 02/55] lint --- packages/svelte/src/internal/client/dev/tracing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index beccfae54aec..9f32e292054a 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -170,7 +170,7 @@ export function set_tracing_expression_reactive(value) { } export function get_stack() { - let error = Error() + let error = Error(); const stack = error.stack; if (stack) { From 3084911592dedf799686fae9f49a01f25347a999 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 15:26:39 +0000 Subject: [PATCH 03/55] fix --- .../phases/3-transform/client/visitors/MemberExpression.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js index ebfe61424bc6..45a988bfd51c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js @@ -27,7 +27,8 @@ export function MemberExpression(node, context) { parent?.type !== 'BindDirective' && parent?.type !== 'AssignmentExpression' && parent?.type !== 'UpdateExpression' && - parent?.type !== 'Component' + parent?.type !== 'Component' && + parent?.type !== 'ChainExpression' ) { return trace( node, From 0c584a0320873a9b501f3ec933936d733a451209 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 15:33:34 +0000 Subject: [PATCH 04/55] fix --- .../phases/3-transform/client/visitors/MemberExpression.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js index 45a988bfd51c..dcabc2063005 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js @@ -28,7 +28,7 @@ export function MemberExpression(node, context) { parent?.type !== 'AssignmentExpression' && parent?.type !== 'UpdateExpression' && parent?.type !== 'Component' && - parent?.type !== 'ChainExpression' + (parent?.type !== 'MemberExpression' || !parent?.optional) ) { return trace( node, From 1e8fe64e1788b15632d9655435478a87c2ca8e3f Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 16:14:49 +0000 Subject: [PATCH 05/55] fix --- .../phases/3-transform/client/visitors/MemberExpression.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js index dcabc2063005..0227da874551 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js @@ -28,7 +28,7 @@ export function MemberExpression(node, context) { parent?.type !== 'AssignmentExpression' && parent?.type !== 'UpdateExpression' && parent?.type !== 'Component' && - (parent?.type !== 'MemberExpression' || !parent?.optional) + !node.optional ) { return trace( node, From b7f485d96faf805e42f138cd05a9e2892c734d49 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 16:16:58 +0000 Subject: [PATCH 06/55] fix --- .../phases/3-transform/client/visitors/MemberExpression.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js index 0227da874551..2293accb20f1 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js @@ -28,6 +28,7 @@ export function MemberExpression(node, context) { parent?.type !== 'AssignmentExpression' && parent?.type !== 'UpdateExpression' && parent?.type !== 'Component' && + parent?.type !== 'AwaitExpression' && !node.optional ) { return trace( From 09839789d9819ae2c9f169b37e7d57d7c7ba4e49 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 16:21:31 +0000 Subject: [PATCH 07/55] fix --- .../phases/3-transform/client/visitors/CallExpression.js | 4 +++- .../compiler/phases/3-transform/client/visitors/Identifier.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js index 1e90c69a99c8..b018a941a390 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js @@ -62,7 +62,9 @@ export function CallExpression(node, context) { ); } - if (dev) { + const parent = context.path.at(-1); + + if (dev && parent?.type !== 'AwaitExpression') { return trace( node, { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js index 53ae7a21fff3..73d74f63ba53 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js @@ -43,7 +43,7 @@ export function Identifier(node, context) { } } - if (transformed && transformed !== node && dev) { + if (dev && transformed && transformed !== node && parent.type !== 'AwaitExpression') { return trace(node, transformed, context.state); } From 73221288b0cfadb58a3a90d7231c22162887faec Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 16:26:02 +0000 Subject: [PATCH 08/55] fix --- .../src/compiler/phases/3-transform/client/utils.js | 12 ++++++++++++ .../3-transform/client/visitors/CallExpression.js | 4 +--- .../phases/3-transform/client/visitors/Identifier.js | 2 +- .../3-transform/client/visitors/MemberExpression.js | 1 - 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 60e939373b4c..c292305ee81a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -14,6 +14,7 @@ import { } from '../../../../constants.js'; import { dev } from '../../../state.js'; import { get_value } from './visitors/shared/declarations.js'; +import { walk } from 'zimmerframe'; /** * @param {Binding} binding @@ -361,6 +362,17 @@ export function trace(node, expression, state) { const loc = node.loc; const source = state.source; let code = ''; + let bailout = false; + + walk(expression, null, { + AwaitExpression() { + bailout = true; + } + }) + + if (bailout) { + return expression; + } if (loc) { const start = loc.start; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js index b018a941a390..1e90c69a99c8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js @@ -62,9 +62,7 @@ export function CallExpression(node, context) { ); } - const parent = context.path.at(-1); - - if (dev && parent?.type !== 'AwaitExpression') { + if (dev) { return trace( node, { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js index 73d74f63ba53..53ae7a21fff3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js @@ -43,7 +43,7 @@ export function Identifier(node, context) { } } - if (dev && transformed && transformed !== node && parent.type !== 'AwaitExpression') { + if (transformed && transformed !== node && dev) { return trace(node, transformed, context.state); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js index 2293accb20f1..0227da874551 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js @@ -28,7 +28,6 @@ export function MemberExpression(node, context) { parent?.type !== 'AssignmentExpression' && parent?.type !== 'UpdateExpression' && parent?.type !== 'Component' && - parent?.type !== 'AwaitExpression' && !node.optional ) { return trace( From 0c8575f7cbc3e18131c0d8cbd86c284fae92dc43 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 16:52:01 +0000 Subject: [PATCH 09/55] fix --- packages/svelte/src/internal/client/dev/tracing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 9f32e292054a..5fd73a69774e 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -135,7 +135,7 @@ export function trace(fn, label, computed) { label, value, time, - stacks: [read_stack, write_stack], + stacks: write_stack ? [read_stack, write_stack] : [read_stack], sub: null }); From 3470d4dc228d72171ffb071ddd9b056df66de54b Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 18:02:23 +0000 Subject: [PATCH 10/55] more tweaks --- packages/svelte/src/internal/client/dev/tracing.js | 13 +++++++++---- .../src/internal/client/reactivity/deriveds.js | 5 +++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 5fd73a69774e..69ab39e5a85e 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -126,17 +126,22 @@ export function trace(fn, label, computed) { var read_stack = ['read', get_stack()]; if (tracing_expression_reactive !== NOT_REACTIVE) { - var write_stack; + var set_stack; if (signals.size === 1) { - write_stack = Array.from(signals)[0].stack; + set_stack = Array.from(signals)[0].stack; + } + var sub = null; + if (tracing_expressions.length !== 0) { + sub = tracing_expressions.slice(); + tracing_expressions = []; } tracing_expressions.push({ changed: tracing_expression_reactive === REACTIVE_CHANGED, label, value, time, - stacks: write_stack ? [read_stack, write_stack] : [read_stack], - sub: null + stacks: set_stack ? [read_stack, set_stack] : [read_stack], + sub }); if (previously_tracing_expressions !== null) { diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 98fbfb0f5242..ffb1cdaa01ff 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -24,6 +24,7 @@ import { equals, safe_equals } from './equality.js'; import * as e from '../errors.js'; import { destroy_effect } from './effects.js'; import { inspect_effects, set_inspect_effects } from './sources.js'; +import { get_stack } from '../dev/tracing.js'; /** * @template V @@ -56,6 +57,10 @@ export function derived(fn) { parent: active_effect }; + if (DEV) { + signal.stack = ['created', get_stack()]; + } + if (active_reaction !== null && (active_reaction.f & DERIVED) !== 0) { var derived = /** @type {Derived} */ (active_reaction); (derived.children ??= []).push(signal); From 602a91931d86fea02c7ea5f61f0412e8ad58a96a Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 18:09:47 +0000 Subject: [PATCH 11/55] lint --- packages/svelte/src/compiler/phases/3-transform/client/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index c292305ee81a..4b6a2da0f8d5 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -368,7 +368,7 @@ export function trace(node, expression, state) { AwaitExpression() { bailout = true; } - }) + }); if (bailout) { return expression; From e35334cd91e0a4d19a2a6ac2fc99fea83fd38059 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 18:14:17 +0000 Subject: [PATCH 12/55] improve label for derived cached --- packages/svelte/src/internal/client/dev/tracing.js | 11 +++++++---- packages/svelte/src/internal/client/runtime.js | 10 ++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 69ab39e5a85e..efedd377c72b 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -6,10 +6,11 @@ import { captured_signals, set_captured_signals } from '../runtime.js'; export const NOT_REACTIVE = 0; export const REACTIVE_UNCHANGED = 1; export const REACTIVE_CHANGED = 2; +export const REACTIVE_CHANGED_CACHED = 3; /** @type { { changed: boolean, label: string, time: number, sub: any, stacks: any[], value: any }[] | null } */ export let tracing_expressions = null; -/** @type { 0 | 1 | 2 } */ +/** @type { 0 | 1 | 2 | 3 } */ export let tracing_expression_reactive = NOT_REACTIVE; /** @@ -136,8 +137,10 @@ export function trace(fn, label, computed) { tracing_expressions = []; } tracing_expressions.push({ - changed: tracing_expression_reactive === REACTIVE_CHANGED, - label, + changed: + tracing_expression_reactive === REACTIVE_CHANGED || + tracing_expression_reactive === REACTIVE_CHANGED_CACHED, + label: label + (tracing_expression_reactive === REACTIVE_CHANGED_CACHED ? ' [cached derived]' : ''), value, time, stacks: set_stack ? [read_stack, set_stack] : [read_stack], @@ -168,7 +171,7 @@ export function trace(fn, label, computed) { } /** - * @param {0 | 1 | 2} value + * @param {0 | 1 | 2 | 3} value */ export function set_tracing_expression_reactive(value) { tracing_expression_reactive = value; diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index a03de203d6a6..d2296c3270dd 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -39,7 +39,8 @@ import { set_tracing_expression_reactive, REACTIVE_UNCHANGED, REACTIVE_CHANGED, - tracing_expression_reactive + tracing_expression_reactive, + REACTIVE_CHANGED_CACHED } from './dev/tracing.js'; const FLUSH_MICROTASK = 0; @@ -785,10 +786,13 @@ export function get(signal) { } } + var updated = false; + if (is_derived) { derived = /** @type {Derived} */ (signal); if (check_dirtiness(derived)) { + updated = true; update_derived(derived); } } @@ -801,7 +805,9 @@ export function get(signal) { ) { set_tracing_expression_reactive( signal.version > active_reaction.version || active_reaction.version === current_version - ? REACTIVE_CHANGED + ? updated + ? REACTIVE_CHANGED_CACHED + : REACTIVE_CHANGED : REACTIVE_UNCHANGED ); } From 4735d649fa37c443f5de4be344560bf8ece426fa Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 18:18:32 +0000 Subject: [PATCH 13/55] improve label for derived cached --- packages/svelte/src/internal/client/runtime.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index d2296c3270dd..ccd8feaaf320 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -806,8 +806,8 @@ export function get(signal) { set_tracing_expression_reactive( signal.version > active_reaction.version || active_reaction.version === current_version ? updated - ? REACTIVE_CHANGED_CACHED - : REACTIVE_CHANGED + ? REACTIVE_CHANGED + : REACTIVE_CHANGED_CACHED : REACTIVE_UNCHANGED ); } From e88150fcac37e605bd6de284bc46fa6558248a56 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 18:22:59 +0000 Subject: [PATCH 14/55] lint --- packages/svelte/src/internal/client/dev/tracing.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index efedd377c72b..85007a44ec6a 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -140,7 +140,9 @@ export function trace(fn, label, computed) { changed: tracing_expression_reactive === REACTIVE_CHANGED || tracing_expression_reactive === REACTIVE_CHANGED_CACHED, - label: label + (tracing_expression_reactive === REACTIVE_CHANGED_CACHED ? ' [cached derived]' : ''), + label: + label + + (tracing_expression_reactive === REACTIVE_CHANGED_CACHED ? ' [cached derived]' : ''), value, time, stacks: set_stack ? [read_stack, set_stack] : [read_stack], From 87d5bc2dca5ae8fb7eb0249cb15dce0ecef93d72 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Nov 2024 18:24:38 +0000 Subject: [PATCH 15/55] better stacks --- packages/svelte/src/internal/client/dev/tracing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 85007a44ec6a..07c619c9d837 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -189,7 +189,7 @@ export function get_stack() { for (let i = 0; i < lines.length; i++) { const line = lines[i]; - if (line.includes('svelte/src/internal')) { + if (line.includes('svelte/src/internal') || !line.includes('.svelte')) { continue; } new_lines.push(line); From 554cd74f7006965de1ee68554b2e44ab0cdbe146 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Nov 2024 12:37:24 +0000 Subject: [PATCH 16/55] complete redesign --- .../3-transform/client/transform-client.js | 2 - .../phases/3-transform/client/utils.js | 59 ----- .../client/visitors/BlockStatement.js | 2 +- .../client/visitors/CallExpression.js | 13 - .../3-transform/client/visitors/Identifier.js | 16 +- .../client/visitors/MemberExpression.js | 30 +-- .../client/visitors/NewExpression.js | 24 -- .../svelte/src/internal/client/dev/tracing.js | 247 ++++++++---------- .../src/internal/client/dom/blocks/each.js | 55 +++- packages/svelte/src/internal/client/index.js | 2 +- packages/svelte/src/internal/client/proxy.js | 20 +- .../internal/client/reactivity/deriveds.js | 2 +- .../src/internal/client/reactivity/sources.js | 8 +- .../src/internal/client/reactivity/types.d.ts | 4 +- .../svelte/src/internal/client/runtime.js | 41 +-- 15 files changed, 212 insertions(+), 313 deletions(-) delete mode 100644 packages/svelte/src/compiler/phases/3-transform/client/visitors/NewExpression.js diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 809ae0851aea..2b5cdc459c69 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -17,7 +17,6 @@ import { BindDirective } from './visitors/BindDirective.js'; import { BlockStatement } from './visitors/BlockStatement.js'; import { BreakStatement } from './visitors/BreakStatement.js'; import { CallExpression } from './visitors/CallExpression.js'; -import { NewExpression } from './visitors/NewExpression.js'; import { ClassBody } from './visitors/ClassBody.js'; import { Comment } from './visitors/Comment.js'; import { Component } from './visitors/Component.js'; @@ -92,7 +91,6 @@ const visitors = { BlockStatement, BreakStatement, CallExpression, - NewExpression, ClassBody, Comment, Component, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 4b6a2da0f8d5..c46090597709 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -14,7 +14,6 @@ import { } from '../../../../constants.js'; import { dev } from '../../../state.js'; import { get_value } from './visitors/shared/declarations.js'; -import { walk } from 'zimmerframe'; /** * @param {Binding} binding @@ -352,61 +351,3 @@ export function is_inlinable_expression(node_or_nodes, state) { } return has_expression_tag; } - -/** - * @param {Expression} node - * @param {Expression} expression - * @param {ClientTransformState} state - */ -export function trace(node, expression, state) { - const loc = node.loc; - const source = state.source; - let code = ''; - let bailout = false; - - walk(expression, null, { - AwaitExpression() { - bailout = true; - } - }); - - if (bailout) { - return expression; - } - - if (loc) { - const start = loc.start; - const end = loc.end; - - if (start.line === end.line) { - code = source[start.line - 1].slice(start.column, end.column); - } else { - for (let i = start.line; i < end.line + 1; i++) { - const loc = source[i - 1]; - - if (i === start.line) { - code += loc.slice(start.column) + '\n'; - } else if (i === end.line) { - code += loc.slice(0, end.column); - } else { - code += loc + '\n'; - } - } - } - } else if (node.start !== undefined && node.end !== undefined) { - code = source.join('\n').slice(node.start, node.end); - } else { - return expression; - } - - return b.call( - '$.trace', - b.thunk(expression), - b.literal(code), - node.type === 'CallExpression' || - node.type === 'MemberExpression' || - node.type === 'NewExpression' - ? b.literal(true) - : undefined - ); -} diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js index e5bbe4cde59a..d0f130299152 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js @@ -15,7 +15,7 @@ export function BlockStatement(node, context) { return b.block([ b.return( b.call( - '$.log_trace', + '$.trace', b.thunk( b.block( node.body.map( diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js index 1e90c69a99c8..c952f1a61e4d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js @@ -4,7 +4,6 @@ import { dev, is_ignored } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; import { get_rune } from '../../../scope.js'; import { transform_inspect_rune } from '../../utils.js'; -import { trace } from '../utils.js'; /** * @param {CallExpression} node @@ -62,17 +61,5 @@ export function CallExpression(node, context) { ); } - if (dev) { - return trace( - node, - { - ...node, - callee: /** @type {Expression} */ (context.visit(node.callee)), - arguments: node.arguments.map((arg) => /** @type {Expression} */ (context.visit(arg))) - }, - context.state - ); - } - context.next(); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js index 53ae7a21fff3..ae62909eff8a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js @@ -2,8 +2,7 @@ /** @import { Context } from '../types' */ import is_reference from 'is-reference'; import * as b from '../../../../utils/builders.js'; -import { build_getter, trace } from '../utils.js'; -import { dev } from '../../../../state.js'; +import { build_getter } from '../utils.js'; /** * @param {Identifier} node @@ -11,7 +10,6 @@ import { dev } from '../../../../state.js'; */ export function Identifier(node, context) { const parent = /** @type {Node} */ (context.path.at(-1)); - let transformed; if (is_reference(node, parent)) { if (node.name === '$$props') { @@ -34,18 +32,10 @@ export function Identifier(node, context) { grand_parent?.type !== 'AssignmentExpression' && grand_parent?.type !== 'UpdateExpression' ) { - transformed = b.id('$$props'); + return b.id('$$props'); } } - if (!transformed) { - transformed = build_getter(node, context.state); - } - } - - if (transformed && transformed !== node && dev) { - return trace(node, transformed, context.state); + return build_getter(node, context.state); } - - return transformed; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js index 0227da874551..9d0722a4c392 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/MemberExpression.js @@ -1,47 +1,19 @@ /** @import { MemberExpression, Expression, Super, PrivateIdentifier } from 'estree' */ /** @import { Context } from '../types' */ -import { dev } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; -import { trace } from '../utils.js'; /** * @param {MemberExpression} node * @param {Context} context */ export function MemberExpression(node, context) { - let transformed; // rewrite `this.#foo` as `this.#foo.v` inside a constructor if (node.property.type === 'PrivateIdentifier') { const field = context.state.private_state.get(node.property.name); if (field) { - transformed = context.state.in_constructor ? b.member(node, 'v') : b.call('$.get', node); + return context.state.in_constructor ? b.member(node, 'v') : b.call('$.get', node); } } - const parent = context.path.at(-1); - - if ( - dev && - // Bail out of tracing members if they're used as calees to avoid context issues - (parent?.type !== 'CallExpression' || parent.callee !== node) && - parent?.type !== 'BindDirective' && - parent?.type !== 'AssignmentExpression' && - parent?.type !== 'UpdateExpression' && - parent?.type !== 'Component' && - !node.optional - ) { - return trace( - node, - transformed || { - ...node, - object: /** @type {Expression | Super} */ (context.visit(node.object)), - property: /** @type {Expression | PrivateIdentifier} */ (context.visit(node.property)) - }, - context.state - ); - } else if (transformed) { - return transformed; - } - context.next(); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/NewExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/NewExpression.js deleted file mode 100644 index 1a3ad56488b3..000000000000 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/NewExpression.js +++ /dev/null @@ -1,24 +0,0 @@ -/** @import { NewExpression, Expression } from 'estree' */ -/** @import { Context } from '../types' */ -import { dev } from '../../../../state.js'; -import { trace } from '../utils.js'; - -/** - * @param {NewExpression} node - * @param {Context} context - */ -export function NewExpression(node, context) { - if (dev) { - return trace( - node, - { - ...node, - callee: /** @type {Expression} */ (context.visit(node.callee)), - arguments: node.arguments.map((arg) => /** @type {Expression} */ (context.visit(arg))) - }, - context.state - ); - } - - context.next(); -} diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 07c619c9d837..b5070e7c48ec 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -1,61 +1,106 @@ +/** @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 { STATE_SYMBOL } from '../constants.js'; -import { captured_signals, set_captured_signals } from '../runtime.js'; - -export const NOT_REACTIVE = 0; -export const REACTIVE_UNCHANGED = 1; -export const REACTIVE_CHANGED = 2; -export const REACTIVE_CHANGED_CACHED = 3; - -/** @type { { changed: boolean, label: string, time: number, sub: any, stacks: any[], value: any }[] | null } */ +import { DERIVED, STATE_SYMBOL } from '../constants.js'; +import { + active_reaction, + captured_signals, + current_version, + set_captured_signals, + untrack +} from '../runtime.js'; + +/** @type { any } */ export let tracing_expressions = null; -/** @type { 0 | 1 | 2 | 3 } */ -export let tracing_expression_reactive = NOT_REACTIVE; /** - * @param {any} expressions + * @param { Value } signal + * @param { number } version + * @param { { read: Error[] } } [entry] */ -function log_expressions(expressions) { - for (let expression of expressions) { - const val = expression.value; - const label = expression.label; - const time = expression.time; - const changed = expression.changed; - - if (time) { - // eslint-disable-next-line no-console - console.groupCollapsed( - `%c${label} %c(${time.toFixed(2)}ms)`, - changed ? 'color: CornflowerBlue; font-weight: bold' : 'color: grey; font-weight: bold', - 'color: grey', - val && typeof val === 'object' && STATE_SYMBOL in val ? snapshot(val, true) : val - ); - } else { - // eslint-disable-next-line no-console - console.groupCollapsed( - `%c${label}`, - changed ? 'color: CornflowerBlue; font-weight: bold' : 'color: grey; font-weight: bold', - val && typeof val === 'object' && STATE_SYMBOL in val ? snapshot(val, true) : val - ); - } +function log_entry(signal, version, entry) { + const debug = signal.debug; + const value = signal.v; + + if (value === UNINITIALIZED) { + return; + } - if (expression.sub) { - log_expressions(expression.sub); + 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, version); + } + 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 === version + ? '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, version); + } + } - for (var [name, stack] of expression.stacks) { - // eslint-disable-next-line no-console - console.groupCollapsed('%c' + name + ' stack', 'color: white; font-weight: normal;'); + const read = entry?.read; + + if (read && read.length > 0) { + // eslint-disable-next-line no-console + console.groupCollapsed('tracked'); + for (var stack of read) { // eslint-disable-next-line no-console console.log(stack); - // eslint-disable-next-line no-console - console.groupEnd(); } + // eslint-disable-next-line no-console + console.groupEnd(); + } + const created = signal.created; + + if (created) { + // eslint-disable-next-line no-console + console.groupCollapsed('created'); + // eslint-disable-next-line no-console + console.log(created); + // eslint-disable-next-line no-console + console.groupEnd(); + } + const updated = signal.updated; + if (updated) { + // eslint-disable-next-line no-console + console.groupCollapsed('updated'); + // eslint-disable-next-line no-console + console.log(updated); // eslint-disable-next-line no-console console.groupEnd(); } + // eslint-disable-next-line no-console + console.groupEnd(); } /** @@ -63,129 +108,59 @@ function log_expressions(expressions) { * @param {() => T} fn * @param {string} label */ -export function log_trace(fn, label) { +export function trace(fn, label) { var previously_tracing_expressions = tracing_expressions; try { - tracing_expressions = []; + tracing_expressions = { entries: new Map(), reaction: active_reaction }; var start = performance.now(); + var version = current_version; var value = fn(); var time = (performance.now() - start).toFixed(2); - if (tracing_expressions.length > 0) { - // eslint-disable-next-line no-console - console.group(`${label} %c(${time}ms)`, 'color: grey'); - log_expressions(tracing_expressions); + + if (tracing_expressions.size === 0) { // eslint-disable-next-line no-console - console.groupEnd(); + console.log(`${label} %cno reactive dependencies (${time}ms)`, 'color: grey'); } else { // eslint-disable-next-line no-console - console.log(`${label} %cno reactive dependencies (${time}ms)`, 'color: grey'); - } + console.group(`${label} %c(${time}ms)`, 'color: grey'); - if (previously_tracing_expressions !== null) { - previously_tracing_expressions.push(...tracing_expressions); - } + var entries = tracing_expressions.entries; - return value; - } finally { - tracing_expressions = previously_tracing_expressions; - } -} + tracing_expressions = null; -/** - * @template T - * @param {() => T} fn - * @param {boolean} [computed] - * @param {string} label - */ -export function trace(fn, label, computed) { - // If we aren't capturing the trace, just return the value - if (tracing_expressions === null) { - return fn(); - } - var previously_tracing_expressions = tracing_expressions; - var previously_tracing_expression_reactive = tracing_expression_reactive; - var previous_captured_signals = captured_signals; - var signals = new Set(); - set_captured_signals(signals); - - try { - tracing_expression_reactive = NOT_REACTIVE; - tracing_expressions = []; - var value, - time = 0; - - if (computed) { - var start = performance.now(); - value = fn(); - time = performance.now() - start; - } else { - value = fn(); + for (const [signal, entry] of entries) { + log_entry(signal, version, entry); + } + // eslint-disable-next-line no-console + console.groupEnd(); } - if (tracing_expressions !== null) { - var read_stack = ['read', get_stack()]; + if (previously_tracing_expressions !== null) { + for (const [signal, entry] of tracing_expressions.entries) { + var prev_entry = previously_tracing_expressions.get(signal); - if (tracing_expression_reactive !== NOT_REACTIVE) { - var set_stack; - if (signals.size === 1) { - set_stack = Array.from(signals)[0].stack; + if (prev_entry === undefined) { + previously_tracing_expressions.set(signal, entry); + } else { + prev_entry.read.push(...entry.read); } - var sub = null; - if (tracing_expressions.length !== 0) { - sub = tracing_expressions.slice(); - tracing_expressions = []; - } - tracing_expressions.push({ - changed: - tracing_expression_reactive === REACTIVE_CHANGED || - tracing_expression_reactive === REACTIVE_CHANGED_CACHED, - label: - label + - (tracing_expression_reactive === REACTIVE_CHANGED_CACHED ? ' [cached derived]' : ''), - value, - time, - stacks: set_stack ? [read_stack, set_stack] : [read_stack], - sub - }); - - if (previously_tracing_expressions !== null) { - previously_tracing_expressions.push(...tracing_expressions); - } - } else if (tracing_expressions.length !== 0) { - previously_tracing_expressions.push({ - changed: tracing_expressions.some((e) => e.changed), - label, - value, - time, - stacks: [read_stack], - sub: tracing_expressions - }); } } return value; } finally { tracing_expressions = previously_tracing_expressions; - tracing_expression_reactive = previously_tracing_expression_reactive; - set_captured_signals(previous_captured_signals); } } -/** - * @param {0 | 1 | 2 | 3} value - */ -export function set_tracing_expression_reactive(value) { - tracing_expression_reactive = value; -} - export function get_stack() { let error = Error(); const stack = error.stack; if (stack) { const lines = stack.split('\n'); - const new_lines = []; + const new_lines = ['\n']; for (let i = 0; i < lines.length; i++) { const line = lines[i]; @@ -195,6 +170,10 @@ export function get_stack() { new_lines.push(line); } + if (new_lines.length === 1) { + return null; + } + define_property(error, 'stack', { value: new_lines.join('\n') }); diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index d62c575391bc..9e6405594059 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -36,6 +36,7 @@ import { array_from, is_array } from '../../../shared/utils.js'; import { INERT } from '../../constants.js'; import { queue_micro_task } from '../task.js'; import { active_effect, active_reaction } from '../../runtime.js'; +import { DEV } from 'esm-env'; /** * The row of a keyed each block that is currently updating. We track this @@ -191,7 +192,18 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f var value = array[i]; var key = get_key(value, i); - item = create_item(hydrate_node, state, prev, null, value, key, i, render_fn, flags); + item = create_item( + hydrate_node, + state, + prev, + null, + value, + key, + i, + render_fn, + flags, + get_collection + ); state.items.set(key, item); prev = item; @@ -205,7 +217,16 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f if (!hydrating) { var effect = /** @type {Effect} */ (active_reaction); - reconcile(array, state, anchor, render_fn, flags, (effect.f & INERT) !== 0, get_key); + reconcile( + array, + state, + anchor, + render_fn, + flags, + (effect.f & INERT) !== 0, + get_key, + get_collection + ); } if (fallback_fn !== null) { @@ -251,9 +272,10 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f * @param {number} flags * @param {boolean} is_inert * @param {(value: V, index: number) => any} get_key + * @param {() => V[]} get_collection * @returns {void} */ -function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key) { +function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, get_collection) { var is_animated = (flags & EACH_IS_ANIMATED) !== 0; var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; @@ -319,7 +341,8 @@ function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key) { key, i, render_fn, - flags + flags, + get_collection ); items.set(key, prev); @@ -486,9 +509,21 @@ function update_item(item, value, index, type) { * @param {number} index * @param {(anchor: Node, item: V | Source, index: number | Value) => void} render_fn * @param {number} flags + * @param {() => V[]} get_collection * @returns {EachItem} */ -function create_item(anchor, state, prev, next, value, key, index, render_fn, flags) { +function create_item( + anchor, + state, + prev, + next, + value, + key, + index, + render_fn, + flags, + get_collection +) { var previous_each_item = current_each_item; var reactive = (flags & EACH_ITEM_REACTIVE) !== 0; var mutable = (flags & EACH_ITEM_IMMUTABLE) === 0; @@ -496,6 +531,16 @@ function create_item(anchor, state, prev, next, value, key, index, render_fn, fl var v = reactive ? (mutable ? mutable_source(value) : source(value)) : value; var i = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index); + if (DEV && reactive) { + // For tracing purposes, we need to link the source signal we create with the + // collection + index so that tracing works as intended + /** @type {Value} */ (v).debug = () => { + var collection_index = typeof i === 'number' ? index : i.v; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + get_collection()[collection_index]; + }; + } + /** @type {EachItem} */ var item = { i, diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index b300fd31ff6c..187cdac696fb 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -11,7 +11,7 @@ export { skip_ownership_validation } from './dev/ownership.js'; export { check_target, legacy_api } from './dev/legacy.js'; -export { log_trace, trace } from './dev/tracing.js'; +export { trace } from './dev/tracing.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'; diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index a8fd18c0dd9e..f8929e7fa2c5 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -13,6 +13,7 @@ import { source, set } from './reactivity/sources.js'; import { STATE_SYMBOL, STATE_SYMBOL_METADATA } from './constants.js'; import { UNINITIALIZED } from '../../constants.js'; import * as e from './errors.js'; +import { get_stack } from './dev/tracing.js'; /** * @template T @@ -22,6 +23,11 @@ import * as e from './errors.js'; * @returns {T} */ export function proxy(value, parent = null, prev) { + /** @type {Error | null} */ + var stack = null; + if (DEV) { + stack = get_stack(); + } // if non-proxyable, or is already a proxy, return `value` if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) { return value; @@ -41,7 +47,7 @@ export function proxy(value, parent = null, prev) { if (is_proxied_array) { // We need to create the length source eagerly to ensure that // mutations to the array are properly synced with our proxy - sources.set('length', source(/** @type {any[]} */ (value).length)); + sources.set('length', source(/** @type {any[]} */ (value).length, stack)); } /** @type {ProxyMetadata} */ @@ -87,7 +93,7 @@ export function proxy(value, parent = null, prev) { var s = sources.get(prop); if (s === undefined) { - s = source(descriptor.value); + s = source(descriptor.value, stack); sources.set(prop, s); } else { set(s, proxy(descriptor.value, metadata)); @@ -101,7 +107,7 @@ export function proxy(value, parent = null, prev) { if (s === undefined) { if (prop in target) { - sources.set(prop, source(UNINITIALIZED)); + sources.set(prop, source(UNINITIALIZED, stack)); } } else { // When working with arrays, we need to also ensure we update the length when removing @@ -135,7 +141,7 @@ export function proxy(value, parent = null, prev) { // create a source, but only if it's an own property and not a prototype property if (s === undefined && (!exists || get_descriptor(target, prop)?.writable)) { - s = source(proxy(exists ? target[prop] : UNINITIALIZED, metadata)); + s = source(proxy(exists ? target[prop] : UNINITIALIZED, metadata), stack); sources.set(prop, s); } @@ -203,7 +209,7 @@ export function proxy(value, parent = null, prev) { (active_effect !== null && (!has || get_descriptor(target, prop)?.writable)) ) { if (s === undefined) { - s = source(has ? proxy(target[prop], metadata) : UNINITIALIZED); + s = source(has ? proxy(target[prop], metadata) : UNINITIALIZED, stack); sources.set(prop, s); } @@ -230,7 +236,7 @@ export function proxy(value, parent = null, prev) { // If the item exists in the original, we need to create a uninitialized source, // else a later read of the property would result in a source being created with // the value of the original item at that index. - other_s = source(UNINITIALIZED); + other_s = source(UNINITIALIZED, stack); sources.set(i + '', other_s); } } @@ -242,7 +248,7 @@ export function proxy(value, parent = null, prev) { // object property before writing to that property. if (s === undefined) { if (!has || get_descriptor(target, prop)?.writable) { - s = source(undefined); + s = source(undefined, stack); set(s, proxy(value, metadata)); sources.set(prop, s); } diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index ffb1cdaa01ff..7b8632f04a1f 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -58,7 +58,7 @@ export function derived(fn) { }; if (DEV) { - signal.stack = ['created', get_stack()]; + signal.created = get_stack(); } if (active_reaction !== null && (active_reaction.f & DERIVED) !== 0) { diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 333ea4fcebb3..5062c34b6acd 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -47,9 +47,10 @@ export function set_inspect_effects(v) { /** * @template V * @param {V} v + * @param {Error | null} [stack] * @returns {Source} */ -export function source(v) { +export function source(v, stack) { /** @type {Value} */ var signal = { f: 0, // TODO ideally we could skip this altogether, but it causes type errors @@ -60,7 +61,8 @@ export function source(v) { }; if (DEV) { - signal.stack = ['created', get_stack()]; + signal.created = stack || get_stack(); + signal.debug = null; } return signal; @@ -169,7 +171,7 @@ export function internal_set(source, value) { source.version = increment_version(); if (DEV) { - source.stack = ['updated', get_stack()]; + source.updated = get_stack(); } mark_reactions(source, DIRTY); diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 71cf8952c369..49ed02af1525 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -15,7 +15,9 @@ export interface Value extends Signal { /** The latest value for this signal */ v: V; /** Dev only */ - stack?: [string, Error]; + created?: Error | null; + updated?: Error | null; + debug?: null | (() => void); } export interface Reaction extends Signal { diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index ccd8feaaf320..641276e14041 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -34,14 +34,7 @@ import * as e from './errors.js'; import { lifecycle_outside_component } from '../shared/errors.js'; import { FILENAME } from '../../constants.js'; import { legacy_mode_flag } from '../flags/index.js'; -import { - tracing_expressions, - set_tracing_expression_reactive, - REACTIVE_UNCHANGED, - REACTIVE_CHANGED, - tracing_expression_reactive, - REACTIVE_CHANGED_CACHED -} from './dev/tracing.js'; +import { tracing_expressions, get_stack } from './dev/tracing.js'; const FLUSH_MICROTASK = 0; const FLUSH_SYNC = 1; @@ -130,7 +123,7 @@ export function set_untracked_writes(value) { } /** @type {number} Used by sources and deriveds for handling updates to unowned deriveds */ -let current_version = 0; +export let current_version = 0; // If we are working with a get() chain that has no active container, // to prevent memory leaks, we skip adding the reaction. @@ -786,30 +779,38 @@ export function get(signal) { } } - var updated = false; + var updated = true; if (is_derived) { derived = /** @type {Derived} */ (signal); if (check_dirtiness(derived)) { - updated = true; update_derived(derived); + } else { + updated = false; } } if ( DEV && - active_reaction !== null && tracing_expressions !== null && - tracing_expression_reactive !== REACTIVE_UNCHANGED + active_reaction !== null && + tracing_expressions.reaction === active_reaction ) { - set_tracing_expression_reactive( - signal.version > active_reaction.version || active_reaction.version === current_version - ? updated - ? REACTIVE_CHANGED - : REACTIVE_CHANGED_CACHED - : REACTIVE_UNCHANGED - ); + // Used when mapping state between special blocks like `each` + if (signal.debug) { + signal.debug(); + } else if (signal.created) { + var entry = tracing_expressions.entries.get(signal); + + if (entry === undefined) { + entry = { + read: [] + }; + tracing_expressions.entries.set(signal, entry); + } + entry.read.push(get_stack()); + } } return signal.v; From e8997c37b7f07d568ff8c73eac2363fa520d5214 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Nov 2024 13:03:55 +0000 Subject: [PATCH 17/55] fixes --- packages/svelte/src/internal/client/dev/tracing.js | 13 +++++++------ packages/svelte/src/internal/client/runtime.js | 2 ++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index b5070e7c48ec..6ce5299681d3 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -6,8 +6,8 @@ import { DERIVED, STATE_SYMBOL } from '../constants.js'; import { active_reaction, captured_signals, - current_version, set_captured_signals, + trace_version, untrack } from '../runtime.js'; @@ -40,7 +40,7 @@ function log_entry(signal, version, entry) { } if (captured.size > 0) { for (const dep of captured) { - log_entry(dep, version); + log_entry(dep, signal.version); } return; } @@ -48,9 +48,7 @@ function log_entry(signal, version, entry) { 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 === version - ? 'dirty' - : 'clean'; + signal.version > current_reaction.version || version === signal.version ? 'dirty' : 'clean'; // eslint-disable-next-line no-console console.groupCollapsed( `%c${type}`, @@ -114,7 +112,7 @@ export function trace(fn, label) { tracing_expressions = { entries: new Map(), reaction: active_reaction }; var start = performance.now(); - var version = current_version; + var version = trace_version; var value = fn(); var time = (performance.now() - start).toFixed(2); @@ -164,6 +162,9 @@ export function get_stack() { 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; } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 641276e14041..7e0d3b2cced7 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -124,6 +124,7 @@ export function set_untracked_writes(value) { /** @type {number} Used by sources and deriveds for handling updates to unowned deriveds */ export let current_version = 0; +export let trace_version = 0; // If we are working with a get() chain that has no active container, // to prevent memory leaks, we skip adding the reaction. @@ -504,6 +505,7 @@ function flush_queued_root_effects(root_effects) { var previously_flushing_effect = is_flushing_effect; is_flushing_effect = true; + trace_version = current_version; try { for (var i = 0; i < length; i++) { From 6ac0779fa38be8d7b688cb467f286c87bcdd91f7 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Nov 2024 13:12:08 +0000 Subject: [PATCH 18/55] dead code --- .../phases/3-transform/client/transform-client.js | 12 +++--------- .../compiler/phases/3-transform/client/types.d.ts | 4 ---- .../3-transform/client/visitors/BlockStatement.js | 11 +---------- .../svelte/src/compiler/phases/3-transform/index.js | 4 ++-- 4 files changed, 6 insertions(+), 25 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 2b5cdc459c69..5349f6025533 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -134,16 +134,14 @@ const visitors = { /** * @param {ComponentAnalysis} analysis - * @param {string} source * @param {ValidatedCompileOptions} options * @returns {ESTree.Program} */ -export function client_component(analysis, source, options) { +export function client_component(analysis, options) { /** @type {ComponentClientTransformState} */ const state = { analysis, options, - source: source.split('\n'), scope: analysis.module.scope, scopes: analysis.module.scopes, is_instance: false, @@ -165,7 +163,6 @@ export function client_component(analysis, source, options) { private_state: new Map(), transform: {}, in_constructor: false, - trace_dependencies: false, // these are set inside the `Fragment` visitor, and cannot be used until then before_init: /** @type {any} */ (null), @@ -646,23 +643,20 @@ export function client_component(analysis, source, options) { /** * @param {Analysis} analysis - * @param {string} source * @param {ValidatedModuleCompileOptions} options * @returns {ESTree.Program} */ -export function client_module(analysis, source, options) { +export function client_module(analysis, options) { /** @type {ClientTransformState} */ const state = { analysis, options, - source: source.split('\n'), scope: analysis.module.scope, scopes: analysis.module.scopes, public_state: new Map(), private_state: new Map(), transform: {}, - in_constructor: false, - trace_dependencies: false + in_constructor: false }; const module = /** @type {ESTree.Program} */ ( diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts index 37beea6b54aa..0b54de538419 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts @@ -23,10 +23,6 @@ export interface ClientTransformState extends TransformState { */ readonly in_constructor: boolean; - readonly source: string[]; - - readonly trace_dependencies: boolean; - readonly transform: Record< string, { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js index d0f130299152..5309507890d3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BlockStatement.js @@ -16,16 +16,7 @@ export function BlockStatement(node, context) { b.return( b.call( '$.trace', - b.thunk( - b.block( - node.body.map( - (n) => - /** @type {Statement} */ ( - context.visit(n, { ...context.state, trace_dependencies: true }) - ) - ) - ) - ), + b.thunk(b.block(node.body.map((n) => /** @type {Statement} */ (context.visit(n))))), b.literal(tracing) ) ) diff --git a/packages/svelte/src/compiler/phases/3-transform/index.js b/packages/svelte/src/compiler/phases/3-transform/index.js index b1a59e6f1502..8f6597ee1298 100644 --- a/packages/svelte/src/compiler/phases/3-transform/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/index.js @@ -30,7 +30,7 @@ export function transform_component(analysis, source, options) { const program = options.generate === 'server' ? server_component(analysis, options) - : client_component(analysis, source, options); + : client_component(analysis, options); const js_source_name = get_source_name(options.filename, options.outputFilename, 'input.svelte'); const js = print(program, { @@ -79,7 +79,7 @@ export function transform_module(analysis, source, options) { const program = options.generate === 'server' ? server_module(analysis, options) - : client_module(analysis, source, options); + : client_module(analysis, options); const basename = options.filename.split(/[/\\]/).at(-1); if (program.body.length > 0) { From ffab3460cb0036034e0a91b3a6cb781e8b588e39 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Nov 2024 13:12:48 +0000 Subject: [PATCH 19/55] dead code --- playgrounds/sandbox/run.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/playgrounds/sandbox/run.js b/playgrounds/sandbox/run.js index 975613b5f454..4531e7182da8 100644 --- a/playgrounds/sandbox/run.js +++ b/playgrounds/sandbox/run.js @@ -48,12 +48,12 @@ for (const generate of /** @type {const} */ (['client', 'server'])) { fs.writeFileSync(`${cwd}/output/${file}.json`, JSON.stringify(ast, null, '\t')); - // try { - // const migrated = migrate(source); - // fs.writeFileSync(`${cwd}/output/${file}.migrated.svelte`, migrated.code); - // } catch (e) { - // console.warn(`Error migrating ${file}`, e); - // } + try { + const migrated = migrate(source); + fs.writeFileSync(`${cwd}/output/${file}.migrated.svelte`, migrated.code); + } catch (e) { + console.warn(`Error migrating ${file}`, e); + } } const compiled = compile(source, { From 0c973066bff4a67730106cd6d5941ec399678b9e Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Nov 2024 15:56:02 +0000 Subject: [PATCH 20/55] improve change detection --- .../svelte/src/internal/client/dev/tracing.js | 21 +++++++------------ .../svelte/src/internal/client/runtime.js | 4 +--- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 6ce5299681d3..0f28968eed0d 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -3,23 +3,16 @@ 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, - trace_version, - untrack -} from '../runtime.js'; +import { active_reaction, captured_signals, set_captured_signals, untrack } from '../runtime.js'; /** @type { any } */ export let tracing_expressions = null; /** * @param { Value } signal - * @param { number } version * @param { { read: Error[] } } [entry] */ -function log_entry(signal, version, entry) { +function log_entry(signal, entry) { const debug = signal.debug; const value = signal.v; @@ -40,7 +33,7 @@ function log_entry(signal, version, entry) { } if (captured.size > 0) { for (const dep of captured) { - log_entry(dep, signal.version); + log_entry(dep); } return; } @@ -48,7 +41,8 @@ function log_entry(signal, version, entry) { const type = (signal.f & DERIVED) !== 0 ? 'derived' : 'state'; const current_reaction = /** @type {Reaction} */ (active_reaction); const status = - signal.version > current_reaction.version || version === signal.version ? 'dirty' : 'clean'; + signal.version > current_reaction.version || current_reaction.version === 0 ? 'dirty' : 'clean'; + // eslint-disable-next-line no-console console.groupCollapsed( `%c${type}`, @@ -61,7 +55,7 @@ function log_entry(signal, version, entry) { if (type === 'derived') { const deps = new Set(/** @type {Derived} */ (signal).deps); for (const dep of deps) { - log_entry(dep, version); + log_entry(dep); } } @@ -112,7 +106,6 @@ export function trace(fn, label) { tracing_expressions = { entries: new Map(), reaction: active_reaction }; var start = performance.now(); - var version = trace_version; var value = fn(); var time = (performance.now() - start).toFixed(2); @@ -128,7 +121,7 @@ export function trace(fn, label) { tracing_expressions = null; for (const [signal, entry] of entries) { - log_entry(signal, version, entry); + log_entry(signal, entry); } // eslint-disable-next-line no-console console.groupEnd(); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 7e0d3b2cced7..5dae20bbbf74 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -123,8 +123,7 @@ export function set_untracked_writes(value) { } /** @type {number} Used by sources and deriveds for handling updates to unowned deriveds */ -export let current_version = 0; -export let trace_version = 0; +let current_version = 0; // If we are working with a get() chain that has no active container, // to prevent memory leaks, we skip adding the reaction. @@ -505,7 +504,6 @@ function flush_queued_root_effects(root_effects) { var previously_flushing_effect = is_flushing_effect; is_flushing_effect = true; - trace_version = current_version; try { for (var i = 0; i < length; i++) { From f6ebd00b9b5d52430fe77a5b1035f236bb2b4934 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Nov 2024 16:02:37 +0000 Subject: [PATCH 21/55] rename rune --- packages/svelte/src/ambient.d.ts | 8 ++++++-- .../compiler/phases/2-analyze/visitors/CallExpression.js | 2 +- .../phases/3-transform/client/visitors/CallExpression.js | 2 +- .../3-transform/client/visitors/VariableDeclaration.js | 4 ++-- .../phases/3-transform/server/visitors/CallExpression.js | 2 +- packages/svelte/src/utils.js | 4 ++-- packages/svelte/types/index.d.ts | 8 ++++++-- 7 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/svelte/src/ambient.d.ts b/packages/svelte/src/ambient.d.ts index 9d37d9a3f785..2f876fb63fe9 100644 --- a/packages/svelte/src/ambient.d.ts +++ b/packages/svelte/src/ambient.d.ts @@ -379,6 +379,12 @@ declare function $inspect( ...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: * @@ -399,5 +405,3 @@ declare function $inspect( * https://svelte.dev/docs/svelte/$host */ declare function $host(): El; - -declare function $trace(name: string): void; diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 27e9964d93e8..2d35fdea2e7b 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -136,7 +136,7 @@ export function CallExpression(node, context) { break; - case '$trace': + case '$inspect.trace': if (node.arguments.length !== 1) { e.rune_invalid_arguments_length(node, rune, 'exactly one argument'); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js index c952f1a61e4d..af14bbd1e75f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js @@ -34,7 +34,7 @@ export function CallExpression(node, context) { case '$inspect().with': return transform_inspect_rune(node, context); - case '$trace': + case '$inspect.trace': return b.empty; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js index d04507372901..afb90bbec7f9 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js @@ -27,9 +27,9 @@ export function VariableDeclaration(node, context) { rune === '$effect.tracking' || rune === '$effect.root' || rune === '$inspect' || + rune === '$inspect.trace' || rune === '$state.snapshot' || - rune === '$host' || - rune === '$trace' + rune === '$host' ) { if (init != null && is_hoisted_function(init)) { context.state.hoisted.push( diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js index 308aec56254b..462ebb9501e5 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js @@ -37,7 +37,7 @@ export function CallExpression(node, context) { return transform_inspect_rune(node, context); } - if (rune === '$trace') { + if (rune === '$inspect.trace') { return b.empty; } diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index d3c6bcb6f167..03cba55c3079 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -414,8 +414,8 @@ const RUNES = /** @type {const} */ ([ '$effect.root', '$inspect', '$inspect().with', - '$host', - '$trace' + '$inspect.trace', + '$host' ]); /** diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 0e29e3e7258b..1493f8c30e5a 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2686,6 +2686,12 @@ declare function $inspect( ...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: * @@ -2707,6 +2713,4 @@ declare function $inspect( */ declare function $host(): El; -declare function $trace(name: string): void; - //# sourceMappingURL=index.d.ts.map \ No newline at end of file From 66482c408064347670d97824ff29660379f226ea Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Nov 2024 16:07:32 +0000 Subject: [PATCH 22/55] lint --- packages/svelte/src/ambient.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/svelte/src/ambient.d.ts b/packages/svelte/src/ambient.d.ts index 2f876fb63fe9..843b016d18a8 100644 --- a/packages/svelte/src/ambient.d.ts +++ b/packages/svelte/src/ambient.d.ts @@ -380,9 +380,7 @@ declare function $inspect( ): { with: (fn: (type: 'init' | 'update', ...values: T) => void) => void }; declare namespace $inspect { - export function trace(name: string): void; - } /** From 9589f5344391ef7c8f74acb18dfc2f3875a7c343 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Nov 2024 16:11:05 +0000 Subject: [PATCH 23/55] lint --- packages/svelte/types/index.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 1493f8c30e5a..c828f01e4cf1 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2687,9 +2687,7 @@ declare function $inspect( ): { with: (fn: (type: 'init' | 'update', ...values: T) => void) => void }; declare namespace $inspect { - export function trace(name: string): void; - } /** From dad777a70b90ce4e05c98d6174c6284ae603d382 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 16 Nov 2024 18:27:43 +0000 Subject: [PATCH 24/55] fix bug --- packages/svelte/src/internal/client/dev/tracing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 0f28968eed0d..509a506f9479 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -109,7 +109,7 @@ export function trace(fn, label) { var value = fn(); var time = (performance.now() - start).toFixed(2); - if (tracing_expressions.size === 0) { + if (tracing_expressions.entries.size === 0) { // eslint-disable-next-line no-console console.log(`${label} %cno reactive dependencies (${time}ms)`, 'color: grey'); } else { From 33934097329b7631c9b11617a3fbf5fb5f36835e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 21 Nov 2024 09:47:30 -0500 Subject: [PATCH 25/55] tweaks --- .../svelte/src/internal/client/dev/tracing.js | 42 +++++++------------ packages/svelte/src/internal/client/proxy.js | 2 +- .../internal/client/reactivity/deriveds.js | 2 +- .../src/internal/client/reactivity/sources.js | 4 +- .../svelte/src/internal/client/runtime.js | 2 +- 5 files changed, 20 insertions(+), 32 deletions(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 509a506f9479..c44c6649d483 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -38,7 +38,8 @@ function log_entry(signal, entry) { return; } } - const type = (signal.f & DERIVED) !== 0 ? 'derived' : 'state'; + + 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'; @@ -52,45 +53,32 @@ function log_entry(signal, entry) { typeof value === 'object' && STATE_SYMBOL in value ? snapshot(value, true) : value ); - if (type === 'derived') { + 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) { - // eslint-disable-next-line no-console - console.groupCollapsed('tracked'); for (var stack of read) { // eslint-disable-next-line no-console console.log(stack); } - // eslint-disable-next-line no-console - console.groupEnd(); } - const created = signal.created; - if (created) { - // eslint-disable-next-line no-console - console.groupCollapsed('created'); - // eslint-disable-next-line no-console - console.log(created); - // eslint-disable-next-line no-console - console.groupEnd(); - } - const updated = signal.updated; - - if (updated) { - // eslint-disable-next-line no-console - console.groupCollapsed('updated'); - // eslint-disable-next-line no-console - console.log(updated); - // eslint-disable-next-line no-console - console.groupEnd(); - } // eslint-disable-next-line no-console console.groupEnd(); } @@ -145,7 +133,7 @@ export function trace(fn, label) { } } -export function get_stack() { +export function get_stack(label = 'TraceInvoked') { let error = Error(); const stack = error.stack; @@ -173,7 +161,7 @@ export function get_stack() { }); define_property(error, 'name', { - value: 'TraceInvokedError' + value: `${label}Error` }); } return error; diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index f8929e7fa2c5..d74f55866f20 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -26,7 +26,7 @@ export function proxy(value, parent = null, prev) { /** @type {Error | null} */ var stack = null; if (DEV) { - stack = get_stack(); + stack = get_stack('CreatedAt'); } // if non-proxyable, or is already a proxy, return `value` if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) { diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 7b8632f04a1f..c9fe14395acd 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -58,7 +58,7 @@ export function derived(fn) { }; if (DEV) { - signal.created = get_stack(); + signal.created = get_stack('CreatedAt'); } if (active_reaction !== null && (active_reaction.f & DERIVED) !== 0) { diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 5062c34b6acd..24dd837772b6 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -61,7 +61,7 @@ export function source(v, stack) { }; if (DEV) { - signal.created = stack || get_stack(); + signal.created = stack ?? get_stack('CreatedAt'); signal.debug = null; } @@ -171,7 +171,7 @@ export function internal_set(source, value) { source.version = increment_version(); if (DEV) { - source.updated = get_stack(); + source.updated = get_stack('UpdatedAt'); } mark_reactions(source, DIRTY); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 5dae20bbbf74..2c61803822ea 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -809,7 +809,7 @@ export function get(signal) { }; tracing_expressions.entries.set(signal, entry); } - entry.read.push(get_stack()); + entry.read.push(get_stack('TracedAt')); } } From 55b7549ce35ef2f33436c1ff29a8f896961a9992 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 21 Nov 2024 14:56:45 +0000 Subject: [PATCH 26/55] Update packages/svelte/src/internal/client/dev/tracing.js Co-authored-by: Rich Harris --- packages/svelte/src/internal/client/dev/tracing.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 509a506f9479..a6dafa8dad31 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -38,7 +38,8 @@ function log_entry(signal, entry) { return; } } - const type = (signal.f & DERIVED) !== 0 ? 'derived' : 'state'; + + 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'; From 8c103b20504af7e043619126fd59db84fe06bdac Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 21 Nov 2024 14:56:50 +0000 Subject: [PATCH 27/55] Update packages/svelte/src/internal/client/dev/tracing.js Co-authored-by: Rich Harris --- packages/svelte/src/internal/client/dev/tracing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index a6dafa8dad31..81eb38976877 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -53,7 +53,7 @@ function log_entry(signal, entry) { typeof value === 'object' && STATE_SYMBOL in value ? snapshot(value, true) : value ); - if (type === 'derived') { + if (type === '$derived') { const deps = new Set(/** @type {Derived} */ (signal).deps); for (const dep of deps) { log_entry(dep); From 3da9dac10b11c9e0ff68c7c6f26f9bc74f925dd0 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 21 Nov 2024 14:57:49 +0000 Subject: [PATCH 28/55] Update packages/svelte/src/internal/client/dev/tracing.js Co-authored-by: Rich Harris --- packages/svelte/src/internal/client/dev/tracing.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 81eb38976877..6ac5fae94611 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -174,7 +174,8 @@ export function get_stack() { }); define_property(error, 'name', { - value: 'TraceInvokedError' + // 'Error' suffix is required for stack traces to be rendered properly + value: `${label}Error` }); } return error; From a078e25273038f277110acbe96c7fd02e7ac6393 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 21 Nov 2024 14:57:58 +0000 Subject: [PATCH 29/55] Update packages/svelte/src/internal/client/dev/tracing.js Co-authored-by: Rich Harris --- packages/svelte/src/internal/client/dev/tracing.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 6ac5fae94611..b656ba1430a3 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -146,7 +146,10 @@ export function trace(fn, label) { } } -export function get_stack() { +/** + * @param {string} label + */ +export function get_stack(label) { let error = Error(); const stack = error.stack; From f8eee243dd48f3d432ec0b33decdc45fa9f8dc0e Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 5 Dec 2024 12:56:08 +0000 Subject: [PATCH 30/55] todos --- .../98-reference/.generated/compile-errors.md | 18 +++++++++++++ .../svelte/messages/compile-errors/script.md | 12 +++++++++ packages/svelte/src/compiler/errors.js | 27 +++++++++++++++++++ .../2-analyze/visitors/CallExpression.js | 8 +++--- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/documentation/docs/98-reference/.generated/compile-errors.md b/documentation/docs/98-reference/.generated/compile-errors.md index 3bd162d8d74d..b017fd4fb729 100644 --- a/documentation/docs/98-reference/.generated/compile-errors.md +++ b/documentation/docs/98-reference/.generated/compile-errors.md @@ -958,6 +958,24 @@ A `