diff --git a/.changeset/short-worlds-enter.md b/.changeset/short-worlds-enter.md new file mode 100644 index 0000000000..96e0f80370 --- /dev/null +++ b/.changeset/short-worlds-enter.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: propagate `$effect` errors to `` diff --git a/packages/svelte/src/internal/client/error-handling.js b/packages/svelte/src/internal/client/error-handling.js index 4736333d1a..5d16759266 100644 --- a/packages/svelte/src/internal/client/error-handling.js +++ b/packages/svelte/src/internal/client/error-handling.js @@ -3,7 +3,7 @@ import { DEV } from 'esm-env'; import { FILENAME } from '../../constants.js'; import { is_firefox } from './dom/operations.js'; -import { ERROR_VALUE, BOUNDARY_EFFECT, REACTION_RAN } from './constants.js'; +import { ERROR_VALUE, BOUNDARY_EFFECT, REACTION_RAN, EFFECT } from './constants.js'; import { define_property, get_descriptor } from '../shared/utils.js'; import { active_effect, active_reaction } from './runtime.js'; @@ -25,22 +25,19 @@ export function handle_error(error) { adjustments.set(error, get_adjustments(error, effect)); } - if ((effect.f & REACTION_RAN) === 0) { - // if the error occurred while creating this subtree, we let it - // bubble up until it hits a boundary that can handle it - if ((effect.f & BOUNDARY_EFFECT) === 0) { - if (DEV && !effect.parent && error instanceof Error) { - apply_adjustments(error); - } - - throw error; + // if the error occurred while creating this subtree, we let it + // bubble up until it hits a boundary that can handle it, unless + // it's an $effect in which case it doesn't run immediately + if ((effect.f & REACTION_RAN) === 0 && (effect.f & EFFECT) === 0) { + if (DEV && !effect.parent && error instanceof Error) { + apply_adjustments(error); } - /** @type {Boundary} */ (effect.b).error(error); - } else { - // otherwise we bubble up the effect tree ourselves - invoke_error_boundary(error, effect); + throw error; } + + // otherwise we bubble up the effect tree ourselves + invoke_error_boundary(error, effect); } /** @@ -50,6 +47,11 @@ export function handle_error(error) { export function invoke_error_boundary(error, effect) { while (effect !== null) { if ((effect.f & BOUNDARY_EFFECT) !== 0) { + if ((effect.f & REACTION_RAN) === 0) { + // we are still creating the boundary effect + throw error; + } + try { /** @type {Boundary} */ (effect.b).error(error); return; diff --git a/packages/svelte/tests/runtime-runes/samples/boundary-effect-error/Test.svelte b/packages/svelte/tests/runtime-runes/samples/boundary-effect-error/Test.svelte new file mode 100644 index 0000000000..4aec88b712 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/boundary-effect-error/Test.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/boundary-effect-error/_config.js b/packages/svelte/tests/runtime-runes/samples/boundary-effect-error/_config.js new file mode 100644 index 0000000000..4d2728abc1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/boundary-effect-error/_config.js @@ -0,0 +1,10 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + // allow effects to run / microtasks to flush + await Promise.resolve(); + + assert.htmlEqual(target.innerHTML, '

caught: boom

'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/boundary-effect-error/main.svelte b/packages/svelte/tests/runtime-runes/samples/boundary-effect-error/main.svelte new file mode 100644 index 0000000000..63968f6b35 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/boundary-effect-error/main.svelte @@ -0,0 +1,11 @@ + + + + + + {#snippet failed(e)} +

caught: {e.message}

+ {/snippet} +