Skip to content

Commit 2ebb277

Browse files
authored
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 ...'
1 parent 019b26b commit 2ebb277

File tree

9 files changed

+43
-43
lines changed

9 files changed

+43
-43
lines changed

.changeset/violet-mails-trade.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: more information when hydration fails

packages/svelte/messages/client-errors/errors.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,9 @@
3636

3737
> 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
3838
39-
## hydration_missing_marker_close
39+
## hydration_failed
4040

41-
> Missing hydration closing marker
42-
43-
## hydration_missing_marker_open
44-
45-
> Missing hydration opening marker
41+
> Failed to hydrate the application
4642
4743
## lifecycle_legacy_only
4844

packages/svelte/messages/client-warnings/warnings.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
> Hydration failed because the initial UI does not match what was rendered on the server
88
9+
> Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%
10+
911
## lifecycle_double_unmount
1012

1113
> Tried to unmount a component that was not mounted

packages/svelte/scripts/process-messages/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ function transform(name, dest) {
163163
type: 'Literal',
164164
value: text
165165
};
166+
prev_vars = vars;
166167
continue;
167168
}
168169

packages/svelte/src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
2222
export const HYDRATION_START = '[';
2323
export const HYDRATION_END = ']';
2424
export const HYDRATION_END_ELSE = `${HYDRATION_END}!`; // used to indicate that an `{:else}...` block was rendered
25+
export const HYDRATION_ERROR = {};
2526

2627
export const UNINITIALIZED = Symbol();
2728

packages/svelte/src/internal/client/dom/hydration.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { HYDRATION_END, HYDRATION_START } from '../../../constants.js';
2-
import * as e from '../errors.js';
1+
import { DEV } from 'esm-env';
2+
import { HYDRATION_END, HYDRATION_START, HYDRATION_ERROR } from '../../../constants.js';
3+
import * as w from '../warnings.js';
34

45
/**
56
* 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) {
6768
nodes.push(current);
6869
}
6970

70-
e.hydration_missing_marker_close();
71+
let location;
72+
73+
if (DEV) {
74+
// @ts-expect-error
75+
const loc = node.parentNode?.__svelte_meta?.loc;
76+
if (loc) {
77+
location = `${loc.file}:${loc.line}:${loc.column}`;
78+
}
79+
}
80+
81+
w.hydration_mismatch(location);
82+
throw HYDRATION_ERROR;
7183
}

packages/svelte/src/internal/client/errors.js

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -161,34 +161,18 @@ export function effect_update_depth_exceeded() {
161161
}
162162

163163
/**
164-
* Missing hydration closing marker
164+
* Failed to hydrate the application
165165
* @returns {never}
166166
*/
167-
export function hydration_missing_marker_close() {
167+
export function hydration_failed() {
168168
if (DEV) {
169-
const error = new Error(`${"hydration_missing_marker_close"}\n${"Missing hydration closing marker"}`);
169+
const error = new Error(`${"hydration_failed"}\n${"Failed to hydrate the application"}`);
170170

171171
error.name = 'Svelte error';
172172
throw error;
173173
} else {
174174
// TODO print a link to the documentation
175-
throw new Error("hydration_missing_marker_close");
176-
}
177-
}
178-
179-
/**
180-
* Missing hydration opening marker
181-
* @returns {never}
182-
*/
183-
export function hydration_missing_marker_open() {
184-
if (DEV) {
185-
const error = new Error(`${"hydration_missing_marker_open"}\n${"Missing hydration opening marker"}`);
186-
187-
error.name = 'Svelte error';
188-
throw error;
189-
} else {
190-
// TODO print a link to the documentation
191-
throw new Error("hydration_missing_marker_open");
175+
throw new Error("hydration_failed");
192176
}
193177
}
194178

packages/svelte/src/internal/client/render.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
empty,
77
init_operations
88
} from './dom/operations.js';
9-
import { HYDRATION_START, PassiveDelegatedEvents } from '../../constants.js';
9+
import { HYDRATION_ERROR, HYDRATION_START, PassiveDelegatedEvents } from '../../constants.js';
1010
import { flush_sync, push, pop, current_component_context } from './runtime.js';
1111
import { effect_root, branch } from './reactivity/effects.js';
1212
import {
@@ -153,7 +153,7 @@ export function hydrate(component, options) {
153153
}
154154

155155
if (!node) {
156-
e.hydration_missing_marker_open();
156+
throw HYDRATION_ERROR;
157157
}
158158

159159
const anchor = hydrate_anchor(node);
@@ -167,22 +167,20 @@ export function hydrate(component, options) {
167167
return instance;
168168
}, false);
169169
} catch (error) {
170-
if (
171-
!hydrated &&
172-
options.recover !== false &&
173-
/** @type {Error} */ (error).message.includes('hydration_missing_marker_close')
174-
) {
175-
w.hydration_mismatch();
170+
if (error === HYDRATION_ERROR) {
171+
if (options.recover === false) {
172+
e.hydration_failed();
173+
}
176174

177175
// If an error occured above, the operations might not yet have been initialised.
178176
init_operations();
179177
clear_text_content(target);
180178

181179
set_hydrating(false);
182180
return mount(component, options);
183-
} else {
184-
throw error;
185181
}
182+
183+
throw error;
186184
} finally {
187185
set_hydrating(!!previous_hydrate_nodes);
188186
set_hydrate_nodes(previous_hydrate_nodes);

packages/svelte/src/internal/client/warnings.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ export function hydration_attribute_changed(attribute, html, value) {
2121
}
2222

2323
/**
24-
* Hydration failed because the initial UI does not match what was rendered on the server
24+
* Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%
25+
* @param {string | undefined | null} [location]
2526
*/
26-
export function hydration_mismatch() {
27+
export function hydration_mismatch(location) {
2728
if (DEV) {
28-
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);
29+
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);
2930
} else {
3031
// TODO print a link to the documentation
3132
console.warn("hydration_mismatch");
@@ -66,7 +67,7 @@ export function ownership_invalid_binding(parent, child, owner) {
6667
*/
6768
export function ownership_invalid_mutation(component, owner) {
6869
if (DEV) {
69-
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);
70+
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);
7071
} else {
7172
// TODO print a link to the documentation
7273
console.warn("ownership_invalid_mutation");

0 commit comments

Comments
 (0)