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 <simon.holthausen@vercel.com>
pull/18721/head
Rich Harris 4 days ago committed by GitHub
parent bfc9b65b3d
commit aadc97ce1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,14 +1,8 @@
/** @import { Effect, Source, TemplateNode, } from '#client' */ /** @import { Effect, Source, TemplateNode, } from '#client' */
import { import { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants';
BOUNDARY_EFFECT,
DIRTY,
EFFECT_PRESERVED,
EFFECT_TRANSPARENT,
MAYBE_DIRTY
} from '#client/constants';
import { HYDRATION_START_ELSE, HYDRATION_START_FAILED } from '../../../../constants.js'; import { HYDRATION_START_ELSE, HYDRATION_START_FAILED } from '../../../../constants.js';
import { component_context, set_component_context } from '../../context.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 { import {
block, block,
branch, branch,
@ -271,13 +265,31 @@ export class Boundary {
queue_micro_task(() => { queue_micro_task(() => {
var fragment = (this.#offscreen_fragment = document.createDocumentFragment()); var fragment = (this.#offscreen_fragment = document.createDocumentFragment());
var anchor = create_text(); var anchor = create_text();
var handled = false;
fragment.append(anchor); fragment.append(anchor);
this.#main_effect = this.#run(() => { 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) { if (this.#pending_count === 0) {
this.#anchor.before(fragment); this.#anchor.before(fragment);
this.#offscreen_fragment = null; this.#offscreen_fragment = null;
@ -362,9 +374,6 @@ export class Boundary {
try { try {
Batch.ensure(); Batch.ensure();
return fn(); return fn();
} catch (e) {
handle_error(e);
return null;
} finally { } finally {
set_active_effect(previous_effect); set_active_effect(previous_effect);
set_active_reaction(previous_reaction); set_active_reaction(previous_reaction);

@ -0,0 +1,7 @@
<script>
const { environment } = $props();
if (environment === 'client') {
throw new Error('oops');
}
</script>

@ -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');
}
});

@ -0,0 +1,24 @@
<script>
import Child from './Child.svelte';
const { environment } = $props();
</script>
<svelte:boundary>
<Child {environment} />
{await new Promise(() => {})}
{#snippet pending()}loading inner{/snippet}
{#snippet failed(error)}inner failed: {error.message}{/snippet}
</svelte:boundary>
<svelte:boundary>
<svelte:boundary>
<Child {environment} />
{await new Promise(() => {})}
{#snippet pending()}loading nested{/snippet}
</svelte:boundary>
{#snippet failed(error)}outer failed: {error.message}{/snippet}
</svelte:boundary>
Loading…
Cancel
Save