From 9dddb31b4a9260a520d19e2955393da0aa1d7f3b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 08:07:08 -0400 Subject: [PATCH 01/13] Version Packages (#16276) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/new-trees-behave.md | 5 ----- .changeset/short-fireants-flow.md | 5 ----- packages/svelte/CHANGELOG.md | 10 ++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 .changeset/new-trees-behave.md delete mode 100644 .changeset/short-fireants-flow.md diff --git a/.changeset/new-trees-behave.md b/.changeset/new-trees-behave.md deleted file mode 100644 index d5fab30f3e..0000000000 --- a/.changeset/new-trees-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: simplify props diff --git a/.changeset/short-fireants-flow.md b/.changeset/short-fireants-flow.md deleted file mode 100644 index b9955ff577..0000000000 --- a/.changeset/short-fireants-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': minor ---- - -feat: add `getAbortSignal()` diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index ef64091ca3..7589eda728 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.35.0 + +### Minor Changes + +- feat: add `getAbortSignal()` ([#16266](https://github.com/sveltejs/svelte/pull/16266)) + +### Patch Changes + +- chore: simplify props ([#16270](https://github.com/sveltejs/svelte/pull/16270)) + ## 5.34.9 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 2d88d2a051..e8c28aeaa1 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.34.9", + "version": "5.35.0", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 1c0aed6e87..baa2dbd046 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.34.9'; +export const VERSION = '5.35.0'; export const PUBLIC_VERSION = '5'; From 32882a956bc1283063d6da52401ac02b0aedefdb Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:12:25 +0200 Subject: [PATCH 02/13] feat: add parent hierarchy to `__svelte_meta` objects at dev time (#16255) * 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 ...}
foo
{/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 * oops * Apply suggestions from code review Co-authored-by: Rich Harris * tweak * original component tag * make render appear in tree, keep tree in sync when rerenders occur --------- Co-authored-by: Rich Harris --- .changeset/hot-buses-end.md | 5 + .../3-transform/client/visitors/AwaitBlock.js | 8 +- .../3-transform/client/visitors/EachBlock.js | 4 +- .../3-transform/client/visitors/IfBlock.js | 4 +- .../3-transform/client/visitors/KeyBlock.js | 8 +- .../3-transform/client/visitors/RenderTag.js | 14 +- .../client/visitors/shared/component.js | 10 +- .../client/visitors/shared/utils.js | 35 +++- packages/svelte/src/compiler/state.js | 3 + .../svelte/src/internal/client/context.js | 40 +++- .../src/internal/client/dev/elements.js | 2 + .../src/internal/client/dom/blocks/await.js | 16 +- .../client/dom/blocks/svelte-element.js | 3 +- packages/svelte/src/internal/client/index.js | 2 +- .../src/internal/client/reactivity/effects.js | 8 +- .../src/internal/client/reactivity/types.d.ts | 10 +- .../svelte/src/internal/client/runtime.js | 9 +- .../svelte/src/internal/client/types.d.ts | 9 + .../samples/svelte-meta-parent/_config.js | 186 ++++++++++++++++++ .../samples/svelte-meta-parent/child.svelte | 1 + .../samples/svelte-meta-parent/main.svelte | 50 +++++ .../svelte-meta-parent/passthrough.svelte | 9 + 22 files changed, 408 insertions(+), 28 deletions(-) create mode 100644 .changeset/hot-buses-end.md create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/passthrough.svelte diff --git a/.changeset/hot-buses-end.md b/.changeset/hot-buses-end.md new file mode 100644 index 0000000000..dd5a28fca8 --- /dev/null +++ b/.changeset/hot-buses-end.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: add parent hierarchy to `__svelte_meta` objects diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js index 7873cf3ddb..c550c8e17b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js @@ -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, add_svelte_meta } from './shared/utils.js'; /** * @param {AST.AwaitBlock} node @@ -54,7 +54,7 @@ export function AwaitBlock(node, context) { } context.state.init.push( - b.stmt( + add_svelte_meta( b.call( '$.await', context.state.node, @@ -64,7 +64,9 @@ export function AwaitBlock(node, context) { : b.null, then_block, catch_block - ) + ), + node, + 'await' ) ); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js index 201c4b278f..353927b865 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js @@ -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, add_svelte_meta } from './shared/utils.js'; /** * @param {AST.EachBlock} node @@ -337,7 +337,7 @@ export function EachBlock(node, context) { ); } - context.state.init.push(b.stmt(b.call('$.each', ...args))); + context.state.init.push(add_svelte_meta(b.call('$.each', ...args), node, 'each')); } /** diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js index deab040e50..cfd2bb7b09 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js @@ -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, add_svelte_meta } from './shared/utils.js'; /** * @param {AST.IfBlock} node @@ -74,7 +74,7 @@ export function IfBlock(node, context) { args.push(b.id('$$elseif')); } - statements.push(b.stmt(b.call('$.if', ...args))); + statements.push(add_svelte_meta(b.call('$.if', ...args), node, 'if')); context.state.init.push(b.block(statements)); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js index 2f17479c7e..3add1fbe93 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/KeyBlock.js @@ -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, add_svelte_meta } from './shared/utils.js'; /** * @param {AST.KeyBlock} node @@ -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))) + add_svelte_meta( + b.call('$.key', context.state.node, b.thunk(key), b.arrow([b.id('$$anchor')], body)), + node, + 'key' + ) ); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js index c3615d9d50..7f1a4ae7ba 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RenderTag.js @@ -3,7 +3,7 @@ /** @import { ComponentContext } from '../types' */ import { unwrap_optional } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; -import { build_expression } from './shared/utils.js'; +import { add_svelte_meta, build_expression } from './shared/utils.js'; /** * @param {AST.RenderTag} node @@ -48,16 +48,22 @@ export function RenderTag(node, context) { } context.state.init.push( - b.stmt(b.call('$.snippet', context.state.node, b.thunk(snippet_function), ...args)) + add_svelte_meta( + b.call('$.snippet', context.state.node, b.thunk(snippet_function), ...args), + node, + 'render' + ) ); } else { context.state.init.push( - b.stmt( + add_svelte_meta( (node.expression.type === 'CallExpression' ? b.call : b.maybe_call)( snippet_function, context.state.node, ...args - ) + ), + node, + 'render' ) ); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js index cb6e4de478..aa3704b50b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js @@ -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, + add_svelte_meta +} 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'; @@ -483,7 +488,8 @@ export function build_component(node, component_name, context) { ); } else { context.state.template.push_comment(); - statements.push(b.stmt(fn(anchor))); + + statements.push(add_svelte_meta(fn(anchor), node, 'component', { componentTag: node.name })); } return statements.length > 1 ? b.block(statements) : statements[0]; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js index b80466ccc9..de74fede0c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js @@ -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, ExpressionStatement } from 'estree' */ /** @import { AST, ExpressionMetadata } from '#compiler' */ /** @import { ComponentClientTransformState, ComponentContext, Context } from '../../types' */ import { walk } from 'zimmerframe'; @@ -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, component_name } from '../../../../../state.js'; import { build_getter, create_derived } from '../../utils.js'; /** @@ -424,3 +424,34 @@ export function build_expression(context, expression, metadata, state = context. return sequence; } + +/** + * Wraps a statement/expression with dev stack tracking in dev mode + * @param {Expression} 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' | 'render'} type - Type of block/component + * @param {Record} [additional] - Any additional properties to add to the dev stack entry + * @returns {ExpressionStatement} - Statement with or without dev stack wrapping + */ +export function add_svelte_meta(expression, node, type, additional) { + if (!dev) { + return b.stmt(expression); + } + + const location = node.start !== undefined && locator(node.start); + if (!location) { + return b.stmt(expression); + } + + return b.stmt( + b.call( + '$.add_svelte_meta', + b.arrow([], expression), + b.literal(type), + b.id(component_name), + b.literal(location.line), + b.literal(location.column), + additional && b.object(Object.entries(additional).map(([k, v]) => b.init(k, b.literal(v)))) + ) + ); +} diff --git a/packages/svelte/src/compiler/state.js b/packages/svelte/src/compiler/state.js index 9095651ced..5eb25dd6bb 100644 --- a/packages/svelte/src/compiler/state.js +++ b/packages/svelte/src/compiler/state.js @@ -16,6 +16,9 @@ export let warnings = []; */ export let filename; +/** + * The name of the component that is used in the `export default function ...` statement. + */ export let component_name = ''; /** diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index 7c7213b7a2..e4220149ab 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -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'; @@ -11,6 +11,7 @@ import { } from './runtime.js'; import { effect, teardown } from './reactivity/effects.js'; import { legacy_mode_flag } from '../flags/index.js'; +import { FILENAME } from '../../constants.js'; /** @type {ComponentContext | null} */ export let component_context = null; @@ -20,6 +21,43 @@ 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 {any} component - Component function + * @param {number} line - Line number + * @param {number} column - Column number + * @param {Record} [additional] - Any additional properties to add to the dev stack entry + * @returns {any} + */ +export function add_svelte_meta(callback, type, component, line, column, additional) { + const parent = dev_stack; + + dev_stack = { + type, + file: component[FILENAME], + line, + column, + parent, + ...additional + }; + + try { + return callback(); + } finally { + dev_stack = parent; + } +} + /** * The current component function. Different from current component context: * ```html diff --git a/packages/svelte/src/internal/client/dev/elements.js b/packages/svelte/src/internal/client/dev/elements.js index f70f893d1e..8dd54e0a2a 100644 --- a/packages/svelte/src/internal/client/dev/elements.js +++ b/packages/svelte/src/internal/client/dev/elements.js @@ -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 @@ -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] } }; diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 47df5fc9a5..325224fff2 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -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; @@ -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 | typeof UNINITIALIZED} */ var input = UNINITIALIZED; @@ -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 { @@ -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); diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js index ffa57b2d8b..231a3621b1 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -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'; @@ -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], diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 60f9af9120..576a30fa77 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -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, add_svelte_meta } 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'; diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index a2806bde81..a2de8940a6 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -41,7 +41,7 @@ import { DEV } from 'esm-env'; import { define_property } from '../../shared/utils.js'; import { get_next_sibling } from '../dom/operations.js'; import { derived } from './deriveds.js'; -import { component_context, dev_current_component_function } from '../context.js'; +import { component_context, dev_current_component_function, dev_stack } from '../context.js'; /** * @param {'$effect' | '$effect.pre' | '$inspect'} rune @@ -359,7 +359,11 @@ export function template_effect(fn, thunks = [], d = derived) { * @param {number} flags */ export function block(fn, flags = 0) { - return create_effect(RENDER_EFFECT | BLOCK_EFFECT | flags, fn, true); + var effect = create_effect(RENDER_EFFECT | BLOCK_EFFECT | flags, fn, true); + if (DEV) { + effect.dev_stack = dev_stack; + } + return effect; } /** diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 88c84f27fe..80c4155705 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -1,4 +1,10 @@ -import type { ComponentContext, Dom, Equals, TemplateNode, TransitionManager } from '#client'; +import type { + ComponentContext, + DevStackEntry, + Equals, + TemplateNode, + TransitionManager +} from '#client'; export interface Signal { /** Flags bitmask */ @@ -80,6 +86,8 @@ export interface Effect extends Reaction { parent: Effect | null; /** Dev only */ component_function?: any; + /** Dev only. Only set for certain block effects. Contains a reference to the stack that represents the render tree */ + dev_stack?: DevStackEntry | null; } export type Source = Value; diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 5a798ba3e9..8e6242447e 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -34,12 +34,13 @@ import { tracing_expressions, get_stack } from './dev/tracing.js'; import { component_context, dev_current_component_function, + dev_stack, is_runes, set_component_context, - set_dev_current_component_function + set_dev_current_component_function, + set_dev_stack } from './context.js'; import { handle_error, invoke_error_boundary } from './error-handling.js'; -import { snapshot } from '../shared/clone.js'; let is_flushing = false; @@ -444,6 +445,9 @@ export function update_effect(effect) { if (DEV) { var previous_component_fn = dev_current_component_function; set_dev_current_component_function(effect.component_function); + var previous_stack = /** @type {any} */ (dev_stack); + // only block effects have a dev stack, keep the current one otherwise + set_dev_stack(effect.dev_stack ?? dev_stack); } try { @@ -478,6 +482,7 @@ export function update_effect(effect) { if (DEV) { set_dev_current_component_function(previous_component_fn); + set_dev_stack(previous_stack); } } } diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 9703c2aac1..0b7310e172 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -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' | 'render'; + line: number; + column: number; + parent: DevStackEntry | null; + componentTag?: string; +} + export * from './reactivity/types'; diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/_config.js new file mode 100644 index 0000000000..eba85d5098 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/_config.js @@ -0,0 +1,186 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client', 'hydrate'], + compileOptions: { + dev: true + }, + html: ` +

no parent

+ +

if

+

each

+

loading

+

key

+

hi

+

hi

+

hi

+

hi

+

hi

+ `, + + async test({ target, assert }) { + function check() { + const [main, if_, each, await_, key, child1, child2, child3, child4, dynamic] = + target.querySelectorAll('p'); + + // @ts-expect-error + assert.deepEqual(main.__svelte_meta.parent, null); + + // @ts-expect-error + assert.deepEqual(if_.__svelte_meta.parent, { + file: 'main.svelte', + type: 'if', + line: 12, + column: 0, + parent: null + }); + + // @ts-expect-error + assert.deepEqual(each.__svelte_meta.parent, { + file: 'main.svelte', + type: 'each', + line: 16, + column: 0, + parent: null + }); + + // @ts-expect-error + assert.deepEqual(await_.__svelte_meta.parent, { + file: 'main.svelte', + type: 'await', + line: 20, + column: 0, + parent: null + }); + + // @ts-expect-error + assert.deepEqual(key.__svelte_meta.parent, { + file: 'main.svelte', + type: 'key', + line: 26, + column: 0, + parent: null + }); + + // @ts-expect-error + assert.deepEqual(child1.__svelte_meta.parent, { + file: 'main.svelte', + type: 'component', + componentTag: 'Child', + line: 30, + column: 0, + parent: null + }); + + // @ts-expect-error + assert.deepEqual(child2.__svelte_meta.parent, { + file: 'main.svelte', + type: 'component', + componentTag: 'Child', + line: 33, + column: 1, + parent: { + file: 'passthrough.svelte', + type: 'render', + line: 5, + column: 0, + parent: { + file: 'main.svelte', + type: 'component', + componentTag: 'Passthrough', + line: 32, + column: 0, + parent: null + } + } + }); + + // @ts-expect-error + assert.deepEqual(child3.__svelte_meta.parent, { + file: 'main.svelte', + type: 'component', + componentTag: 'Child', + line: 38, + column: 2, + parent: { + file: 'passthrough.svelte', + type: 'render', + line: 5, + column: 0, + parent: { + file: 'main.svelte', + type: 'component', + componentTag: 'Passthrough', + line: 37, + column: 1, + parent: { + file: 'passthrough.svelte', + type: 'render', + line: 5, + column: 0, + parent: { + file: 'main.svelte', + type: 'component', + componentTag: 'Passthrough', + line: 36, + column: 0, + parent: null + } + } + } + } + }); + + // @ts-expect-error + assert.deepEqual(child4.__svelte_meta.parent, { + file: 'passthrough.svelte', + type: 'render', + line: 8, + column: 1, + parent: { + file: 'passthrough.svelte', + type: 'if', + line: 7, + column: 0, + parent: { + file: 'main.svelte', + type: 'component', + componentTag: 'Passthrough', + line: 43, + column: 1, + parent: { + file: 'main.svelte', + type: 'if', + line: 42, + column: 0, + parent: null + } + } + } + }); + + // @ts-expect-error + assert.deepEqual(dynamic.__svelte_meta.parent, { + file: 'main.svelte', + type: 'component', + componentTag: 'x.y', + line: 50, + column: 0, + parent: null + }); + } + + await tick(); + check(); + + // Test that stack is kept when re-rendering + const button = target.querySelector('button'); + button?.click(); + await tick(); + button?.click(); + await tick(); + check(); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/child.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/child.svelte new file mode 100644 index 0000000000..0df6def593 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/child.svelte @@ -0,0 +1 @@ +

hi

\ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/main.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/main.svelte new file mode 100644 index 0000000000..b9bd46d8f7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/main.svelte @@ -0,0 +1,50 @@ + + +

no parent

+ + +{#if true} +

if

+{/if} + +{#each [1]} +

each

+{/each} + +{#await Promise.resolve()} +

loading

+{:then} +

await

+{/await} + +{#key key} +

key

+{/key} + + + + + + + + + + + + + +{#if show} + + {#snippet named()} +

hi

+ {/snippet} +
+{/if} + + diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/passthrough.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/passthrough.svelte new file mode 100644 index 0000000000..70ba81a4c1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-meta-parent/passthrough.svelte @@ -0,0 +1,9 @@ + + +{@render children?.()} + +{#if true} + {@render named?.()} +{/if} From 49cba86e9b604f66bcd037e5eeb7a27788510b3b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 2 Jul 2025 11:19:51 -0400 Subject: [PATCH 03/13] chore: rename EFFECT_HAS_DERIVED to EFFECT_PRESERVED (#16283) --- packages/svelte/src/internal/client/constants.js | 2 +- packages/svelte/src/internal/client/reactivity/deriveds.js | 4 ++-- packages/svelte/src/internal/client/reactivity/effects.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index cd5e0d2244..38db28fecf 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -17,7 +17,7 @@ export const EFFECT_RAN = 1 << 15; export const EFFECT_TRANSPARENT = 1 << 16; export const INSPECT_EFFECT = 1 << 18; export const HEAD_EFFECT = 1 << 19; -export const EFFECT_HAS_DERIVED = 1 << 20; +export const EFFECT_PRESERVED = 1 << 20; export const EFFECT_IS_UPDATING = 1 << 21; export const STATE_SYMBOL = Symbol('$state'); diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index d3123d24a1..fdfc13c0e2 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -1,6 +1,6 @@ /** @import { Derived, Effect } from '#client' */ import { DEV } from 'esm-env'; -import { CLEAN, DERIVED, DIRTY, EFFECT_HAS_DERIVED, MAYBE_DIRTY, UNOWNED } from '#client/constants'; +import { CLEAN, DERIVED, DIRTY, EFFECT_PRESERVED, MAYBE_DIRTY, UNOWNED } from '#client/constants'; import { active_reaction, active_effect, @@ -38,7 +38,7 @@ export function derived(fn) { } else { // Since deriveds are evaluated lazily, any effects created inside them are // created too late to ensure that the parent effect is added to the tree - active_effect.f |= EFFECT_HAS_DERIVED; + active_effect.f |= EFFECT_PRESERVED; } /** @type {Derived} */ diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index a2de8940a6..702c410ce8 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -31,7 +31,7 @@ import { INSPECT_EFFECT, HEAD_EFFECT, MAYBE_DIRTY, - EFFECT_HAS_DERIVED, + EFFECT_PRESERVED, BOUNDARY_EFFECT, STALE_REACTION } from '#client/constants'; @@ -135,7 +135,7 @@ function create_effect(type, fn, sync, push = true) { effect.first === null && effect.nodes_start === null && effect.teardown === null && - (effect.f & (EFFECT_HAS_DERIVED | BOUNDARY_EFFECT)) === 0; + (effect.f & (EFFECT_PRESERVED | BOUNDARY_EFFECT)) === 0; if (!inert && push) { if (parent !== null) { From 7914cb183554b16627bec40c8e91b163beaf0480 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 2 Jul 2025 12:16:53 -0400 Subject: [PATCH 04/13] chore: convert boundary implementation to a class (#16284) * WIP class boundaries * WIP * WIP * WIP * unused * unused * unused --- .../internal/client/dom/blocks/boundary.js | 233 +++++++++++------- .../src/internal/client/error-handling.js | 4 +- .../src/internal/client/reactivity/effects.js | 1 + .../src/internal/client/reactivity/types.d.ts | 3 + 4 files changed, 149 insertions(+), 92 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index d7a53da1d1..03cec4fd2f 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -1,6 +1,5 @@ /** @import { Effect, TemplateNode, } from '#client' */ - -import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '#client/constants'; +import { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants'; import { component_context, set_component_context } from '../../context.js'; import { invoke_error_boundary } from '../../error-handling.js'; import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js'; @@ -21,116 +20,170 @@ import { import { queue_micro_task } from '../task.js'; /** - * @param {Effect} boundary - * @param {() => void} fn + * @typedef {{ + * onerror?: (error: unknown, reset: () => void) => void; + * failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void; + * }} BoundaryProps */ -function with_boundary(boundary, fn) { - var previous_effect = active_effect; - var previous_reaction = active_reaction; - var previous_ctx = component_context; - - set_active_effect(boundary); - set_active_reaction(boundary); - set_component_context(boundary.ctx); - - try { - fn(); - } finally { - set_active_effect(previous_effect); - set_active_reaction(previous_reaction); - set_component_context(previous_ctx); - } -} + +var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_EFFECT; /** * @param {TemplateNode} node - * @param {{ - * onerror?: (error: unknown, reset: () => void) => void, - * failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void - * }} props - * @param {((anchor: Node) => void)} boundary_fn + * @param {BoundaryProps} props + * @param {((anchor: Node) => void)} children * @returns {void} */ -export function boundary(node, props, boundary_fn) { - var anchor = node; +export function boundary(node, props, children) { + new Boundary(node, props, children); +} + +export class Boundary { + /** @type {TemplateNode} */ + #anchor; + + /** @type {TemplateNode} */ + #hydrate_open; + + /** @type {BoundaryProps} */ + #props; + + /** @type {((anchor: Node) => void)} */ + #children; /** @type {Effect} */ - var boundary_effect; - - block(() => { - var boundary = /** @type {Effect} */ (active_effect); - var hydrate_open = hydrate_node; - var is_creating_fallback = false; - - // We re-use the effect's fn property to avoid allocation of an additional field - boundary.fn = (/** @type {unknown}} */ error) => { - var onerror = props.onerror; - let failed = props.failed; - - // If we have nothing to capture the error, or if we hit an error while - // rendering the fallback, re-throw for another boundary to handle - if ((!onerror && !failed) || is_creating_fallback) { - throw error; - } + #effect; - var reset = () => { - pause_effect(boundary_effect); + /** @type {Effect | null} */ + #main_effect = null; - with_boundary(boundary, () => { - is_creating_fallback = false; - boundary_effect = branch(() => boundary_fn(anchor)); - }); - }; + /** @type {Effect | null} */ + #failed_effect = null; - var previous_reaction = active_reaction; + #is_creating_fallback = false; - try { - set_active_reaction(null); - onerror?.(error, reset); - } finally { - set_active_reaction(previous_reaction); + /** + * @param {TemplateNode} node + * @param {BoundaryProps} props + * @param {((anchor: Node) => void)} children + */ + constructor(node, props, children) { + this.#anchor = node; + this.#props = props; + this.#children = children; + + this.#hydrate_open = hydrate_node; + + this.#effect = block(() => { + /** @type {Effect} */ (active_effect).b = this; + + if (hydrating) { + hydrate_next(); } - if (boundary_effect) { - destroy_effect(boundary_effect); - } else if (hydrating) { - set_hydrate_node(hydrate_open); - next(); - set_hydrate_node(remove_nodes()); + try { + this.#main_effect = branch(() => children(this.#anchor)); + } catch (error) { + this.error(error); } + }, flags); - if (failed) { - // Render the `failed` snippet in a microtask - queue_micro_task(() => { - with_boundary(boundary, () => { - is_creating_fallback = true; - - try { - boundary_effect = branch(() => { - failed( - anchor, - () => error, - () => reset - ); - }); - } catch (error) { - invoke_error_boundary(error, /** @type {Effect} */ (boundary.parent)); - } - - is_creating_fallback = false; - }); + if (hydrating) { + this.#anchor = hydrate_node; + } + } + + /** + * @param {() => Effect | null} fn + */ + #run(fn) { + var previous_effect = active_effect; + var previous_reaction = active_reaction; + var previous_ctx = component_context; + + set_active_effect(this.#effect); + set_active_reaction(this.#effect); + set_component_context(this.#effect.ctx); + + try { + return fn(); + } finally { + set_active_effect(previous_effect); + set_active_reaction(previous_reaction); + set_component_context(previous_ctx); + } + } + + /** @param {unknown} error */ + error(error) { + var onerror = this.#props.onerror; + let failed = this.#props.failed; + + const reset = () => { + if (this.#failed_effect !== null) { + pause_effect(this.#failed_effect, () => { + this.#failed_effect = null; }); } + + this.#main_effect = this.#run(() => { + this.#is_creating_fallback = false; + return branch(() => this.#children(this.#anchor)); + }); }; - if (hydrating) { - hydrate_next(); + // If we have nothing to capture the error, or if we hit an error while + // rendering the fallback, re-throw for another boundary to handle + if (this.#is_creating_fallback || (!onerror && !failed)) { + throw error; + } + + var previous_reaction = active_reaction; + + try { + set_active_reaction(null); + onerror?.(error, reset); + } finally { + set_active_reaction(previous_reaction); } - boundary_effect = branch(() => boundary_fn(anchor)); - }, EFFECT_TRANSPARENT | BOUNDARY_EFFECT); + if (this.#main_effect) { + destroy_effect(this.#main_effect); + this.#main_effect = null; + } - if (hydrating) { - anchor = hydrate_node; + if (this.#failed_effect) { + destroy_effect(this.#failed_effect); + this.#failed_effect = null; + } + + if (hydrating) { + set_hydrate_node(this.#hydrate_open); + next(); + set_hydrate_node(remove_nodes()); + } + + if (failed) { + queue_micro_task(() => { + this.#failed_effect = this.#run(() => { + this.#is_creating_fallback = true; + + try { + return branch(() => { + failed( + this.#anchor, + () => error, + () => reset + ); + }); + } catch (error) { + invoke_error_boundary(error, /** @type {Effect} */ (this.#effect.parent)); + return null; + } finally { + this.#is_creating_fallback = false; + } + }); + }); + } } } diff --git a/packages/svelte/src/internal/client/error-handling.js b/packages/svelte/src/internal/client/error-handling.js index 99f3ed6cd4..378f7408ef 100644 --- a/packages/svelte/src/internal/client/error-handling.js +++ b/packages/svelte/src/internal/client/error-handling.js @@ -1,4 +1,5 @@ /** @import { Effect } from '#client' */ +/** @import { Boundary } from './dom/blocks/boundary.js' */ import { DEV } from 'esm-env'; import { FILENAME } from '../../constants.js'; import { is_firefox } from './dom/operations.js'; @@ -39,8 +40,7 @@ export function invoke_error_boundary(error, effect) { while (effect !== null) { if ((effect.f & BOUNDARY_EFFECT) !== 0) { try { - // @ts-expect-error - effect.fn(error); + /** @type {Boundary} */ (effect.b).error(error); return; } catch {} } diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 702c410ce8..7570064c37 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -104,6 +104,7 @@ function create_effect(type, fn, sync, push = true) { last: null, next: null, parent, + b: parent && parent.b, prev: null, teardown: null, transitions: null, diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 80c4155705..90f922df68 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -5,6 +5,7 @@ import type { TemplateNode, TransitionManager } from '#client'; +import type { Boundary } from '../dom/blocks/boundary'; export interface Signal { /** Flags bitmask */ @@ -84,6 +85,8 @@ export interface Effect extends Reaction { last: null | Effect; /** Parent effect */ parent: Effect | null; + /** The boundary this effect belongs to */ + b: Boundary | null; /** Dev only */ component_function?: any; /** Dev only. Only set for certain block effects. Contains a reference to the stack that represents the render tree */ From dde791311d3c6a7b28e276548e6beb45a9f2e798 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 2 Jul 2025 13:06:45 -0400 Subject: [PATCH 05/13] chore: minor tweaks (#16285) --- packages/svelte/src/internal/client/constants.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 38db28fecf..1cffa43940 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -15,17 +15,17 @@ export const DESTROYED = 1 << 14; export const EFFECT_RAN = 1 << 15; /** 'Transparent' effects do not create a transition boundary */ export const EFFECT_TRANSPARENT = 1 << 16; -export const INSPECT_EFFECT = 1 << 18; -export const HEAD_EFFECT = 1 << 19; -export const EFFECT_PRESERVED = 1 << 20; -export const EFFECT_IS_UPDATING = 1 << 21; +export const INSPECT_EFFECT = 1 << 17; +export const HEAD_EFFECT = 1 << 18; +export const EFFECT_PRESERVED = 1 << 19; +export const EFFECT_IS_UPDATING = 1 << 20; export const STATE_SYMBOL = Symbol('$state'); export const LEGACY_PROPS = Symbol('legacy props'); export const LOADING_ATTR_SYMBOL = Symbol(''); export const PROXY_PATH_SYMBOL = Symbol('proxy path'); -// allow users to ignore aborted signal errors if `reason.stale` +/** allow users to ignore aborted signal errors if `reason.name === 'StaleReactionError` */ export const STALE_REACTION = new (class StaleReactionError extends Error { name = 'StaleReactionError'; message = 'The reaction that called `getAbortSignal()` was re-run or destroyed'; From 5ebbedf2051715f1df10a884976d7d23b1d7f5fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:18:28 +0200 Subject: [PATCH 06/13] Version Packages (#16281) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/hot-buses-end.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/hot-buses-end.md diff --git a/.changeset/hot-buses-end.md b/.changeset/hot-buses-end.md deleted file mode 100644 index dd5a28fca8..0000000000 --- a/.changeset/hot-buses-end.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -feat: add parent hierarchy to `__svelte_meta` objects diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 7589eda728..fb5d43c9dd 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.35.1 + +### Patch Changes + +- feat: add parent hierarchy to `__svelte_meta` objects ([#16255](https://github.com/sveltejs/svelte/pull/16255)) + ## 5.35.0 ### Minor Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index e8c28aeaa1..b1e57b9829 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.35.0", + "version": "5.35.1", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index baa2dbd046..2efcf03eb1 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.35.0'; +export const VERSION = '5.35.1'; export const PUBLIC_VERSION = '5'; From eeb32a50d14d2ac69970b0e207746092a34d2fc3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Jul 2025 11:47:26 -0400 Subject: [PATCH 07/13] fix: bump esrap (#16295) --- .changeset/fifty-hats-watch.md | 5 +++++ packages/svelte/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 .changeset/fifty-hats-watch.md diff --git a/.changeset/fifty-hats-watch.md b/.changeset/fifty-hats-watch.md new file mode 100644 index 0000000000..83bb72e735 --- /dev/null +++ b/.changeset/fifty-hats-watch.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: bump esrap diff --git a/packages/svelte/package.json b/packages/svelte/package.json index b1e57b9829..b6776a8fbc 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -171,7 +171,7 @@ "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", - "esrap": "^2.0.0", + "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7fb655b2d..87fb2b2667 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,8 +87,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 esrap: - specifier: ^2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 is-reference: specifier: ^3.0.3 version: 3.0.3 @@ -1261,8 +1261,8 @@ packages: resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} - esrap@2.0.0: - resolution: {integrity: sha512-zhw1TDqno99Ld5wOpe0t47rzVyxfGc1fvxNzPsqk4idUf5dcAePkAyfTceLJaSTytjiWDu26S5tI+grjvymXJA==} + esrap@2.1.0: + resolution: {integrity: sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -3622,7 +3622,7 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.0.0: + esrap@2.1.0: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 From 220e901a825a23df7c310cd5978d3141e573e645 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 11:50:58 -0400 Subject: [PATCH 08/13] Version Packages (#16297) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/fifty-hats-watch.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/fifty-hats-watch.md diff --git a/.changeset/fifty-hats-watch.md b/.changeset/fifty-hats-watch.md deleted file mode 100644 index 83bb72e735..0000000000 --- a/.changeset/fifty-hats-watch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: bump esrap diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index fb5d43c9dd..ad72bfabb0 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.35.2 + +### Patch Changes + +- fix: bump esrap ([#16295](https://github.com/sveltejs/svelte/pull/16295)) + ## 5.35.1 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index b6776a8fbc..872c21a179 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.35.1", + "version": "5.35.2", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 2efcf03eb1..47c354c7a2 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.35.1'; +export const VERSION = '5.35.2'; export const PUBLIC_VERSION = '5'; From 432763a03ebcb4c03e7c5084cb52aac6f1afaa22 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Jul 2025 12:11:28 -0400 Subject: [PATCH 09/13] chore: `reaction_sources` -> `source_ownership` (#16287) * reaction_sources -> source_ownership * put equality statements first, it's faster --- .../src/internal/client/reactivity/sources.js | 4 +-- .../svelte/src/internal/client/runtime.js | 28 ++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 0db3530232..a86ccfee4f 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -11,7 +11,7 @@ import { untrack, increment_write_version, update_effect, - reaction_sources, + source_ownership, check_dirtiness, untracking, is_destroying_effect, @@ -140,7 +140,7 @@ export function set(source, value, should_proxy = false) { (!untracking || (active_reaction.f & INSPECT_EFFECT) !== 0) && is_runes() && (active_reaction.f & (DERIVED | BLOCK_EFFECT | INSPECT_EFFECT)) !== 0 && - !(reaction_sources?.[1].includes(source) && reaction_sources[0] === active_reaction) + !(source_ownership?.reaction === active_reaction && source_ownership.sources.includes(source)) ) { e.state_unsafe_mutation(); } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 8e6242447e..fce6c78b56 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -86,17 +86,17 @@ export function set_active_effect(effect) { /** * When sources are created within a reaction, reading and writing * them within that reaction should not cause a re-run - * @type {null | [active_reaction: Reaction, sources: Source[]]} + * @type {null | { reaction: Reaction, sources: Source[] }} */ -export let reaction_sources = null; +export let source_ownership = null; /** @param {Value} value */ export function push_reaction_value(value) { if (active_reaction !== null && active_reaction.f & EFFECT_IS_UPDATING) { - if (reaction_sources === null) { - reaction_sources = [active_reaction, [value]]; + if (source_ownership === null) { + source_ownership = { reaction: active_reaction, sources: [value] }; } else { - reaction_sources[1].push(value); + source_ownership.sources.push(value); } } } @@ -235,7 +235,12 @@ function schedule_possible_effect_self_invalidation(signal, effect, root = true) for (var i = 0; i < reactions.length; i++) { var reaction = reactions[i]; - if (reaction_sources?.[1].includes(signal) && reaction_sources[0] === active_reaction) continue; + if ( + source_ownership?.reaction === active_reaction && + source_ownership.sources.includes(signal) + ) { + continue; + } if ((reaction.f & DERIVED) !== 0) { schedule_possible_effect_self_invalidation(/** @type {Derived} */ (reaction), effect, false); @@ -257,7 +262,7 @@ export function update_reaction(reaction) { var previous_untracked_writes = untracked_writes; var previous_reaction = active_reaction; var previous_skip_reaction = skip_reaction; - var previous_reaction_sources = reaction_sources; + var previous_reaction_sources = source_ownership; var previous_component_context = component_context; var previous_untracking = untracking; @@ -270,7 +275,7 @@ export function update_reaction(reaction) { (flags & UNOWNED) !== 0 && (untracking || !is_updating_effect || active_reaction === null); active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null; - reaction_sources = null; + source_ownership = null; set_component_context(reaction.ctx); untracking = false; read_version++; @@ -358,7 +363,7 @@ export function update_reaction(reaction) { untracked_writes = previous_untracked_writes; active_reaction = previous_reaction; skip_reaction = previous_skip_reaction; - reaction_sources = previous_reaction_sources; + source_ownership = previous_reaction_sources; set_component_context(previous_component_context); untracking = previous_untracking; @@ -739,7 +744,10 @@ export function get(signal) { // Register the dependency on the current reaction signal. if (active_reaction !== null && !untracking) { - if (!reaction_sources?.[1].includes(signal) || reaction_sources[0] !== active_reaction) { + if ( + source_ownership?.reaction !== active_reaction || + !source_ownership?.sources.includes(signal) + ) { var deps = active_reaction.deps; if (signal.rv < read_version) { signal.rv = read_version; From c3348aea06b5dcc13159b96053351f36d6cb6d71 Mon Sep 17 00:00:00 2001 From: 7nik Date: Fri, 4 Jul 2025 17:54:37 +0300 Subject: [PATCH 10/13] fix: do not proxify the value assigned to a derived (#16302) --- .changeset/new-wolves-punch.md | 5 ++++ .../client/visitors/AssignmentExpression.js | 1 + .../samples/writable-derived-4/_config.js | 23 +++++++++++++++++++ .../samples/writable-derived-4/main.svelte | 9 ++++++++ 4 files changed, 38 insertions(+) create mode 100644 .changeset/new-wolves-punch.md create mode 100644 packages/svelte/tests/runtime-runes/samples/writable-derived-4/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/writable-derived-4/main.svelte diff --git a/.changeset/new-wolves-punch.md b/.changeset/new-wolves-punch.md new file mode 100644 index 0000000000..c856038cbc --- /dev/null +++ b/.changeset/new-wolves-punch.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: do not proxify the value assigned to a derived diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js index ce190814f8..7d64d60bca 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js @@ -137,6 +137,7 @@ function build_assignment(operator, left, right, context) { binding.kind !== 'prop' && binding.kind !== 'bindable_prop' && binding.kind !== 'raw_state' && + binding.kind !== 'derived' && binding.kind !== 'store_sub' && context.state.analysis.runes && should_proxy(right, context.state.scope) && diff --git a/packages/svelte/tests/runtime-runes/samples/writable-derived-4/_config.js b/packages/svelte/tests/runtime-runes/samples/writable-derived-4/_config.js new file mode 100644 index 0000000000..8d312ca73a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/writable-derived-4/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target }) { + const [btn1, btn2, btn3] = target.getElementsByTagName('button'); + const [div] = target.getElementsByTagName('div'); + + flushSync(() => btn1.click()); + assert.equal(div.textContent, '1'); + flushSync(() => btn2.click()); + assert.equal(div.textContent, '1'); + flushSync(() => btn3.click()); + assert.equal(div.textContent, '2'); + + flushSync(() => btn1.click()); + assert.equal(div.textContent, '2'); + flushSync(() => btn2.click()); + assert.equal(div.textContent, '2'); + flushSync(() => btn3.click()); + assert.equal(div.textContent, '3'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/writable-derived-4/main.svelte b/packages/svelte/tests/runtime-runes/samples/writable-derived-4/main.svelte new file mode 100644 index 0000000000..a24f69ba77 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/writable-derived-4/main.svelte @@ -0,0 +1,9 @@ + + + + + + +
{count.value}
From 834cd91b367172859b26d35a23aad4e87800b881 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Sun, 6 Jul 2025 14:57:45 +0200 Subject: [PATCH 11/13] fix: account for mounting when `select_option` in `attribute_effect` (#16309) --- .changeset/curvy-carrots-prove.md | 5 +++++ .../src/internal/client/dom/elements/attributes.js | 5 +++-- .../samples/select-spread-option-selected/_config.js | 9 +++++++++ .../samples/select-spread-option-selected/main.svelte | 8 ++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 .changeset/curvy-carrots-prove.md create mode 100644 packages/svelte/tests/runtime-runes/samples/select-spread-option-selected/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/select-spread-option-selected/main.svelte diff --git a/.changeset/curvy-carrots-prove.md b/.changeset/curvy-carrots-prove.md new file mode 100644 index 0000000000..7723990179 --- /dev/null +++ b/.changeset/curvy-carrots-prove.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: account for mounting when `select_option` in `attribute_effect` diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 5db685cf3e..c96338778f 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -514,9 +514,10 @@ export function attribute_effect( if (is_select) { var select = /** @type {HTMLSelectElement} */ (element); - + var mounting = true; effect(() => { - select_option(select, /** @type {Record} */ (prev).value); + select_option(select, /** @type {Record} */ (prev).value, mounting); + mounting = false; init_select(select); }); } diff --git a/packages/svelte/tests/runtime-runes/samples/select-spread-option-selected/_config.js b/packages/svelte/tests/runtime-runes/samples/select-spread-option-selected/_config.js new file mode 100644 index 0000000000..50da3ac095 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/select-spread-option-selected/_config.js @@ -0,0 +1,9 @@ +import { ok, test } from '../../test'; + +export default test({ + async test({ assert, target, instance }) { + const select = target.querySelector('select'); + ok(select); + assert.equal(select.selectedIndex, 1); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/select-spread-option-selected/main.svelte b/packages/svelte/tests/runtime-runes/samples/select-spread-option-selected/main.svelte new file mode 100644 index 0000000000..e5fc6c5535 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/select-spread-option-selected/main.svelte @@ -0,0 +1,8 @@ + + + From 4a26edf54576152affe8649eca8c9b3d1a5b8015 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 7 Jul 2025 10:17:17 -0400 Subject: [PATCH 12/13] chore: remove unused `mounting` variable from `select_option` call (#16312) --- .../svelte/src/internal/client/dom/elements/attributes.js | 7 +++---- .../src/internal/client/dom/elements/bindings/select.js | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index c96338778f..66f484a59a 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -491,7 +491,7 @@ export function attribute_effect( var current = set_attributes(element, prev, next, css_hash, skip_warning); if (inited && is_select && 'value' in next) { - select_option(/** @type {HTMLSelectElement} */ (element), next.value, false); + select_option(/** @type {HTMLSelectElement} */ (element), next.value); } for (let symbol of Object.getOwnPropertySymbols(effects)) { @@ -514,10 +514,9 @@ export function attribute_effect( if (is_select) { var select = /** @type {HTMLSelectElement} */ (element); - var mounting = true; + effect(() => { - select_option(select, /** @type {Record} */ (prev).value, mounting); - mounting = false; + select_option(select, /** @type {Record} */ (prev).value, true); init_select(select); }); } diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/select.js b/packages/svelte/src/internal/client/dom/elements/bindings/select.js index 5e89686d86..e39fb865cd 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/select.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/select.js @@ -9,9 +9,9 @@ import * as w from '../../../warnings.js'; * @template V * @param {HTMLSelectElement} select * @param {V} value - * @param {boolean} [mounting] + * @param {boolean} mounting */ -export function select_option(select, value, mounting) { +export function select_option(select, value, mounting = false) { if (select.multiple) { // If value is null or undefined, keep the selection as is if (value == undefined) { From 63e48365fbad844f5a82cf5e5a994fdfd13f419b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:53:41 -0400 Subject: [PATCH 13/13] Version Packages (#16303) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/curvy-carrots-prove.md | 5 ----- .changeset/new-wolves-punch.md | 5 ----- packages/svelte/CHANGELOG.md | 8 ++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) delete mode 100644 .changeset/curvy-carrots-prove.md delete mode 100644 .changeset/new-wolves-punch.md diff --git a/.changeset/curvy-carrots-prove.md b/.changeset/curvy-carrots-prove.md deleted file mode 100644 index 7723990179..0000000000 --- a/.changeset/curvy-carrots-prove.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: account for mounting when `select_option` in `attribute_effect` diff --git a/.changeset/new-wolves-punch.md b/.changeset/new-wolves-punch.md deleted file mode 100644 index c856038cbc..0000000000 --- a/.changeset/new-wolves-punch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: do not proxify the value assigned to a derived diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index ad72bfabb0..4f1be5e299 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.35.3 + +### Patch Changes + +- fix: account for mounting when `select_option` in `attribute_effect` ([#16309](https://github.com/sveltejs/svelte/pull/16309)) + +- fix: do not proxify the value assigned to a derived ([#16302](https://github.com/sveltejs/svelte/pull/16302)) + ## 5.35.2 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 872c21a179..30a8f8833a 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.35.2", + "version": "5.35.3", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 47c354c7a2..d98b622908 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.35.2'; +export const VERSION = '5.35.3'; export const PUBLIC_VERSION = '5';