From 09c9a3c16533d2223e17700684718b4829cda9c6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 16 Jul 2025 18:55:24 -0400 Subject: [PATCH] fix: silence `$inspect` errors when the effect is about to be destroyed (#16391) * fix: silence `$inspect` errors when the effect is about to be destroyed * changeset --- .changeset/six-swans-rush.md | 5 ++ .../svelte/src/internal/client/dev/inspect.js | 51 ++++++++++++------- .../samples/inspect-exception/_config.js | 2 +- 3 files changed, 39 insertions(+), 19 deletions(-) create mode 100644 .changeset/six-swans-rush.md diff --git a/.changeset/six-swans-rush.md b/.changeset/six-swans-rush.md new file mode 100644 index 0000000000..cfb5b97400 --- /dev/null +++ b/.changeset/six-swans-rush.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: silence `$inspect` errors when the effect is about to be destroyed diff --git a/packages/svelte/src/internal/client/dev/inspect.js b/packages/svelte/src/internal/client/dev/inspect.js index e15c66901c..c593f2622c 100644 --- a/packages/svelte/src/internal/client/dev/inspect.js +++ b/packages/svelte/src/internal/client/dev/inspect.js @@ -1,6 +1,6 @@ import { UNINITIALIZED } from '../../../constants.js'; import { snapshot } from '../../shared/clone.js'; -import { inspect_effect, validate_effect } from '../reactivity/effects.js'; +import { inspect_effect, render_effect, validate_effect } from '../reactivity/effects.js'; import { untrack } from '../runtime.js'; /** @@ -12,29 +12,44 @@ export function inspect(get_value, inspector = console.log) { validate_effect('$inspect'); let initial = true; + let error = /** @type {any} */ (UNINITIALIZED); + // Inspect effects runs synchronously so that we can capture useful + // stack traces. As a consequence, reading the value might result + // in an error (an `$inspect(object.property)` will run before the + // `{#if object}...{/if}` that contains it) inspect_effect(() => { - /** @type {any} */ - var value = UNINITIALIZED; - - // Capturing the value might result in an exception due to the inspect effect being - // sync and thus operating on stale data. In the case we encounter an exception we - // can bail-out of reporting the value. Instead we simply console.error the error - // so at least it's known that an error occured, but we don't stop execution try { - value = get_value(); - } catch (error) { - // eslint-disable-next-line no-console - console.error(error); + var value = get_value(); + } catch (e) { + error = e; + return; } - if (value !== UNINITIALIZED) { - var snap = snapshot(value, true); - untrack(() => { - inspector(initial ? 'init' : 'update', ...snap); - }); - } + var snap = snapshot(value, true); + untrack(() => { + inspector(initial ? 'init' : 'update', ...snap); + }); initial = false; }); + + // If an error occurs, we store it (along with its stack trace). + // If the render effect subsequently runs, we log the error, + // but if it doesn't run it's because the `$inspect` was + // destroyed, meaning we don't need to bother + render_effect(() => { + try { + // call `get_value` so that this runs alongside the inspect effect + get_value(); + } catch { + // ignore + } + + if (error !== UNINITIALIZED) { + // eslint-disable-next-line no-console + console.error(error); + error = UNINITIALIZED; + } + }); } diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-exception/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-exception/_config.js index e155ff236a..83e0eb9443 100644 --- a/packages/svelte/tests/runtime-runes/samples/inspect-exception/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/inspect-exception/_config.js @@ -11,7 +11,7 @@ export default test({ b1?.click(); flushSync(); - assert.ok(errors.length > 0); + assert.equal(errors.length, 0); assert.deepEqual(logs, ['init', 'a', 'init', 'b']); } });