Skip to content

Commit 3385000

Browse files
committed
feat: add parent hierarchy to __svelte_meta objects at dev time
This adds a `parent` property to the `__svelte_meta` properties that are added to elements at dev time. This property represents the closest non-element parent the element is related to. For example for `{#if ...}<div>foo</div>{/if}` the `parent` of the div would be the line/column of the if block. The parent is recursive and goes upwards (through component boundaries) until the root component is reached, which has no parent. part of #11389
1 parent 32481be commit 3385000

File tree

19 files changed

+262
-20
lines changed

19 files changed

+262
-20
lines changed

.changeset/hot-buses-end.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: add parent hierarchy to \_\_svelte_meta objects at dev time

packages/svelte/src/compiler/phases/3-transform/client/transform-client.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,9 @@ export function client_component(analysis, options) {
453453
if (analysis.custom_element) {
454454
to_remove.push(b.literal('$$host'));
455455
}
456+
if (dev) {
457+
to_remove.push(b.literal('$$dev'));
458+
}
456459

457460
component_block.body.unshift(
458461
b.const(

packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { extract_identifiers } from '../../../../utils/ast.js';
55
import * as b from '#compiler/builders';
66
import { create_derived } from '../utils.js';
77
import { get_value } from './shared/declarations.js';
8-
import { build_expression } from './shared/utils.js';
8+
import { build_expression, with_dev_stack } from './shared/utils.js';
99

1010
/**
1111
* @param {AST.AwaitBlock} node
@@ -54,7 +54,7 @@ export function AwaitBlock(node, context) {
5454
}
5555

5656
context.state.init.push(
57-
b.stmt(
57+
with_dev_stack(
5858
b.call(
5959
'$.await',
6060
context.state.node,
@@ -64,7 +64,9 @@ export function AwaitBlock(node, context) {
6464
: b.null,
6565
then_block,
6666
catch_block
67-
)
67+
),
68+
node,
69+
'await'
6870
)
6971
);
7072
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { dev } from '../../../../state.js';
1313
import { extract_paths, object } from '../../../../utils/ast.js';
1414
import * as b from '#compiler/builders';
1515
import { get_value } from './shared/declarations.js';
16-
import { build_expression } from './shared/utils.js';
16+
import { build_expression, with_dev_stack } from './shared/utils.js';
1717

1818
/**
1919
* @param {AST.EachBlock} node
@@ -337,7 +337,7 @@ export function EachBlock(node, context) {
337337
);
338338
}
339339

340-
context.state.init.push(b.stmt(b.call('$.each', ...args)));
340+
context.state.init.push(with_dev_stack(b.call('$.each', ...args), node, 'each'));
341341
}
342342

343343
/**

packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/** @import { AST } from '#compiler' */
33
/** @import { ComponentContext } from '../types' */
44
import * as b from '#compiler/builders';
5-
import { build_expression } from './shared/utils.js';
5+
import { build_expression, with_dev_stack } from './shared/utils.js';
66

77
/**
88
* @param {AST.IfBlock} node
@@ -74,7 +74,7 @@ export function IfBlock(node, context) {
7474
args.push(b.id('$$elseif'));
7575
}
7676

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

7979
context.state.init.push(b.block(statements));
8080
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/** @import { AST } from '#compiler' */
33
/** @import { ComponentContext } from '../types' */
44
import * as b from '#compiler/builders';
5-
import { build_expression } from './shared/utils.js';
5+
import { build_expression, with_dev_stack } from './shared/utils.js';
66

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

1717
context.state.init.push(
18-
b.stmt(b.call('$.key', context.state.node, b.thunk(key), b.arrow([b.id('$$anchor')], body)))
18+
with_dev_stack(
19+
b.call('$.key', context.state.node, b.thunk(key), b.arrow([b.id('$$anchor')], body)),
20+
node,
21+
'key'
22+
)
1923
);
2024
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
import { dev, is_ignored } from '../../../../../state.js';
55
import { get_attribute_chunks, object } from '../../../../../utils/ast.js';
66
import * as b from '#compiler/builders';
7-
import { build_bind_this, memoize_expression, validate_binding } from '../shared/utils.js';
7+
import {
8+
build_bind_this,
9+
memoize_expression,
10+
validate_binding,
11+
with_dev_stack
12+
} from '../shared/utils.js';
813
import { build_attribute_value } from '../shared/element.js';
914
import { build_event_handler } from './events.js';
1015
import { determine_slot } from '../../../../../utils/slot.js';
@@ -483,7 +488,10 @@ export function build_component(node, component_name, context) {
483488
);
484489
} else {
485490
context.state.template.push_comment();
486-
statements.push(b.stmt(fn(anchor)));
491+
492+
statements.push(
493+
with_dev_stack(fn(anchor), node, 'component', { componentTag: component_name })
494+
);
487495
}
488496

489497
return statements.length > 1 ? b.block(statements) : statements[0];

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { AssignmentExpression, Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression, Pattern } from 'estree' */
1+
/** @import { AssignmentExpression, Expression, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression, Pattern, CallExpression, Statement } from 'estree' */
22
/** @import { AST, ExpressionMetadata } from '#compiler' */
33
/** @import { ComponentClientTransformState, ComponentContext, Context } from '../../types' */
44
import { walk } from 'zimmerframe';
@@ -7,7 +7,7 @@ import * as b from '#compiler/builders';
77
import { sanitize_template_string } from '../../../../../utils/sanitize_template_string.js';
88
import { regex_is_valid_identifier } from '../../../../patterns.js';
99
import is_reference from 'is-reference';
10-
import { dev, is_ignored, locator } from '../../../../../state.js';
10+
import { dev, is_ignored, locator, filename } from '../../../../../state.js';
1111
import { build_getter, create_derived } from '../../utils.js';
1212

1313
/**
@@ -394,3 +394,37 @@ export function build_expression(context, expression, metadata, state = context.
394394

395395
return sequence;
396396
}
397+
398+
/**
399+
* Wraps a statement/expression with dev stack tracking in dev mode
400+
* @param {CallExpression} call_expression - The function call to wrap (e.g., $.if, $.each, etc.)
401+
* @param {{ start?: number }} node - AST node for location info
402+
* @param {'component' | 'if' | 'each' | 'await' | 'key'} type - Type of block/component
403+
* @param {Record<string, number | string>} [additional] - Any additional properties to add to the dev stack entry
404+
* @returns {Statement} - Statement with or without dev stack wrapping
405+
*/
406+
export function with_dev_stack(call_expression, node, type, additional) {
407+
if (!dev) {
408+
return b.stmt(call_expression);
409+
}
410+
411+
const location = node.start && locator(node.start);
412+
if (!location) {
413+
return b.stmt(call_expression);
414+
}
415+
416+
// Wrap with dev stack tracking
417+
return b.stmt(
418+
b.call(
419+
'$.with_dev_stack',
420+
b.arrow([], b.block([b.stmt(call_expression)])),
421+
b.literal(type),
422+
b.literal(filename),
423+
b.literal(location.line),
424+
b.literal(location.column),
425+
additional
426+
? b.object(Object.entries(additional).map(([key, value]) => b.init(key, b.literal(value))))
427+
: b.null
428+
)
429+
);
430+
}

packages/svelte/src/internal/client/context.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { ComponentContext } from '#client' */
1+
/** @import { ComponentContext, DevStackEntry } from '#client' */
22

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

23+
/** @type {DevStackEntry | null} */
24+
export let dev_stack = null;
25+
26+
/** @param {DevStackEntry | null} stack */
27+
export function set_dev_stack(stack) {
28+
dev_stack = stack;
29+
}
30+
31+
/**
32+
* Execute a callback with a new dev stack entry
33+
* @param {() => any} callback - Function to execute
34+
* @param {DevStackEntry['type']} type - Type of block/component
35+
* @param {string} file - Source file
36+
* @param {number} line - Line number
37+
* @param {number} column - Column number
38+
* @param {Record<string, any>} [additional] - Any additional properties to add to the dev stack entry
39+
* @returns {any}
40+
*/
41+
export function with_dev_stack(callback, type, file, line, column, additional) {
42+
/** @type {DevStackEntry} */
43+
const new_entry = {
44+
type,
45+
file,
46+
line,
47+
column,
48+
parent: dev_stack,
49+
...additional
50+
};
51+
const previous_stack = dev_stack;
52+
dev_stack = new_entry;
53+
54+
try {
55+
return callback();
56+
} finally {
57+
dev_stack = previous_stack;
58+
}
59+
}
60+
2361
/**
2462
* The current component function. Different from current component context:
2563
* ```html

packages/svelte/src/internal/client/dev/elements.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { COMMENT_NODE, DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE } from '#client/constants';
33
import { HYDRATION_END, HYDRATION_START, HYDRATION_START_ELSE } from '../../../constants.js';
44
import { hydrating } from '../dom/hydration.js';
5+
import { dev_stack } from '../context.js';
56

67
/**
78
* @param {any} fn
@@ -28,6 +29,7 @@ export function add_locations(fn, filename, locations) {
2829
function assign_location(element, filename, location) {
2930
// @ts-expect-error
3031
element.__svelte_meta = {
32+
parent: dev_stack,
3133
loc: { file: filename, line: location[0], column: location[1] }
3234
};
3335

0 commit comments

Comments
 (0)