From 8e5f1db50ce9ff7054fadd8fdc29a8327c01ca56 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:14:03 -0700 Subject: [PATCH 1/3] fix: don't clone non-proxies in `$inspect` --- .changeset/itchy-games-guess.md | 5 ++ .../svelte/src/internal/client/dev/inspect.js | 2 +- packages/svelte/src/internal/shared/clone.js | 70 ++++++++++++++----- 3 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 .changeset/itchy-games-guess.md diff --git a/.changeset/itchy-games-guess.md b/.changeset/itchy-games-guess.md new file mode 100644 index 000000000000..44516cfc21f2 --- /dev/null +++ b/.changeset/itchy-games-guess.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't clone non-proxies in `$inspect` diff --git a/packages/svelte/src/internal/client/dev/inspect.js b/packages/svelte/src/internal/client/dev/inspect.js index c593f2622ca8..f79cf472991a 100644 --- a/packages/svelte/src/internal/client/dev/inspect.js +++ b/packages/svelte/src/internal/client/dev/inspect.js @@ -26,7 +26,7 @@ export function inspect(get_value, inspector = console.log) { return; } - var snap = snapshot(value, true); + var snap = snapshot(value, true, true); untrack(() => { inspector(initial ? 'init' : 'update', ...snap); }); diff --git a/packages/svelte/src/internal/shared/clone.js b/packages/svelte/src/internal/shared/clone.js index 4632fc3d68ed..7dc9ffec2a30 100644 --- a/packages/svelte/src/internal/shared/clone.js +++ b/packages/svelte/src/internal/shared/clone.js @@ -15,9 +15,10 @@ const empty = []; * @template T * @param {T} value * @param {boolean} [skip_warning] + * @param {boolean} [only_deproxy] * @returns {Snapshot} */ -export function snapshot(value, skip_warning = false) { +export function snapshot(value, skip_warning = false, only_deproxy = false) { if (DEV && !skip_warning) { /** @type {string[]} */ const paths = []; @@ -40,7 +41,7 @@ export function snapshot(value, skip_warning = false) { return copy; } - return clone(value, new Map(), '', empty); + return clone(value, new Map(), '', empty, null, only_deproxy); } /** @@ -49,16 +50,19 @@ export function snapshot(value, skip_warning = false) { * @param {Map>} cloned * @param {string} path * @param {string[]} paths - * @param {null | T} original The original value, if `value` was produced from a `toJSON` call + * @param {null | T} [original] The original value, if `value` was produced from a `toJSON` call + * @param {boolean} [only_deproxy] Don't clone objects that aren't proxies * @returns {Snapshot} */ -function clone(value, cloned, path, paths, original = null) { +function clone(value, cloned, path, paths, original = null, only_deproxy = false) { if (typeof value === 'object' && value !== null) { var unwrapped = cloned.get(value); if (unwrapped !== undefined) return unwrapped; - if (value instanceof Map) return /** @type {Snapshot} */ (new Map(value)); - if (value instanceof Set) return /** @type {Snapshot} */ (new Set(value)); + if (value instanceof Map) + return /** @type {Snapshot} */ (only_deproxy ? value : new Map(value)); + if (value instanceof Set) + return /** @type {Snapshot} */ (only_deproxy ? value : new Set(value)); if (is_array(value)) { var copy = /** @type {Snapshot} */ (Array(value.length)); @@ -71,7 +75,7 @@ function clone(value, cloned, path, paths, original = null) { for (var i = 0; i < value.length; i += 1) { var element = value[i]; if (i in value) { - copy[i] = clone(element, cloned, DEV ? `${path}[${i}]` : path, paths); + copy[i] = clone(element, cloned, DEV ? `${path}[${i}]` : path, paths, null, only_deproxy); } } @@ -88,26 +92,49 @@ function clone(value, cloned, path, paths, original = null) { } for (var key in value) { - // @ts-expect-error - copy[key] = clone(value[key], cloned, DEV ? `${path}.${key}` : path, paths); + copy[key] = clone( + // @ts-expect-error + value[key], + cloned, + DEV ? `${path}.${key}` : path, + paths, + null, + only_deproxy + ); } return copy; } if (value instanceof Date) { - return /** @type {Snapshot} */ (structuredClone(value)); + if (only_deproxy) { + structuredClone(value); + } else { + return /** @type {Snapshot} */ (structuredClone(value)); + } } if (typeof (/** @type {T & { toJSON?: any } } */ (value).toJSON) === 'function') { - return clone( - /** @type {T & { toJSON(): any } } */ (value).toJSON(), - cloned, - DEV ? `${path}.toJSON()` : path, - paths, - // Associate the instance with the toJSON clone - value - ); + if (!only_deproxy) { + return clone( + /** @type {T & { toJSON(): any } } */ (value).toJSON(), + cloned, + DEV ? `${path}.toJSON()` : path, + paths, + // Associate the instance with the toJSON clone + value + ); + } else { + // we still want to read each property + clone( + /** @type {T & { toJSON(): any } } */ (value).toJSON(), + cloned, + DEV ? `${path}.toJSON()` : path, + paths, + // Associate the instance with the toJSON clone + value + ); + } } } @@ -116,6 +143,13 @@ function clone(value, cloned, path, paths, original = null) { return /** @type {Snapshot} */ (value); } + if (only_deproxy) { + try { + structuredClone(value); + } catch {} + return /** @type {Snapshot} */ (value); + } + try { return /** @type {Snapshot} */ (structuredClone(value)); } catch (e) { From fbf8a13d09ebab0d812167420f053bfe68e11fe7 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:31:05 -0700 Subject: [PATCH 2/3] apply suggestion --- .../svelte/src/internal/client/dev/inspect.js | 2 +- packages/svelte/src/internal/shared/clone.js | 74 ++++++------------- 2 files changed, 22 insertions(+), 54 deletions(-) diff --git a/packages/svelte/src/internal/client/dev/inspect.js b/packages/svelte/src/internal/client/dev/inspect.js index f79cf472991a..c593f2622ca8 100644 --- a/packages/svelte/src/internal/client/dev/inspect.js +++ b/packages/svelte/src/internal/client/dev/inspect.js @@ -26,7 +26,7 @@ export function inspect(get_value, inspector = console.log) { return; } - var snap = snapshot(value, true, true); + var snap = snapshot(value, true); untrack(() => { inspector(initial ? 'init' : 'update', ...snap); }); diff --git a/packages/svelte/src/internal/shared/clone.js b/packages/svelte/src/internal/shared/clone.js index 7dc9ffec2a30..b5964f8ec2b3 100644 --- a/packages/svelte/src/internal/shared/clone.js +++ b/packages/svelte/src/internal/shared/clone.js @@ -15,15 +15,15 @@ const empty = []; * @template T * @param {T} value * @param {boolean} [skip_warning] - * @param {boolean} [only_deproxy] + * @param {boolean} [no_tojson] * @returns {Snapshot} */ -export function snapshot(value, skip_warning = false, only_deproxy = false) { +export function snapshot(value, skip_warning = false, no_tojson = false) { if (DEV && !skip_warning) { /** @type {string[]} */ const paths = []; - const copy = clone(value, new Map(), '', paths); + const copy = clone(value, new Map(), '', paths, null, no_tojson); if (paths.length === 1 && paths[0] === '') { // value could not be cloned w.state_snapshot_uncloneable(); @@ -41,7 +41,7 @@ export function snapshot(value, skip_warning = false, only_deproxy = false) { return copy; } - return clone(value, new Map(), '', empty, null, only_deproxy); + return clone(value, new Map(), '', empty, null, no_tojson); } /** @@ -51,18 +51,16 @@ export function snapshot(value, skip_warning = false, only_deproxy = false) { * @param {string} path * @param {string[]} paths * @param {null | T} [original] The original value, if `value` was produced from a `toJSON` call - * @param {boolean} [only_deproxy] Don't clone objects that aren't proxies + * @param {boolean} [no_tojson] * @returns {Snapshot} */ -function clone(value, cloned, path, paths, original = null, only_deproxy = false) { +function clone(value, cloned, path, paths, original = null, no_tojson = false) { if (typeof value === 'object' && value !== null) { var unwrapped = cloned.get(value); if (unwrapped !== undefined) return unwrapped; - if (value instanceof Map) - return /** @type {Snapshot} */ (only_deproxy ? value : new Map(value)); - if (value instanceof Set) - return /** @type {Snapshot} */ (only_deproxy ? value : new Set(value)); + if (value instanceof Map) return /** @type {Snapshot} */ (new Map(value)); + if (value instanceof Set) return /** @type {Snapshot} */ (new Set(value)); if (is_array(value)) { var copy = /** @type {Snapshot} */ (Array(value.length)); @@ -75,7 +73,7 @@ function clone(value, cloned, path, paths, original = null, only_deproxy = false for (var i = 0; i < value.length; i += 1) { var element = value[i]; if (i in value) { - copy[i] = clone(element, cloned, DEV ? `${path}[${i}]` : path, paths, null, only_deproxy); + copy[i] = clone(element, cloned, DEV ? `${path}[${i}]` : path, paths, null, no_tojson); } } @@ -92,49 +90,26 @@ function clone(value, cloned, path, paths, original = null, only_deproxy = false } for (var key in value) { - copy[key] = clone( - // @ts-expect-error - value[key], - cloned, - DEV ? `${path}.${key}` : path, - paths, - null, - only_deproxy - ); + // @ts-expect-error + copy[key] = clone(value[key], cloned, DEV ? `${path}.${key}` : path, paths, null, no_tojson); } return copy; } if (value instanceof Date) { - if (only_deproxy) { - structuredClone(value); - } else { - return /** @type {Snapshot} */ (structuredClone(value)); - } + return /** @type {Snapshot} */ (structuredClone(value)); } - if (typeof (/** @type {T & { toJSON?: any } } */ (value).toJSON) === 'function') { - if (!only_deproxy) { - return clone( - /** @type {T & { toJSON(): any } } */ (value).toJSON(), - cloned, - DEV ? `${path}.toJSON()` : path, - paths, - // Associate the instance with the toJSON clone - value - ); - } else { - // we still want to read each property - clone( - /** @type {T & { toJSON(): any } } */ (value).toJSON(), - cloned, - DEV ? `${path}.toJSON()` : path, - paths, - // Associate the instance with the toJSON clone - value - ); - } + if (typeof (/** @type {T & { toJSON?: any } } */ (value).toJSON) === 'function' && !no_tojson) { + return clone( + /** @type {T & { toJSON(): any } } */ (value).toJSON(), + cloned, + DEV ? `${path}.toJSON()` : path, + paths, + // Associate the instance with the toJSON clone + value + ); } } @@ -143,13 +118,6 @@ function clone(value, cloned, path, paths, original = null, only_deproxy = false return /** @type {Snapshot} */ (value); } - if (only_deproxy) { - try { - structuredClone(value); - } catch {} - return /** @type {Snapshot} */ (value); - } - try { return /** @type {Snapshot} */ (structuredClone(value)); } catch (e) { From 485a1af3e11055d4a627dde6951d018a8af92b63 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:37:41 -0700 Subject: [PATCH 3/3] lint --- packages/svelte/src/internal/shared/clone.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/internal/shared/clone.js b/packages/svelte/src/internal/shared/clone.js index b5964f8ec2b3..b8f99ee198c8 100644 --- a/packages/svelte/src/internal/shared/clone.js +++ b/packages/svelte/src/internal/shared/clone.js @@ -15,7 +15,7 @@ const empty = []; * @template T * @param {T} value * @param {boolean} [skip_warning] - * @param {boolean} [no_tojson] + * @param {boolean} [no_tojson] * @returns {Snapshot} */ export function snapshot(value, skip_warning = false, no_tojson = false) { @@ -51,7 +51,7 @@ export function snapshot(value, skip_warning = false, no_tojson = false) { * @param {string} path * @param {string[]} paths * @param {null | T} [original] The original value, if `value` was produced from a `toJSON` call - * @param {boolean} [no_tojson] + * @param {boolean} [no_tojson] * @returns {Snapshot} */ function clone(value, cloned, path, paths, original = null, no_tojson = false) { @@ -90,8 +90,15 @@ function clone(value, cloned, path, paths, original = null, no_tojson = false) { } for (var key in value) { - // @ts-expect-error - copy[key] = clone(value[key], cloned, DEV ? `${path}.${key}` : path, paths, null, no_tojson); + copy[key] = clone( + // @ts-expect-error + value[key], + cloned, + DEV ? `${path}.${key}` : path, + paths, + null, + no_tojson + ); } return copy;