Skip to content

Commit d075eb9

Browse files
committed
Merge branch 'main' into better-effect-pruning
2 parents 1290ab4 + a543559 commit d075eb9

File tree

6 files changed

+71
-13
lines changed

6 files changed

+71
-13
lines changed

.changeset/itchy-games-guess.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: don't clone non-proxies in `$inspect`

.changeset/six-shirts-scream.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: avoid recursion error when tagging circular references

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,11 @@ export function proxy(value) {
9393

9494
/** Used in dev for $inspect.trace() */
9595
var path = '';
96-
96+
let updating = false;
9797
/** @param {string} new_path */
9898
function update_path(new_path) {
99+
if (updating) return;
100+
updating = true;
99101
path = new_path;
100102

101103
tag(version, `${path} version`);
@@ -104,6 +106,7 @@ export function proxy(value) {
104106
for (const [prop, source] of sources) {
105107
tag(source, get_label(path, prop));
106108
}
109+
updating = false;
107110
}
108111

109112
return new Proxy(/** @type {any} */ (value), {
@@ -284,13 +287,13 @@ export function proxy(value) {
284287
if (s === undefined) {
285288
if (!has || get_descriptor(target, prop)?.writable) {
286289
s = with_parent(() => source(undefined, stack));
287-
set(s, proxy(value));
288-
289-
sources.set(prop, s);
290290

291291
if (DEV) {
292292
tag(s, get_label(path, prop));
293293
}
294+
set(s, proxy(value));
295+
296+
sources.set(prop, s);
294297
}
295298
} else {
296299
has = s.v !== UNINITIALIZED;

packages/svelte/src/internal/shared/clone.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ const empty = [];
1515
* @template T
1616
* @param {T} value
1717
* @param {boolean} [skip_warning]
18+
* @param {boolean} [no_tojson]
1819
* @returns {Snapshot<T>}
1920
*/
20-
export function snapshot(value, skip_warning = false) {
21+
export function snapshot(value, skip_warning = false, no_tojson = false) {
2122
if (DEV && !skip_warning) {
2223
/** @type {string[]} */
2324
const paths = [];
2425

25-
const copy = clone(value, new Map(), '', paths);
26+
const copy = clone(value, new Map(), '', paths, null, no_tojson);
2627
if (paths.length === 1 && paths[0] === '') {
2728
// value could not be cloned
2829
w.state_snapshot_uncloneable();
@@ -40,7 +41,7 @@ export function snapshot(value, skip_warning = false) {
4041
return copy;
4142
}
4243

43-
return clone(value, new Map(), '', empty);
44+
return clone(value, new Map(), '', empty, null, no_tojson);
4445
}
4546

4647
/**
@@ -49,10 +50,11 @@ export function snapshot(value, skip_warning = false) {
4950
* @param {Map<T, Snapshot<T>>} cloned
5051
* @param {string} path
5152
* @param {string[]} paths
52-
* @param {null | T} original The original value, if `value` was produced from a `toJSON` call
53+
* @param {null | T} [original] The original value, if `value` was produced from a `toJSON` call
54+
* @param {boolean} [no_tojson]
5355
* @returns {Snapshot<T>}
5456
*/
55-
function clone(value, cloned, path, paths, original = null) {
57+
function clone(value, cloned, path, paths, original = null, no_tojson = false) {
5658
if (typeof value === 'object' && value !== null) {
5759
var unwrapped = cloned.get(value);
5860
if (unwrapped !== undefined) return unwrapped;
@@ -71,7 +73,7 @@ function clone(value, cloned, path, paths, original = null) {
7173
for (var i = 0; i < value.length; i += 1) {
7274
var element = value[i];
7375
if (i in value) {
74-
copy[i] = clone(element, cloned, DEV ? `${path}[${i}]` : path, paths);
76+
copy[i] = clone(element, cloned, DEV ? `${path}[${i}]` : path, paths, null, no_tojson);
7577
}
7678
}
7779

@@ -88,8 +90,15 @@ function clone(value, cloned, path, paths, original = null) {
8890
}
8991

9092
for (var key in value) {
91-
// @ts-expect-error
92-
copy[key] = clone(value[key], cloned, DEV ? `${path}.${key}` : path, paths);
93+
copy[key] = clone(
94+
// @ts-expect-error
95+
value[key],
96+
cloned,
97+
DEV ? `${path}.${key}` : path,
98+
paths,
99+
null,
100+
no_tojson
101+
);
93102
}
94103

95104
return copy;
@@ -99,7 +108,7 @@ function clone(value, cloned, path, paths, original = null) {
99108
return /** @type {Snapshot<T>} */ (structuredClone(value));
100109
}
101110

102-
if (typeof (/** @type {T & { toJSON?: any } } */ (value).toJSON) === 'function') {
111+
if (typeof (/** @type {T & { toJSON?: any } } */ (value).toJSON) === 'function' && !no_tojson) {
103112
return clone(
104113
/** @type {T & { toJSON(): any } } */ (value).toJSON(),
105114
cloned,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { test } from '../../test';
2+
import { normalise_trace_logs } from '../../../helpers.js';
3+
4+
export default test({
5+
compileOptions: {
6+
dev: true
7+
},
8+
9+
test({ assert, logs }) {
10+
const files = { id: 1, items: [{ id: 2, items: [{ id: 3 }, { id: 4 }] }] };
11+
// @ts-expect-error
12+
files.items[0].parent = files;
13+
assert.deepEqual(normalise_trace_logs(logs), [
14+
{ log: 'test (main.svelte:5:4)' },
15+
{ log: '$state', highlighted: true },
16+
{ log: 'filesState.files', highlighted: false },
17+
{ log: files },
18+
{ log: '$state', highlighted: true },
19+
{ log: 'filesState.files.items[0].parent.items', highlighted: false },
20+
{ log: files.items },
21+
{ log: '$state', highlighted: true },
22+
{ log: 'filesState.files.items[0].parent.items[0]', highlighted: false },
23+
{ log: files.items[0] }
24+
]);
25+
}
26+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
const filesState = $state({ files: {} });
3+
let nodes = { id: 1, items: [{ id: 2, items: [{ id: 3 }, { id: 4 }] }] };
4+
filesState.files = nodes;
5+
function test() {
6+
$inspect.trace();
7+
filesState.files.items[0].parent = filesState.files;
8+
}
9+
$effect(test);
10+
</script>

0 commit comments

Comments
 (0)