From 3ad05194446866d3a769d4b095ce931974b5839a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 18 Jun 2025 09:58:11 -0400 Subject: [PATCH] correctly handle errors inside boundary during reset --- .../internal/client/dom/blocks/boundary.js | 36 ++++++++++--------- .../_config.js | 28 +++++++++++++++ .../main.svelte | 20 +++++++++++ 3 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/main.svelte diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 2edc251de9..cda73f4af2 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -2,7 +2,7 @@ import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '#client/constants'; import { component_context, set_component_context } from '../../context.js'; -import { invoke_error_boundary } from '../../error-handling.js'; +import { handle_error, invoke_error_boundary } from '../../error-handling.js'; import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js'; import { active_effect, @@ -36,6 +36,8 @@ function with_boundary(boundary, fn) { try { fn(); + } catch (e) { + handle_error(e); } finally { set_active_effect(previous_effect); set_active_reaction(previous_reaction); @@ -74,7 +76,16 @@ export function boundary(node, props, boundary_fn) { throw error; } + if (boundary_effect) { + destroy_effect(boundary_effect); + } else if (hydrating) { + set_hydrate_node(hydrate_open); + next(); + set_hydrate_node(remove_nodes()); + } + var did_reset = false; + var calling_on_error = false; var reset = () => { if (did_reset) { @@ -84,17 +95,16 @@ export function boundary(node, props, boundary_fn) { did_reset = true; + if (calling_on_error) { + w.reset_misuse(); + throw error; + } + pause_effect(boundary_effect); with_boundary(boundary, () => { is_creating_fallback = false; - try { - boundary_effect = branch(() => boundary_fn(anchor)); - } catch (error) { - // If the new subtree immediately throws during mount, warn the dev. - w.reset_misuse(); - throw error; - } + boundary_effect = branch(() => boundary_fn(anchor)); }); }; @@ -102,19 +112,13 @@ export function boundary(node, props, boundary_fn) { try { set_active_reaction(null); + calling_on_error = true; onerror?.(error, reset); + calling_on_error = false; } finally { set_active_reaction(previous_reaction); } - if (boundary_effect) { - destroy_effect(boundary_effect); - } else if (hydrating) { - set_hydrate_node(hydrate_open); - next(); - set_hydrate_node(remove_nodes()); - } - if (failed) { // Render the `failed` snippet in a microtask queue_micro_task(() => { diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/_config.js new file mode 100644 index 0000000000..3d63345bbf --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/_config.js @@ -0,0 +1,28 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target, warnings }) { + const [toggle] = target.querySelectorAll('button'); + + flushSync(() => toggle.click()); + assert.htmlEqual( + target.innerHTML, + // TODO the synthetic stack shouldn't be part of the message here + `

yikes! in {expression} in undefined

` + ); + + const [, reset] = target.querySelectorAll('button'); + flushSync(() => reset.click()); + assert.htmlEqual( + target.innerHTML, + `

yikes! in {expression} in undefined

` + ); + + flushSync(() => toggle.click()); + + const [, reset2] = target.querySelectorAll('button'); + flushSync(() => reset2.click()); + assert.htmlEqual(target.innerHTML, `

hello!

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/main.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/main.svelte new file mode 100644 index 0000000000..91479a631a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/main.svelte @@ -0,0 +1,20 @@ + + + + + +

{must_throw ? throw_error() : 'hello!'}

+ + {#snippet failed(error, reset)} +

{error.message}

+ + {/snippet} +
+ +