diff --git a/.changeset/twelve-cooks-speak.md b/.changeset/twelve-cooks-speak.md new file mode 100644 index 000000000000..d4fcd5c339e9 --- /dev/null +++ b/.changeset/twelve-cooks-speak.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ignore false-positive errors of `$inspect` dependencies diff --git a/packages/svelte/src/internal/client/dev/inspect.js b/packages/svelte/src/internal/client/dev/inspect.js index 75b29ce9b10c..7a8fa0e963f4 100644 --- a/packages/svelte/src/internal/client/dev/inspect.js +++ b/packages/svelte/src/internal/client/dev/inspect.js @@ -20,6 +20,8 @@ export function inspect(get_value, inspector, show_stack = false) { // in an error (an `$inspect(object.property)` will run before the // `{#if object}...{/if}` that contains it) eager_effect(() => { + error = UNINITIALIZED; + try { var value = get_value(); } catch (e) { diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index a5dd94badf82..f374f6a26bf1 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -266,7 +266,24 @@ export function flush_eager_effects() { eager_effects_deferred = false; for (const effect of eager_effects) { - if (is_dirty(effect)) { + // Mark clean inspect-effects as maybe dirty and then check their dirtiness + // instead of just updating the effects - this way we avoid overfiring. + if ((effect.f & CLEAN) !== 0) { + set_signal_status(effect, MAYBE_DIRTY); + } + + let dirty; + + try { + dirty = is_dirty(effect); + } catch { + // Dirty-checking can evaluate derived dependencies and throw in cases where + // parent effects are about to destroy this eager effect. Run the effect so + // its own error handling can deal with transient failures. + dirty = true; + } + + if (dirty) { update_effect(effect); } } diff --git a/packages/svelte/tests/helpers.js b/packages/svelte/tests/helpers.js index d0ec8b6e44de..52bd47dfae7d 100644 --- a/packages/svelte/tests/helpers.js +++ b/packages/svelte/tests/helpers.js @@ -201,7 +201,7 @@ export const async_mode = process.env.SVELTE_NO_ASYNC !== 'true'; * @param {any[]} logs */ export function normalise_inspect_logs(logs) { - /** @type {string[]} */ + /** @type {any[]} */ const normalised = []; for (const log of logs) { diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-derived-if-destroy/List.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-derived-if-destroy/List.svelte new file mode 100644 index 000000000000..c73ac9a99ddb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-derived-if-destroy/List.svelte @@ -0,0 +1,11 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-derived-if-destroy/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-derived-if-destroy/_config.js new file mode 100644 index 000000000000..c29022c9dc44 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-derived-if-destroy/_config.js @@ -0,0 +1,21 @@ +import { normalise_inspect_logs } from '../../../helpers'; +import { test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + compileOptions: { + dev: true + }, + + async test({ assert, target, errors, logs }) { + const button = target.querySelector('button'); + + flushSync(() => { + button?.click(); + }); + + assert.htmlEqual(target.innerHTML, ''); + assert.equal(errors.length, 0); + assert.deepEqual(normalise_inspect_logs(logs), [[{ id: 1 }, { id: 2 }]]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-derived-if-destroy/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-derived-if-destroy/main.svelte new file mode 100644 index 000000000000..89e09f417b74 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-derived-if-destroy/main.svelte @@ -0,0 +1,15 @@ + + +{#if data} + t)} /> +{/if} + +