Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hot-buses-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

feat: add parent hierarchy to \_\_svelte_meta objects at dev time
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { extract_identifiers } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import { create_derived } from '../utils.js';
import { get_value } from './shared/declarations.js';
import { build_expression } from './shared/utils.js';
import { build_expression, with_dev_stack } from './shared/utils.js';

/**
* @param {AST.AwaitBlock} node
Expand Down Expand Up @@ -54,7 +54,7 @@ export function AwaitBlock(node, context) {
}

context.state.init.push(
b.stmt(
with_dev_stack(
b.call(
'$.await',
context.state.node,
Expand All @@ -64,7 +64,9 @@ export function AwaitBlock(node, context) {
: b.null,
then_block,
catch_block
)
),
node,
'await'
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { dev } from '../../../../state.js';
import { extract_paths, object } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import { get_value } from './shared/declarations.js';
import { build_expression } from './shared/utils.js';
import { build_expression, with_dev_stack } from './shared/utils.js';

/**
* @param {AST.EachBlock} node
Expand Down Expand Up @@ -337,7 +337,7 @@ export function EachBlock(node, context) {
);
}

context.state.init.push(b.stmt(b.call('$.each', ...args)));
context.state.init.push(with_dev_stack(b.call('$.each', ...args), node, 'each'));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */
import * as b from '#compiler/builders';
import { build_expression } from './shared/utils.js';
import { build_expression, with_dev_stack } from './shared/utils.js';

/**
* @param {AST.IfBlock} node
Expand Down Expand Up @@ -74,7 +74,7 @@ export function IfBlock(node, context) {
args.push(b.id('$$elseif'));
}

statements.push(b.stmt(b.call('$.if', ...args)));
statements.push(with_dev_stack(b.call('$.if', ...args), node, 'if'));

context.state.init.push(b.block(statements));
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */
import * as b from '#compiler/builders';
import { build_expression } from './shared/utils.js';
import { build_expression, with_dev_stack } from './shared/utils.js';

/**
* @param {AST.KeyBlock} node
Expand All @@ -15,6 +15,10 @@ export function KeyBlock(node, context) {
const body = /** @type {Expression} */ (context.visit(node.fragment));

context.state.init.push(
b.stmt(b.call('$.key', context.state.node, b.thunk(key), b.arrow([b.id('$$anchor')], body)))
with_dev_stack(
b.call('$.key', context.state.node, b.thunk(key), b.arrow([b.id('$$anchor')], body)),
node,
'key'
)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
import { dev, is_ignored } from '../../../../../state.js';
import { get_attribute_chunks, object } from '../../../../../utils/ast.js';
import * as b from '#compiler/builders';
import { build_bind_this, memoize_expression, validate_binding } from '../shared/utils.js';
import {
build_bind_this,
memoize_expression,
validate_binding,
with_dev_stack
} from '../shared/utils.js';
import { build_attribute_value } from '../shared/element.js';
import { build_event_handler } from './events.js';
import { determine_slot } from '../../../../../utils/slot.js';
Expand Down Expand Up @@ -483,7 +488,10 @@ export function build_component(node, component_name, context) {
);
} else {
context.state.template.push_comment();
statements.push(b.stmt(fn(anchor)));

statements.push(
with_dev_stack(fn(anchor), node, 'component', { componentTag: component_name })
);
}

return statements.length > 1 ? b.block(statements) : statements[0];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @import { AssignmentExpression, Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression, Pattern } from 'estree' */
/** @import { AssignmentExpression, Expression, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression, Pattern, CallExpression, Statement } from 'estree' */
/** @import { AST, ExpressionMetadata } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext, Context } from '../../types' */
import { walk } from 'zimmerframe';
Expand All @@ -7,7 +7,7 @@ import * as b from '#compiler/builders';
import { sanitize_template_string } from '../../../../../utils/sanitize_template_string.js';
import { regex_is_valid_identifier } from '../../../../patterns.js';
import is_reference from 'is-reference';
import { dev, is_ignored, locator } from '../../../../../state.js';
import { dev, is_ignored, locator, filename } from '../../../../../state.js';
import { build_getter, create_derived } from '../../utils.js';

/**
Expand Down Expand Up @@ -394,3 +394,36 @@ export function build_expression(context, expression, metadata, state = context.

return sequence;
}

/**
* Wraps a statement/expression with dev stack tracking in dev mode
* @param {CallExpression} call_expression - The function call to wrap (e.g., $.if, $.each, etc.)
* @param {{ start?: number }} node - AST node for location info
* @param {'component' | 'if' | 'each' | 'await' | 'key'} type - Type of block/component
* @param {Record<string, number | string>} [additional] - Any additional properties to add to the dev stack entry
* @returns {Statement} - Statement with or without dev stack wrapping
*/
export function with_dev_stack(call_expression, node, type, additional) {
if (!dev) {
return b.stmt(call_expression);
}

const location = node.start && locator(node.start);
if (!location) {
return b.stmt(call_expression);
}

return b.stmt(
b.call(
'$.with_dev_stack',
b.arrow([], b.block([b.stmt(call_expression)])),
b.literal(type),
b.literal(filename),
b.literal(location.line),
b.literal(location.column),
additional
? b.object(Object.entries(additional).map(([key, value]) => b.init(key, b.literal(value))))
: b.null
)
);
}
40 changes: 39 additions & 1 deletion packages/svelte/src/internal/client/context.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @import { ComponentContext } from '#client' */
/** @import { ComponentContext, DevStackEntry } from '#client' */

import { DEV } from 'esm-env';
import { lifecycle_outside_component } from '../shared/errors.js';
Expand All @@ -20,6 +20,44 @@ export function set_component_context(context) {
component_context = context;
}

