diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index dd36668873..ff40c04608 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -55,6 +55,9 @@ export class Boundary { /** @type {TemplateNode} */ #anchor; + /** @type {TemplateNode} */ + #hydrate_open; + /** @type {BoundaryProps} */ #props; @@ -105,92 +108,12 @@ export class Boundary { var boundary_effect = /** @type {Effect} */ (active_effect); boundary_effect.b = this; - var hydrate_open = hydrate_node; - - const reset = () => { - this.#pending_count = 0; - this.suspended = false; - - if (this.#failed_effect !== null) { - pause_effect(this.#failed_effect, () => { - this.#failed_effect = null; - }); - } - - this.#main_effect = this.#run(() => { - this.#is_creating_fallback = false; - - try { - return branch(() => this.#children(this.#anchor)); - } finally { - reset_is_throwing_error(); - } - }); - - if (this.#pending_count > 0) { - this.suspended = true; - this.#show_pending_snippet(true); - } - }; + this.#hydrate_open = hydrate_node; // @ts-ignore We re-use the effect's fn property to avoid allocation of an additional field boundary_effect.fn = (/** @type {unknown} */ input) => { var error = input; - var onerror = this.#props.onerror; - let failed = this.#props.failed; - - // If we have nothing to capture the error, or if we hit an error while - // rendering the fallback, re-throw for another boundary to handle - if (this.#is_creating_fallback || (!onerror && !failed)) { - throw error; - } - - onerror?.(error, reset); - - if (this.#main_effect) { - destroy_effect(this.#main_effect); - this.#main_effect = null; - } - - if (this.#pending_effect) { - destroy_effect(this.#pending_effect); - this.#pending_effect = null; - } - - if (this.#failed_effect) { - destroy_effect(this.#failed_effect); - this.#failed_effect = null; - } - - if (hydrating) { - set_hydrate_node(hydrate_open); - next(); - set_hydrate_node(remove_nodes()); - } - - if (failed) { - queue_boundary_micro_task(() => { - this.#failed_effect = this.#run(() => { - this.#is_creating_fallback = true; - - try { - return branch(() => { - failed( - this.#anchor, - () => error, - () => reset - ); - }); - } catch (error) { - handle_error(error, boundary_effect, null, boundary_effect.ctx); - return null; - } finally { - reset_is_throwing_error(); - this.#is_creating_fallback = false; - } - }); - }); - } + this.error(input); }; if (hydrating) { @@ -382,6 +305,91 @@ export class Boundary { } } } + + /** @param {unknown} error */ + error(error) { + var onerror = this.#props.onerror; + let failed = this.#props.failed; + + const reset = () => { + this.#pending_count = 0; + this.suspended = false; + + if (this.#failed_effect !== null) { + pause_effect(this.#failed_effect, () => { + this.#failed_effect = null; + }); + } + + this.#main_effect = this.#run(() => { + this.#is_creating_fallback = false; + + try { + return branch(() => this.#children(this.#anchor)); + } finally { + reset_is_throwing_error(); + } + }); + + if (this.#pending_count > 0) { + this.suspended = true; + this.#show_pending_snippet(true); + } + }; + + // If we have nothing to capture the error, or if we hit an error while + // rendering the fallback, re-throw for another boundary to handle + if (this.#is_creating_fallback || (!onerror && !failed)) { + throw error; + } + + onerror?.(error, reset); + + if (this.#main_effect) { + destroy_effect(this.#main_effect); + this.#main_effect = null; + } + + if (this.#pending_effect) { + destroy_effect(this.#pending_effect); + this.#pending_effect = null; + } + + if (this.#failed_effect) { + destroy_effect(this.#failed_effect); + this.#failed_effect = null; + } + + if (hydrating) { + set_hydrate_node(this.#hydrate_open); + next(); + set_hydrate_node(remove_nodes()); + } + + if (failed) { + queue_boundary_micro_task(() => { + this.#failed_effect = this.#run(() => { + this.#is_creating_fallback = true; + + try { + return branch(() => { + failed( + this.#anchor, + () => error, + () => reset + ); + }); + } catch (error) { + handle_error(error, this.#effect, null, this.#effect.ctx); + return null; + } finally { + reset_is_throwing_error(); + this.#is_creating_fallback = false; + } + }); + }); + } + } } var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_EFFECT;