From aadc97ce1be06e3dad4b98469ac385a07f34fd06 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 25 Aug 2026 12:06:14 -0400 Subject: [PATCH] fix: simplify boundary error handling and handle hydration errors correctly (#18539) remove the `catch` clause in `this.#run` - in two locations there's already a catch, and in the hydration-location we have to add special handling for hydration errors. --------- Co-authored-by: Simon Holthausen --- .../internal/client/dom/blocks/boundary.js | 33 ++++++++++++------- .../async-error-boundary-5/Child.svelte | 7 ++++ .../samples/async-error-boundary-5/_config.js | 14 ++++++++ .../async-error-boundary-5/main.svelte | 24 ++++++++++++++ 4 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/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 4f655a3695..fd6bbdf5b4 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -1,14 +1,8 @@ /** @import { Effect, Source, TemplateNode, } from '#client' */ -import { - BOUNDARY_EFFECT, - DIRTY, - EFFECT_PRESERVED, - EFFECT_TRANSPARENT, - MAYBE_DIRTY -} from '#client/constants'; +import { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants'; import { HYDRATION_START_ELSE, HYDRATION_START_FAILED } from '../../../../constants.js'; import { component_context, set_component_context } from '../../context.js'; -import { handle_error, invoke_error_boundary } from '../../error-handling.js'; +import { invoke_error_boundary } from '../../error-handling.js'; import { block, branch, @@ -271,13 +265,31 @@ export class Boundary { queue_micro_task(() => { var fragment = (this.#offscreen_fragment = document.createDocumentFragment()); var anchor = create_text(); + var handled = false; fragment.append(anchor); this.#main_effect = this.#run(() => { - return branch(() => this.#children(anchor)); + try { + return branch(() => this.#children(anchor)); + } catch (error) { + try { + this.error(error); + handled = true; + } catch (error) { + invoke_error_boundary(error, this.#effect.parent); + } + + return null; + } }); + if (this.#main_effect === null) { + this.#offscreen_fragment = null; + if (handled) this.#resolve(/** @type {Batch} */ (current_batch)); + return; + } + if (this.#pending_count === 0) { this.#anchor.before(fragment); this.#offscreen_fragment = null; @@ -362,9 +374,6 @@ export class Boundary { try { Batch.ensure(); return fn(); - } catch (e) { - handle_error(e); - return null; } finally { set_active_effect(previous_effect); set_active_reaction(previous_reaction); diff --git a/packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/Child.svelte new file mode 100644 index 0000000000..a49033a8cc --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/Child.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/_config.js b/packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/_config.js new file mode 100644 index 0000000000..29e470e46f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/_config.js @@ -0,0 +1,14 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['hydrate'], + server_props: { environment: 'server' }, + props: { environment: 'client' }, + ssrHtml: 'loading inner loading nested', + + async test({ assert, target }) { + await tick(); + assert.htmlEqual(target.innerHTML, 'inner failed: oops outer failed: oops'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/main.svelte new file mode 100644 index 0000000000..16a54630d0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-error-boundary-5/main.svelte @@ -0,0 +1,24 @@ + + + + + {await new Promise(() => {})} + + {#snippet pending()}loading inner{/snippet} + {#snippet failed(error)}inner failed: {error.message}{/snippet} + + + + + + {await new Promise(() => {})} + + {#snippet pending()}loading nested{/snippet} + + + {#snippet failed(error)}outer failed: {error.message}{/snippet} +