fix: bypass error boundaries for hydration recovery (#18841)

Fixes #18840

This PR rethrows the hydration error rather than catching it at the
boundaries so that the svelte hydration warning can surface

### Before submitting the PR, please make sure you do the following

- [x] It's really useful if your PR references an issue where it is
discussed ahead of time. In many cases, features are absent for a
reason. For large changes, please create an RFC:
https://github.com/sveltejs/rfcs
- [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`.
- [x] This message body should clearly illustrate what problems it
solves.
- [x] Ideally, include a test that fails without this PR but passes with
it.
- [x] If this PR changes code within `packages/svelte/src`, add a
changeset (`npx changeset`).

### Tests and linting

- [x] Run the tests with `pnpm test` and lint the project with `pnpm
lint`

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
pull/18821/merge
Tee Ming 2 days ago committed by GitHub
parent 803f59b171
commit 325620ba63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: prevent hydration mismatch recovery from being intercepted by error boundaries

@ -1,6 +1,10 @@
/** @import { Effect, Source, TemplateNode, } from '#client' */
import { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants';
import { HYDRATION_START_ELSE, HYDRATION_START_FAILED } from '../../../../constants.js';
import {
HYDRATION_ERROR,
HYDRATION_START_ELSE,
HYDRATION_START_FAILED
} from '../../../../constants.js';
import { component_context, set_component_context } from '../../context.js';
import { invoke_error_boundary } from '../../error-handling.js';
import {
@ -445,6 +449,10 @@ export class Boundary {
/** @param {unknown} error */
error(error) {
if (error === HYDRATION_ERROR) {
throw error;
}
// 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.#props.onerror && !this.#props.failed) {

@ -1,7 +1,7 @@
/** @import { Derived, Effect } from '#client' */
/** @import { Boundary } from './dom/blocks/boundary.js' */
import { DEV } from 'esm-env';
import { FILENAME } from '../../constants.js';
import { FILENAME, HYDRATION_ERROR } from '../../constants.js';
import { is_firefox } from './dom/operations.js';
import {
ERROR_VALUE,
@ -52,6 +52,10 @@ export function handle_error(error) {
* @param {Effect | null} effect
*/
export function invoke_error_boundary(error, effect) {
if (error === HYDRATION_ERROR) {
throw error;
}
if (effect !== null && (effect.f & DESTROYED) !== 0) {
return;
}

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
expect_hydration_error: true
});

@ -0,0 +1 @@
<!----><p><p>Valid HTML fragment</p><!----></p><!---->

@ -0,0 +1,7 @@
<svelte:boundary>
<p>{@html '<p>Valid HTML fragment</p>'}</p>
{#snippet failed()}
<p>boundary fallback</p>
{/snippet}
</svelte:boundary>
Loading…
Cancel
Save