From 75c448cb72975b80946f9caa7c4fcb15d2845ef7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 14:18:28 -0400 Subject: [PATCH 01/12] remove PROXY_REMOVE_PATH --- .../client/visitors/VariableDeclaration.js | 25 +++++++------------ .../svelte/src/internal/client/constants.js | 2 -- packages/svelte/src/internal/client/proxy.js | 1 - .../src/internal/client/reactivity/sources.js | 7 ++---- 4 files changed, 11 insertions(+), 24 deletions(-) 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 13869229b5b4..b531f0df6e41 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 @@ -9,7 +9,6 @@ import { get_rune } from '../../../scope.js'; import { get_prop_source, is_prop_source, is_state_source, should_proxy } from '../utils.js'; import { is_hoisted_function } from '../../utils.js'; import { get_value } from './shared/declarations.js'; -import { PROXY_REMOVE_PATH } from '#client/constants'; /** * @param {VariableDeclaration} node @@ -90,12 +89,7 @@ export function VariableDeclaration(node, context) { binding.kind === 'bindable_prop' && should_proxy(initial, context.state.scope) ) { - initial = b.call( - '$.proxy', - initial, - dev ? b.literal(id.name) : undefined, - dev ? b.literal(PROXY_REMOVE_PATH) : undefined - ); + initial = b.call('$.proxy', initial, dev ? b.literal(id.name) : undefined); } if (is_prop_source(binding, context.state)) { @@ -136,20 +130,19 @@ export function VariableDeclaration(node, context) { ); const is_state = is_state_source(binding, context.state.analysis); const is_proxy = should_proxy(value, context.state.scope); + if (rune === '$state' && is_proxy) { - value = b.call( - '$.proxy', - value, - dev ? b.literal(id.name) : undefined, - dev ? b.literal(PROXY_REMOVE_PATH) : undefined - ); + value = b.call('$.proxy', value, dev ? b.literal(id.name) : undefined); } + if (is_state) { value = b.call('$.state', value); + + if (dev) { + value = b.call('$.tag', value, b.literal(id.name)); + } } - if (dev && is_state) { - value = b.call('$.tag', value, b.literal(id.name)); - } + return value; }; diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 98695bf75107..268ba29c883a 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -27,8 +27,6 @@ export const EFFECT_IS_UPDATING = 1 << 21; export const PROXY_PRESERVE_PATH = 1 << 1; /** Change proxy path to new "owner" */ export const PROXY_CHANGE_PATH = 1 << 2; -/** "Unown" proxy, so its path becomes `[$state proxy]` */ -export const PROXY_REMOVE_PATH = 1 << 3; export const STATE_SYMBOL = Symbol('$state'); export const LEGACY_PROPS = Symbol('legacy props'); diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 993c6f45a27c..6237655e4922 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -13,7 +13,6 @@ import { PROXY_CHANGE_PATH, PROXY_PATH_SYMBOL, PROXY_PRESERVE_PATH, - PROXY_REMOVE_PATH, STATE_SYMBOL } from '#client/constants'; import { UNINITIALIZED } from '../../constants.js'; diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 847f14d2eca9..ad5ad3383d14 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -27,8 +27,7 @@ import { UNOWNED, MAYBE_DIRTY, BLOCK_EFFECT, - ROOT_EFFECT, - PROXY_REMOVE_PATH + ROOT_EFFECT } from '#client/constants'; import * as e from '../errors.js'; import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js'; @@ -140,9 +139,7 @@ export function set(source, value, should_proxy = false) { e.state_unsafe_mutation(); } - let new_value = should_proxy - ? proxy(value, DEV ? source.trace_name : undefined, DEV ? PROXY_REMOVE_PATH : undefined) - : value; + let new_value = should_proxy ? proxy(value, DEV ? source.trace_name : undefined) : value; return internal_set(source, new_value); } From 4e7f26101dfd08e649af83954bc30c0529ea44cb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 14:39:51 -0400 Subject: [PATCH 02/12] simplify a bit --- packages/svelte/src/internal/client/proxy.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 6237655e4922..66dd5b754970 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -24,21 +24,20 @@ import { tracing_mode_flag } from '../flags/index.js'; * @template T * @param {T} value * @param {string} [path] - * @param {number} [path_preservation] + * @param {PROXY_CHANGE_PATH | PROXY_PRESERVE_PATH} path_preservation * @returns {T} */ export function proxy(value, path, path_preservation = PROXY_PRESERVE_PATH) { // if `DEV`, change the proxy `path` since we don't know if its still "owned" by its original source if ( DEV && - (path_preservation & PROXY_PRESERVE_PATH) === 0 && + (path_preservation & PROXY_CHANGE_PATH) !== 0 && typeof value === 'object' && value !== null && STATE_SYMBOL in value && PROXY_PATH_SYMBOL in value ) { - value[PROXY_PATH_SYMBOL] = - (path_preservation & PROXY_CHANGE_PATH) === 0 ? '[$state proxy]' : path; + value[PROXY_PATH_SYMBOL] = path; } // if non-proxyable, or is already a proxy, return `value` if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) { From a7a08d58603616649d3f9c88677456c23a168fe5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 16:00:35 -0400 Subject: [PATCH 03/12] simplify --- .../svelte/src/internal/client/constants.js | 6 ------ packages/svelte/src/internal/client/proxy.js | 17 ++++++----------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 268ba29c883a..98cef658bf6c 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -22,12 +22,6 @@ export const HEAD_EFFECT = 1 << 19; export const EFFECT_HAS_DERIVED = 1 << 20; export const EFFECT_IS_UPDATING = 1 << 21; -// `$inspect.trace` proxy path flags -/** Keep path the same */ -export const PROXY_PRESERVE_PATH = 1 << 1; -/** Change proxy path to new "owner" */ -export const PROXY_CHANGE_PATH = 1 << 2; - export const STATE_SYMBOL = Symbol('$state'); export const LEGACY_PROPS = Symbol('legacy props'); export const LOADING_ATTR_SYMBOL = Symbol(''); diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 66dd5b754970..cd3fbb324931 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -9,12 +9,7 @@ import { object_prototype } from '../shared/utils.js'; import { state as source, set } from './reactivity/sources.js'; -import { - PROXY_CHANGE_PATH, - PROXY_PATH_SYMBOL, - PROXY_PRESERVE_PATH, - STATE_SYMBOL -} from '#client/constants'; +import { PROXY_PATH_SYMBOL, STATE_SYMBOL } from '#client/constants'; import { UNINITIALIZED } from '../../constants.js'; import * as e from './errors.js'; import { get_stack, tag } from './dev/tracing.js'; @@ -24,14 +19,14 @@ import { tracing_mode_flag } from '../flags/index.js'; * @template T * @param {T} value * @param {string} [path] - * @param {PROXY_CHANGE_PATH | PROXY_PRESERVE_PATH} path_preservation + * @param {boolean} change_path * @returns {T} */ -export function proxy(value, path, path_preservation = PROXY_PRESERVE_PATH) { +export function proxy(value, path, change_path = false) { // if `DEV`, change the proxy `path` since we don't know if its still "owned" by its original source if ( DEV && - (path_preservation & PROXY_CHANGE_PATH) !== 0 && + change_path && typeof value === 'object' && value !== null && STATE_SYMBOL in value && @@ -270,7 +265,7 @@ export function proxy(value, path, path_preservation = PROXY_PRESERVE_PATH) { s = DEV ? tag(s, to_trace_name(prop)) : s; set( s, - with_parent(() => proxy(value, to_trace_name(prop), PROXY_CHANGE_PATH)) + with_parent(() => proxy(value, to_trace_name(prop), true)) ); sources.set(prop, s); } @@ -278,7 +273,7 @@ export function proxy(value, path, path_preservation = PROXY_PRESERVE_PATH) { has = s.v !== UNINITIALIZED; set( s, - with_parent(() => proxy(value, to_trace_name(prop), PROXY_CHANGE_PATH)) + with_parent(() => proxy(value, to_trace_name(prop), true)) ); } From 35cbd04ba54467dcb2b2247386c24dc801ff9aad Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 16:02:07 -0400 Subject: [PATCH 04/12] tweak --- .../phases/3-transform/client/visitors/VariableDeclaration.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b531f0df6e41..f22838a3f136 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 @@ -89,7 +89,7 @@ export function VariableDeclaration(node, context) { binding.kind === 'bindable_prop' && should_proxy(initial, context.state.scope) ) { - initial = b.call('$.proxy', initial, dev ? b.literal(id.name) : undefined); + initial = b.call('$.proxy', initial, dev && b.literal(id.name)); } if (is_prop_source(binding, context.state)) { @@ -132,7 +132,7 @@ export function VariableDeclaration(node, context) { const is_proxy = should_proxy(value, context.state.scope); if (rune === '$state' && is_proxy) { - value = b.call('$.proxy', value, dev ? b.literal(id.name) : undefined); + value = b.call('$.proxy', value, dev && b.literal(id.name)); } if (is_state) { From 9307ad5e111820f5e0b30b7b6913d19956e1c820 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 16:39:08 -0400 Subject: [PATCH 05/12] tweak implementation --- packages/svelte/src/compiler/migrate/index.js | 2 +- packages/svelte/src/internal/client/proxy.js | 65 +++++++++---------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js index 5ca9adb98be3..05efd22e5f47 100644 --- a/packages/svelte/src/compiler/migrate/index.js +++ b/packages/svelte/src/compiler/migrate/index.js @@ -18,7 +18,7 @@ import { import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js'; import { validate_component_options } from '../validate-options.js'; import { is_reserved, is_svg, is_void } from '../../utils.js'; -import { regex_is_valid_identifier } from '../phases/patterns.js'; +import { regex_is_valid_identifier } from '../../regexes.js'; const regex_style_tags = /(]+>)([\S\s]*?)(<\/style>)/g; const style_placeholder = '/*$$__STYLE_CONTENT__$$*/'; diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index cd3fbb324931..f25d940fe3d6 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -15,6 +15,9 @@ import * as e from './errors.js'; import { get_stack, tag } from './dev/tracing.js'; import { tracing_mode_flag } from '../flags/index.js'; +// TODO move all regexes into shared module? +const regex_is_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; + /** * @template T * @param {T} value @@ -23,17 +26,11 @@ import { tracing_mode_flag } from '../flags/index.js'; * @returns {T} */ export function proxy(value, path, change_path = false) { - // if `DEV`, change the proxy `path` since we don't know if its still "owned" by its original source - if ( - DEV && - change_path && - typeof value === 'object' && - value !== null && - STATE_SYMBOL in value && - PROXY_PATH_SYMBOL in value - ) { - value[PROXY_PATH_SYMBOL] = path; + if (DEV && change_path) { + // @ts-expect-error + value?.[PROXY_PATH_SYMBOL]?.(path); } + // if non-proxyable, or is already a proxy, return `value` if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) { return value; @@ -52,16 +49,6 @@ export function proxy(value, path, change_path = false) { var stack = DEV && tracing_mode_flag ? get_stack('CreatedAt') : null; var reaction = active_reaction; - /** @type {(prop: any) => any} */ - var to_trace_name = DEV - ? (prop) => { - return typeof prop === 'symbol' - ? `${path}[Symbol(${prop.description ?? ''})]` - : typeof prop === 'number' || Number(prop) === Number(prop) - ? `${path}[${prop}]` - : `${path}.${prop}`; - } - : (prop) => undefined; /** * @template T @@ -85,6 +72,28 @@ export function proxy(value, path, change_path = false) { sources.set('length', DEV ? tag(length_source, to_trace_name('length')) : length_source); } + /** @param {string | symbol} prop */ + function to_trace_name(prop) { + if (typeof prop === 'symbol') return `${path}[Symbol(${prop.description ?? ''})]`; + if (regex_is_valid_identifier.test(prop)) return `${path}.${prop}`; + return /^\d+$/.test(prop) ? `${path}[${prop}]` : `${path}['${prop}']`; + } + + /** @param {string} new_path */ + function update_path(new_path) { + path = new_path; + + tag(version, `${path} version`); + + // rename all child sources and child proxies + for (const [prop, source] of sources) { + var label = to_trace_name(prop); + + tag(source, label); + source.v?.[PROXY_PATH_SYMBOL]?.(label); + } + } + return new Proxy(/** @type {any} */ (value), { defineProperty(_, prop, descriptor) { if ( @@ -147,8 +156,9 @@ export function proxy(value, path, change_path = false) { if (prop === STATE_SYMBOL) { return value; } + if (DEV && prop === PROXY_PATH_SYMBOL) { - return path; + return update_path; } var s = sources.get(prop); @@ -195,7 +205,7 @@ export function proxy(value, path, change_path = false) { }, has(target, prop) { - if (prop === STATE_SYMBOL || (DEV && prop === PROXY_PATH_SYMBOL)) { + if (prop === STATE_SYMBOL) { return true; } @@ -224,17 +234,6 @@ export function proxy(value, path, change_path = false) { }, set(target, prop, value, receiver) { - if (DEV && prop === PROXY_PATH_SYMBOL) { - path = value; - tag(version, `${path} version`); - // rename all child sources and child proxies - for (const [prop, source] of sources) { - tag(source, to_trace_name(prop)); - if (typeof source.v === 'object' && source.v !== null && PROXY_PATH_SYMBOL in source.v) { - source.v[PROXY_PATH_SYMBOL] = to_trace_name(prop); - } - } - } var s = sources.get(prop); var has = prop in target; From 016f02b721509b4c2a74384cb503b0717bb57a5e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 16:42:58 -0400 Subject: [PATCH 06/12] tweak implementation --- packages/svelte/src/internal/client/proxy.js | 35 +++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index f25d940fe3d6..a9d2eedc1c97 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -22,15 +22,9 @@ const regex_is_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; * @template T * @param {T} value * @param {string} [path] - * @param {boolean} change_path * @returns {T} */ -export function proxy(value, path, change_path = false) { - if (DEV && change_path) { - // @ts-expect-error - value?.[PROXY_PATH_SYMBOL]?.(path); - } - +export function proxy(value, path) { // if non-proxyable, or is already a proxy, return `value` if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) { return value; @@ -261,19 +255,28 @@ export function proxy(value, path, change_path = false) { if (s === undefined) { if (!has || get_descriptor(target, prop)?.writable) { s = with_parent(() => source(undefined, stack)); - s = DEV ? tag(s, to_trace_name(prop)) : s; - set( - s, - with_parent(() => proxy(value, to_trace_name(prop), true)) - ); + var p = with_parent(() => proxy(value, to_trace_name(prop))); + + if (DEV) { + var label = to_trace_name(prop); + tag(s, label); + p?.[PROXY_PATH_SYMBOL]?.(label); + } + + set(s, p); sources.set(prop, s); } } else { has = s.v !== UNINITIALIZED; - set( - s, - with_parent(() => proxy(value, to_trace_name(prop), true)) - ); + + p = with_parent(() => proxy(value, to_trace_name(prop))); + + if (DEV) { + label = to_trace_name(prop); + p?.[PROXY_PATH_SYMBOL]?.(label); + } + + set(s, p); } var descriptor = Reflect.getOwnPropertyDescriptor(target, prop); From 7f4debebcfbce0df3646a3609d9d91419d5149d9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 17:06:18 -0400 Subject: [PATCH 07/12] tweak implementation --- packages/svelte/src/compiler/migrate/index.js | 2 +- .../client/visitors/VariableDeclaration.js | 12 ++- .../svelte/src/internal/client/dev/tracing.js | 12 ++- packages/svelte/src/internal/client/index.js | 2 +- packages/svelte/src/internal/client/proxy.js | 79 +++++++++++++------ 5 files changed, 76 insertions(+), 31 deletions(-) diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js index 05efd22e5f47..5ca9adb98be3 100644 --- a/packages/svelte/src/compiler/migrate/index.js +++ b/packages/svelte/src/compiler/migrate/index.js @@ -18,7 +18,7 @@ import { import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js'; import { validate_component_options } from '../validate-options.js'; import { is_reserved, is_svg, is_void } from '../../utils.js'; -import { regex_is_valid_identifier } from '../../regexes.js'; +import { regex_is_valid_identifier } from '../phases/patterns.js'; const regex_style_tags = /(]+>)([\S\s]*?)(<\/style>)/g; const style_placeholder = '/*$$__STYLE_CONTENT__$$*/'; 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 f22838a3f136..da3e164ad86f 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 @@ -89,7 +89,11 @@ export function VariableDeclaration(node, context) { binding.kind === 'bindable_prop' && should_proxy(initial, context.state.scope) ) { - initial = b.call('$.proxy', initial, dev && b.literal(id.name)); + initial = b.call('$.proxy', initial); + + if (dev) { + initial = b.call('$.tag_proxy', initial, b.literal(id.name)); + } } if (is_prop_source(binding, context.state)) { @@ -132,7 +136,11 @@ export function VariableDeclaration(node, context) { const is_proxy = should_proxy(value, context.state.scope); if (rune === '$state' && is_proxy) { - value = b.call('$.proxy', value, dev && b.literal(id.name)); + value = b.call('$.proxy', value); + + if (dev) { + value = b.call('$.tag_proxy', value, b.literal(id.name)); + } } if (is_state) { diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index e424ebed6a30..1506b023b182 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -2,7 +2,7 @@ import { UNINITIALIZED } from '../../../constants.js'; import { snapshot } from '../../shared/clone.js'; import { define_property } from '../../shared/utils.js'; -import { DERIVED, STATE_SYMBOL } from '#client/constants'; +import { DERIVED, PROXY_PATH_SYMBOL, STATE_SYMBOL } from '#client/constants'; import { effect_tracking } from '../reactivity/effects.js'; import { active_reaction, captured_signals, set_captured_signals, untrack } from '../runtime.js'; @@ -190,6 +190,16 @@ export function tag(source, name) { return source; } +/** + * @param {unknown} value + * @param {string} label + */ +export function tag_proxy(value, label) { + // @ts-expect-error + value?.[PROXY_PATH_SYMBOL]?.(label); + return value; +} + /** * @param {unknown} value */ diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 8e4d36d44d5f..60f9af912060 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -7,7 +7,7 @@ export { add_locations } from './dev/elements.js'; export { hmr } from './dev/hmr.js'; export { create_ownership_validator } from './dev/ownership.js'; export { check_target, legacy_api } from './dev/legacy.js'; -export { trace, tag } from './dev/tracing.js'; +export { trace, tag, tag_proxy } from './dev/tracing.js'; export { inspect } from './dev/inspect.js'; export { validate_snippet_args } from './dev/validation.js'; export { await_block as await } from './dom/blocks/await.js'; diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index a9d2eedc1c97..bf4340ce30c4 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -12,7 +12,7 @@ import { state as source, set } from './reactivity/sources.js'; import { PROXY_PATH_SYMBOL, STATE_SYMBOL } from '#client/constants'; import { UNINITIALIZED } from '../../constants.js'; import * as e from './errors.js'; -import { get_stack, tag } from './dev/tracing.js'; +import { get_stack, tag, tag_proxy } from './dev/tracing.js'; import { tracing_mode_flag } from '../flags/index.js'; // TODO move all regexes into shared module? @@ -21,10 +21,9 @@ const regex_is_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; /** * @template T * @param {T} value - * @param {string} [path] * @returns {T} */ -export function proxy(value, path) { +export function proxy(value) { // if non-proxyable, or is already a proxy, return `value` if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) { return value; @@ -39,7 +38,7 @@ export function proxy(value, path) { /** @type {Map>} */ var sources = new Map(); var is_proxied_array = is_array(value); - var version = DEV ? tag(source(0), `${path} version`) : source(0); + var version = source(0); var stack = DEV && tracing_mode_flag ? get_stack('CreatedAt') : null; var reaction = active_reaction; @@ -62,10 +61,12 @@ export function proxy(value, path) { 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 - const length_source = source(/** @type {any[]} */ (value).length, stack); - sources.set('length', DEV ? tag(length_source, to_trace_name('length')) : length_source); + sources.set('length', source(/** @type {any[]} */ (value).length, stack)); } + /** Used in dev for $inspect.trace() */ + var path = ''; + /** @param {string | symbol} prop */ function to_trace_name(prop) { if (typeof prop === 'symbol') return `${path}[Symbol(${prop.description ?? ''})]`; @@ -84,7 +85,7 @@ export function proxy(value, path) { var label = to_trace_name(prop); tag(source, label); - source.v?.[PROXY_PATH_SYMBOL]?.(label); + tag_proxy(source.v, label); } } @@ -107,13 +108,18 @@ export function proxy(value, path) { if (s === undefined) { s = with_parent(() => source(descriptor.value, stack)); - s = DEV && typeof prop === 'string' ? tag(s, to_trace_name(prop)) : s; sources.set(prop, s); + + if (DEV && typeof prop === 'string') { + tag(s, to_trace_name(prop)); + } } else { - set( - s, - with_parent(() => proxy(descriptor.value, to_trace_name(prop))) - ); + var p = with_parent(() => proxy(descriptor.value)); + set(s, p); + + if (DEV) { + tag_proxy(p, to_trace_name(prop)); + } } return true; @@ -125,8 +131,12 @@ export function proxy(value, path) { if (s === undefined) { if (prop in target) { const s = with_parent(() => source(UNINITIALIZED, stack)); - sources.set(prop, DEV ? tag(s, to_trace_name(prop)) : s); + sources.set(prop, s); update_version(version); + + if (DEV) { + tag(s, to_trace_name(prop)); + } } } else { // When working with arrays, we need to also ensure we update the length when removing @@ -160,10 +170,19 @@ export function proxy(value, path) { // 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 = with_parent(() => - source(proxy(exists ? target[prop] : UNINITIALIZED, to_trace_name(prop)), stack) - ); - s = DEV ? tag(s, to_trace_name(prop)) : s; + s = with_parent(() => { + var p = proxy(exists ? target[prop] : UNINITIALIZED); + var s = source(p, stack); + + if (DEV) { + var label = to_trace_name(prop); + tag(s, label); + tag_proxy(p, label); + } + + return s; + }); + sources.set(prop, s); } @@ -211,10 +230,19 @@ export function proxy(value, path) { (active_effect !== null && (!has || get_descriptor(target, prop)?.writable)) ) { if (s === undefined) { - s = with_parent(() => - source(has ? proxy(target[prop], to_trace_name(prop)) : UNINITIALIZED, stack) - ); - s = DEV ? tag(s, to_trace_name(prop)) : s; + s = with_parent(() => { + var p = has ? proxy(target[prop]) : UNINITIALIZED; + var s = source(p, stack); + + if (DEV) { + var label = to_trace_name(prop); + tag(s, label); + tag_proxy(p, label); + } + + return s; + }); + sources.set(prop, s); } @@ -255,12 +283,12 @@ export function proxy(value, path) { if (s === undefined) { if (!has || get_descriptor(target, prop)?.writable) { s = with_parent(() => source(undefined, stack)); - var p = with_parent(() => proxy(value, to_trace_name(prop))); + var p = with_parent(() => proxy(value)); if (DEV) { var label = to_trace_name(prop); tag(s, label); - p?.[PROXY_PATH_SYMBOL]?.(label); + tag_proxy(p, label); } set(s, p); @@ -269,11 +297,10 @@ export function proxy(value, path) { } else { has = s.v !== UNINITIALIZED; - p = with_parent(() => proxy(value, to_trace_name(prop))); + p = with_parent(() => proxy(value)); if (DEV) { - label = to_trace_name(prop); - p?.[PROXY_PATH_SYMBOL]?.(label); + tag_proxy(p, to_trace_name(prop)); } set(s, p); From 1d6451223aafffa903d7e71b87e16560ff7457f0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 17:10:13 -0400 Subject: [PATCH 08/12] hoist --- packages/svelte/src/internal/client/proxy.js | 38 +++++++++++--------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index bf4340ce30c4..d529ff9a15d4 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -67,13 +67,6 @@ export function proxy(value) { /** Used in dev for $inspect.trace() */ var path = ''; - /** @param {string | symbol} prop */ - function to_trace_name(prop) { - if (typeof prop === 'symbol') return `${path}[Symbol(${prop.description ?? ''})]`; - if (regex_is_valid_identifier.test(prop)) return `${path}.${prop}`; - return /^\d+$/.test(prop) ? `${path}[${prop}]` : `${path}['${prop}']`; - } - /** @param {string} new_path */ function update_path(new_path) { path = new_path; @@ -82,7 +75,7 @@ export function proxy(value) { // rename all child sources and child proxies for (const [prop, source] of sources) { - var label = to_trace_name(prop); + var label = get_label(path, prop); tag(source, label); tag_proxy(source.v, label); @@ -111,14 +104,14 @@ export function proxy(value) { sources.set(prop, s); if (DEV && typeof prop === 'string') { - tag(s, to_trace_name(prop)); + tag(s, get_label(path, prop)); } } else { var p = with_parent(() => proxy(descriptor.value)); set(s, p); if (DEV) { - tag_proxy(p, to_trace_name(prop)); + tag_proxy(p, get_label(path, prop)); } } @@ -135,7 +128,7 @@ export function proxy(value) { update_version(version); if (DEV) { - tag(s, to_trace_name(prop)); + tag(s, get_label(path, prop)); } } } else { @@ -175,7 +168,7 @@ export function proxy(value) { var s = source(p, stack); if (DEV) { - var label = to_trace_name(prop); + var label = get_label(path, prop); tag(s, label); tag_proxy(p, label); } @@ -235,7 +228,7 @@ export function proxy(value) { var s = source(p, stack); if (DEV) { - var label = to_trace_name(prop); + var label = get_label(path, prop); tag(s, label); tag_proxy(p, label); } @@ -270,8 +263,11 @@ export function proxy(value) { // 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 = with_parent(() => source(UNINITIALIZED, stack)); - other_s = DEV ? tag(other_s, to_trace_name(i)) : other_s; sources.set(i + '', other_s); + + if (DEV) { + tag(other_s, get_label(path, i)); + } } } } @@ -286,7 +282,7 @@ export function proxy(value) { var p = with_parent(() => proxy(value)); if (DEV) { - var label = to_trace_name(prop); + var label = get_label(path, prop); tag(s, label); tag_proxy(p, label); } @@ -300,7 +296,7 @@ export function proxy(value) { p = with_parent(() => proxy(value)); if (DEV) { - tag_proxy(p, to_trace_name(prop)); + tag_proxy(p, get_label(path, prop)); } set(s, p); @@ -356,6 +352,16 @@ export function proxy(value) { }); } +/** + * @param {string} path + * @param {string | symbol} prop + */ +function get_label(path, prop) { + if (typeof prop === 'symbol') return `${path}[Symbol(${prop.description ?? ''})]`; + if (regex_is_valid_identifier.test(prop)) return `${path}.${prop}`; + return /^\d+$/.test(prop) ? `${path}[${prop}]` : `${path}['${prop}']`; +} + /** * @param {Source} signal * @param {1 | -1} [d] From 4e6e8a45906d09df3c6857b3d45caaf71ae76ef6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 17:13:53 -0400 Subject: [PATCH 09/12] tweak --- packages/svelte/src/internal/client/dev/tracing.js | 8 ++++---- packages/svelte/src/internal/client/reactivity/sources.js | 2 +- packages/svelte/src/internal/client/reactivity/types.d.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 1506b023b182..df54a135d08e 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -43,7 +43,7 @@ function log_entry(signal, entry) { const type = (signal.f & DERIVED) !== 0 ? '$derived' : '$state'; const current_reaction = /** @type {Reaction} */ (active_reaction); const dirty = signal.wv > current_reaction.wv || current_reaction.wv === 0; - const { trace_name: name } = signal; + const { label: name } = signal; const style = dirty ? 'color: CornflowerBlue; font-weight: bold' : 'color: grey; font-weight: normal'; @@ -183,10 +183,10 @@ export function get_stack(label) { /** * @param {Value} source - * @param {string} name + * @param {string} label */ -export function tag(source, name) { - source.trace_name = name; +export function tag(source, label) { + source.label = label; return source; } diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index ad5ad3383d14..de6c836db696 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -139,7 +139,7 @@ export function set(source, value, should_proxy = false) { e.state_unsafe_mutation(); } - let new_value = should_proxy ? proxy(value, DEV ? source.trace_name : undefined) : value; + let new_value = should_proxy ? proxy(value, DEV ? source.label : undefined) : value; return internal_set(source, new_value); } diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 74982cac9192..c445ade8468d 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -21,7 +21,7 @@ export interface Value extends Signal { updated?: Error | null; trace_need_increase?: boolean; trace_v?: V; - trace_name?: string; + label?: string; debug?: null | (() => void); } From d2487f81b5da760603ca2a7920cbd2eef6c354e8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 17:15:34 -0400 Subject: [PATCH 10/12] fix --- packages/svelte/src/internal/client/reactivity/sources.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index de6c836db696..68d72f168640 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -31,7 +31,7 @@ import { } from '#client/constants'; import * as e from '../errors.js'; import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js'; -import { get_stack } from '../dev/tracing.js'; +import { get_stack, tag_proxy } from '../dev/tracing.js'; import { component_context, is_runes } from '../context.js'; import { proxy } from '../proxy.js'; import { execute_derived } from './deriveds.js'; @@ -139,7 +139,11 @@ export function set(source, value, should_proxy = false) { e.state_unsafe_mutation(); } - let new_value = should_proxy ? proxy(value, DEV ? source.label : undefined) : value; + let new_value = should_proxy ? proxy(value) : value; + + if (DEV && should_proxy) { + tag_proxy(new_value, /** @type {string} */ (source.label)); + } return internal_set(source, new_value); } From b1f761c197f3007426affff45d1173660880cec6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 17:30:13 -0400 Subject: [PATCH 11/12] WIP (reduce number of with_parent calls, move towards possibility of combining tag and tag_proxy) --- packages/svelte/src/internal/client/proxy.js | 37 ++++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index d529ff9a15d4..e6164f46cfb6 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -97,23 +97,20 @@ export function proxy(value) { e.state_descriptors_fixed(); } - var s = sources.get(prop); - - if (s === undefined) { - s = with_parent(() => source(descriptor.value, stack)); - sources.set(prop, s); + with_parent(() => { + var s = sources.get(prop); - if (DEV && typeof prop === 'string') { - tag(s, get_label(path, prop)); - } - } else { - var p = with_parent(() => proxy(descriptor.value)); - set(s, p); + if (s === undefined) { + s = source(descriptor.value, stack); + sources.set(prop, s); - if (DEV) { - tag_proxy(p, get_label(path, prop)); + if (DEV && typeof prop === 'string') { + tag(s, get_label(path, prop)); + } + } else { + set(s, descriptor.value, true); } - } + }); return true; }, @@ -278,22 +275,24 @@ export function proxy(value) { // object property before writing to that property. if (s === undefined) { if (!has || get_descriptor(target, prop)?.writable) { - s = with_parent(() => source(undefined, stack)); - var p = with_parent(() => proxy(value)); + s = with_parent(() => { + var s = source(undefined, stack); + set(s, proxy(value)); + return s; + }); if (DEV) { var label = get_label(path, prop); tag(s, label); - tag_proxy(p, label); + tag_proxy(s.v, label); } - set(s, p); sources.set(prop, s); } } else { has = s.v !== UNINITIALIZED; - p = with_parent(() => proxy(value)); + var p = with_parent(() => proxy(value)); if (DEV) { tag_proxy(p, get_label(path, prop)); From 4105d055f9e3d578a904bf7569c5cafc175dd021 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 10 Jun 2025 18:07:19 -0400 Subject: [PATCH 12/12] DRY out --- .../client/visitors/VariableDeclaration.js | 2 +- .../svelte/src/internal/client/dev/tracing.js | 2 ++ packages/svelte/src/internal/client/proxy.js | 28 +++++-------------- .../src/internal/client/reactivity/sources.js | 2 +- 4 files changed, 11 insertions(+), 23 deletions(-) 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 da3e164ad86f..53f18d42e43a 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 @@ -138,7 +138,7 @@ export function VariableDeclaration(node, context) { if (rune === '$state' && is_proxy) { value = b.call('$.proxy', value); - if (dev) { + if (dev && !is_state) { value = b.call('$.tag_proxy', value, b.literal(id.name)); } } diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index df54a135d08e..708c69e19a01 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -187,6 +187,8 @@ export function get_stack(label) { */ export function tag(source, label) { source.label = label; + tag_proxy(source.v, label); + return source; } diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index e6164f46cfb6..487050669933 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -12,7 +12,7 @@ import { state as source, set } from './reactivity/sources.js'; import { PROXY_PATH_SYMBOL, STATE_SYMBOL } from '#client/constants'; import { UNINITIALIZED } from '../../constants.js'; import * as e from './errors.js'; -import { get_stack, tag, tag_proxy } from './dev/tracing.js'; +import { get_stack, tag } from './dev/tracing.js'; import { tracing_mode_flag } from '../flags/index.js'; // TODO move all regexes into shared module? @@ -75,10 +75,7 @@ export function proxy(value) { // rename all child sources and child proxies for (const [prop, source] of sources) { - var label = get_label(path, prop); - - tag(source, label); - tag_proxy(source.v, label); + tag(source, get_label(path, prop)); } } @@ -165,9 +162,7 @@ export function proxy(value) { var s = source(p, stack); if (DEV) { - var label = get_label(path, prop); - tag(s, label); - tag_proxy(p, label); + tag(s, get_label(path, prop)); } return s; @@ -225,9 +220,7 @@ export function proxy(value) { var s = source(p, stack); if (DEV) { - var label = get_label(path, prop); - tag(s, label); - tag_proxy(p, label); + tag(s, get_label(path, prop)); } return s; @@ -281,23 +274,16 @@ export function proxy(value) { return s; }); + sources.set(prop, s); + if (DEV) { - var label = get_label(path, prop); - tag(s, label); - tag_proxy(s.v, label); + tag(s, get_label(path, prop)); } - - sources.set(prop, s); } } else { has = s.v !== UNINITIALIZED; var p = with_parent(() => proxy(value)); - - if (DEV) { - tag_proxy(p, get_label(path, prop)); - } - set(s, p); } diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 68d72f168640..ad7566f77218 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -141,7 +141,7 @@ export function set(source, value, should_proxy = false) { let new_value = should_proxy ? proxy(value) : value; - if (DEV && should_proxy) { + if (DEV) { tag_proxy(new_value, /** @type {string} */ (source.label)); }