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 > 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 > Failed to hydrate the application
## hydration_missing_marker_open
> Missing hydration opening marker
## lifecycle_legacy_only ## 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
> Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%
## lifecycle_double_unmount ## lifecycle_double_unmount
> Tried to unmount a component that was not mounted > Tried to unmount a component that was not mounted

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

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

@ -1,5 +1,6 @@
import { HYDRATION_END, HYDRATION_START } from '../../../constants.js'; import { DEV } from 'esm-env';
import * as e from '../errors.js'; 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 * 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); 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} * @returns {never}
*/ */
export function hydration_missing_marker_close() { export function hydration_failed() {
if (DEV) { 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'; error.name = 'Svelte error';
throw error; throw error;
} else { } else {
// TODO print a link to the documentation // TODO print a link to the documentation
throw new Error("hydration_missing_marker_close"); throw new Error("hydration_failed");
}
}
/**
* 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");
} }
} }

@ -6,7 +6,7 @@ import {
empty, empty,
init_operations init_operations
} from './dom/operations.js'; } 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 { flush_sync, push, pop, current_component_context } from './runtime.js';
import { effect_root, branch } from './reactivity/effects.js'; import { effect_root, branch } from './reactivity/effects.js';
import { import {
@ -153,7 +153,7 @@ export function hydrate(component, options) {
} }
if (!node) { if (!node) {
e.hydration_missing_marker_open(); throw HYDRATION_ERROR;
} }
const anchor = hydrate_anchor(node); const anchor = hydrate_anchor(node);
@ -167,12 +167,10 @@ export function hydrate(component, options) {
return instance; return instance;
}, false); }, false);
} catch (error) { } catch (error) {
if ( if (error === HYDRATION_ERROR) {
!hydrated && if (options.recover === false) {
options.recover !== false && e.hydration_failed();
/** @type {Error} */ (error).message.includes('hydration_missing_marker_close') }
) {
w.hydration_mismatch();
// If an error occured above, the operations might not yet have been initialised. // If an error occured above, the operations might not yet have been initialised.
init_operations(); init_operations();
@ -180,9 +178,9 @@ export function hydrate(component, options) {
set_hydrating(false); set_hydrating(false);
return mount(component, options); return mount(component, options);
} else {
throw error;
} }
throw error;
} finally { } finally {
set_hydrating(!!previous_hydrate_nodes); set_hydrating(!!previous_hydrate_nodes);
set_hydrate_nodes(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) { 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 { } else {
// TODO print a link to the documentation // TODO print a link to the documentation
console.warn("hydration_mismatch"); console.warn("hydration_mismatch");
@ -66,7 +67,7 @@ export function ownership_invalid_binding(parent, child, owner) {
*/ */
export function ownership_invalid_mutation(component, owner) { export function ownership_invalid_mutation(component, owner) {
if (DEV) { 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 { } else {
// TODO print a link to the documentation // TODO print a link to the documentation
console.warn("ownership_invalid_mutation"); console.warn("ownership_invalid_mutation");

Loading…
Cancel
Save