feat: more information when hydration fails (#11649)

In the case of an invalid child element, we already get information about the parent and the child, but in other cases where a mismatch could occur you're pretty much on your own.

This adds a bit more context to hydration_mismatch warnings — 'The error occurred near ...'
pull/11658/head
Rich Harris 1 year ago committed by GitHub
parent 019b26b775
commit 2ebb277be7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: more information when hydration fails

@ -36,13 +36,9 @@
> Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops
## hydration_missing_marker_close
## hydration_failed
> Missing hydration closing marker
## hydration_missing_marker_open
> Missing hydration opening marker
> Failed to hydrate the application
## lifecycle_legacy_only

@ -6,6 +6,8 @@
> Hydration failed because the initial UI does not match what was rendered on the server
> Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%
## lifecycle_double_unmount
> Tried to unmount a component that was not mounted

@ -163,6 +163,7 @@ function transform(name, dest) {
type: 'Literal',
value: text
};
prev_vars = vars;
continue;
}

@ -22,6 +22,7 @@ export const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
export const HYDRATION_START = '[';
export const HYDRATION_END = ']';
export const HYDRATION_END_ELSE = `${HYDRATION_END}!`; // used to indicate that an `{:else}...` block was rendered
export const HYDRATION_ERROR = {};
export const UNINITIALIZED = Symbol();

@ -1,5 +1,6 @@
import { HYDRATION_END, HYDRATION_START } from '../../../constants.js';
import * as e from '../errors.js';
import { DEV } from 'esm-env';
import { HYDRATION_END, HYDRATION_START, HYDRATION_ERROR } from '../../../constants.js';
import * as w from '../warnings.js';
/**
* Use this variable to guard everything related to hydration code so it can be treeshaken out
@ -67,5 +68,16 @@ export function hydrate_anchor(node) {
nodes.push(current);
}
e.hydration_missing_marker_close();
let location;
if (DEV) {
// @ts-expect-error
const loc = node.parentNode?.__svelte_meta?.loc;
if (loc) {
location = `${loc.file}:${loc.line}:${loc.column}`;
}
}
w.hydration_mismatch(location);
throw HYDRATION_ERROR;
}

@ -161,34 +161,18 @@ export function effect_update_depth_exceeded() {
}
/**
* Missing hydration closing marker
* Failed to hydrate the application
* @returns {never}
*/
export function hydration_missing_marker_close() {
export function hydration_failed() {
if (DEV) {
const error = new Error(`${"hydration_missing_marker_close"}\n${"Missing hydration closing marker"}`);
const error = new Error(`${"hydration_failed"}\n${"Failed to hydrate the application"}`);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("hydration_missing_marker_close");
}
}
/**
* Missing hydration opening marker
* @returns {never}
*/
export function hydration_missing_marker_open() {
if (DEV) {
const error = new Error(`${"hydration_missing_marker_open"}\n${"Missing hydration opening marker"}`);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("hydration_missing_marker_open");
throw new Error("hydration_failed");
}
}

@ -6,7 +6,7 @@ import {
empty,
init_operations
} from './dom/operations.js';
import { HYDRATION_START, PassiveDelegatedEvents } from '../../constants.js';
import { HYDRATION_ERROR, HYDRATION_START, PassiveDelegatedEvents } from '../../constants.js';
import { flush_sync, push, pop, current_component_context } from './runtime.js';
import { effect_root, branch } from './reactivity/effects.js';
import {
@ -153,7 +153,7 @@ export function hydrate(component, options) {
}
if (!node) {
e.hydration_missing_marker_open();
throw HYDRATION_ERROR;
}
const anchor = hydrate_anchor(node);
@ -167,12 +167,10 @@ export function hydrate(component, options) {
return instance;
}, false);
} catch (error) {
if (
!hydrated &&
options.recover !== false &&
/** @type {Error} */ (error).message.includes('hydration_missing_marker_close')
) {
w.hydration_mismatch();
if (error === HYDRATION_ERROR) {
if (options.recover === false) {
e.hydration_failed();
}
// If an error occured above, the operations might not yet have been initialised.
init_operations();
@ -180,9 +178,9 @@ export function hydrate(component, options) {
set_hydrating(false);
return mount(component, options);
} else {
throw error;
}
throw error;
} finally {
set_hydrating(!!previous_hydrate_nodes);
set_hydrate_nodes(previous_hydrate_nodes);

@ -21,11 +21,12 @@ export function hydration_attribute_changed(attribute, html, value) {
}
/**
* Hydration failed because the initial UI does not match what was rendered on the server
* Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%
* @param {string | undefined | null} [location]
*/
export function hydration_mismatch() {
export function hydration_mismatch(location) {
if (DEV) {
console.warn(`%c[svelte] ${"hydration_mismatch"}\n%c${"Hydration failed because the initial UI does not match what was rendered on the server"}`, bold, normal);
console.warn(`%c[svelte] ${"hydration_mismatch"}\n%c${location ? `Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near ${location}` : "Hydration failed because the initial UI does not match what was rendered on the server"}`, bold, normal);
} else {
// TODO print a link to the documentation
console.warn("hydration_mismatch");
@ -66,7 +67,7 @@ export function ownership_invalid_binding(parent, child, owner) {
*/
export function ownership_invalid_mutation(component, owner) {
if (DEV) {
console.warn(`%c[svelte] ${"ownership_invalid_mutation"}\n%c${`${component} mutated a value owned by ${owner}. This is strongly discouraged. Consider passing values to child components with \`bind:\`, or use a callback instead`}`, bold, normal);
console.warn(`%c[svelte] ${"ownership_invalid_mutation"}\n%c${component ? `${component} mutated a value owned by ${owner}. This is strongly discouraged. Consider passing values to child components with \`bind:\`, or use a callback instead` : "Mutating a value outside the component that created it is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead"}`, bold, normal);
} else {
// TODO print a link to the documentation
console.warn("ownership_invalid_mutation");

Loading…
Cancel
Save