/** @type {DevStackEntry | null} */
export let dev_stack = null;

/** @param {DevStackEntry | null} stack */
export function set_dev_stack(stack) {
dev_stack = stack;
}

/**
* Execute a callback with a new dev stack entry
* @param {() => any} callback - Function to execute
* @param {DevStackEntry['type']} type - Type of block/component
* @param {string} file - Source file
* @param {number} line - Line number
* @param {number} column - Column number
* @param {Record<string, any>} [additional] - Any additional properties to add to the dev stack entry
* @returns {any}
*/
export function with_dev_stack(callback, type, file, line, column, additional) {
/** @type {DevStackEntry} */
const new_entry = {
type,
file,
line,
column,
parent: dev_stack,
...additional
};
const previous_stack = dev_stack;
dev_stack = new_entry;

try {
return callback();
} finally {
dev_stack = previous_stack;
}
}

/**
* The current component function. Different from current component context:
* ```html
Expand Down
2 changes: 2 additions & 0 deletions packages/svelte/src/internal/client/dev/elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { COMMENT_NODE, DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE } from '#client/constants';
import { HYDRATION_END, HYDRATION_START, HYDRATION_START_ELSE } from '../../../constants.js';
import { hydrating } from '../dom/hydration.js';
import { dev_stack } from '../context.js';

/**
* @param {any} fn
Expand All @@ -28,6 +29,7 @@ export function add_locations(fn, filename, locations) {
function assign_location(element, filename, location) {
// @ts-expect-error
element.__svelte_meta = {
parent: dev_stack,
loc: { file: filename, line: location[0], column: location[1] }
};

Expand Down
16 changes: 13 additions & 3 deletions packages/svelte/src/internal/client/dom/blocks/await.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import { queue_micro_task } from '../task.js';
import { HYDRATION_START_ELSE, UNINITIALIZED } from '../../../../constants.js';
import {
component_context,
dev_stack,
is_runes,
set_component_context,
set_dev_current_component_function
set_dev_current_component_function,
set_dev_stack
} from '../../context.js';

const PENDING = 0;
Expand All @@ -45,6 +47,7 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {

/** @type {any} */
var component_function = DEV ? component_context?.function : null;
var dev_original_stack = DEV ? dev_stack : null;

/** @type {V | Promise<V> | typeof UNINITIALIZED} */
var input = UNINITIALIZED;
Expand Down Expand Up @@ -75,7 +78,10 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
set_active_effect(effect);
set_active_reaction(effect); // TODO do we need both?
set_component_context(active_component_context);
if (DEV) set_dev_current_component_function(component_function);
if (DEV) {
set_dev_current_component_function(component_function);
set_dev_stack(dev_original_stack);
}
}

try {
Expand Down Expand Up @@ -107,7 +113,11 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
}
} finally {
if (restore) {
if (DEV) set_dev_current_component_function(null);
if (DEV) {
set_dev_current_component_function(null);
set_dev_stack(null);
}

set_component_context(null);
set_active_reaction(null);
set_active_effect(null);
Expand Down
8 changes: 7 additions & 1 deletion packages/svelte/src/internal/client/dom/blocks/snippet.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { EFFECT_TRANSPARENT, ELEMENT_NODE } from '#client/constants';
import { branch, block, destroy_effect, teardown } from '../../reactivity/effects.js';
import {
dev_current_component_function,
set_dev_current_component_function
dev_stack,
set_dev_current_component_function,
set_dev_stack
} from '../../context.js';
import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
import { create_fragment_from_html } from '../reconciler.js';
Expand Down Expand Up @@ -61,14 +63,18 @@ export function snippet(node, get_snippet, ...args) {
* @param {(node: TemplateNode, ...args: any[]) => void} fn
*/
export function wrap_snippet(component, fn) {
var original_stack = dev_stack;
const snippet = (/** @type {TemplateNode} */ node, /** @type {any[]} */ ...args) => {
var previous_component_function = dev_current_component_function;
var previous_stack = dev_stack;
set_dev_current_component_function(component);
set_dev_stack(original_stack);

try {
return fn(node, ...args);
} finally {
set_dev_current_component_function(previous_component_function);
set_dev_stack(previous_stack);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { set_should_intro } from '../../render.js';
import { current_each_item, set_current_each_item } from './each.js';
import { active_effect } from '../../runtime.js';
import { component_context } from '../../context.js';
import { component_context, dev_stack } from '../../context.js';
import { DEV } from 'esm-env';
import { EFFECT_TRANSPARENT, ELEMENT_NODE } from '#client/constants';
import { assign_nodes } from '../template.js';
Expand Down Expand Up @@ -107,6 +107,7 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
if (DEV && location) {
// @ts-expect-error
element.__svelte_meta = {
parent: dev_stack,
loc: {
file: filename,
line: location[0],
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { createAttachmentKey as attachment } from '../../attachments/index.js';
export { FILENAME, HMR, NAMESPACE_SVG } from '../../constants.js';
export { push, pop } from './context.js';
export { push, pop, with_dev_stack } from './context.js';
export { assign, assign_and, assign_or, assign_nullish } from './dev/assign.js';
export { cleanup_styles } from './dev/css.js';
export { add_locations } from './dev/elements.js';
Expand Down
9 changes: 9 additions & 0 deletions packages/svelte/src/internal/client/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,13 @@ export type SourceLocation =
| [line: number, column: number]
| [line: number, column: number, SourceLocation[]];

export interface DevStackEntry {
file: string;
type: 'component' | 'if' | 'each' | 'await' | 'key';
line: number;
column: number;
parent: DevStackEntry | null;
componentTag?: string;
}

export * from './reactivity/types';
Loading