Skip to content

Commit 346cf96

Browse files
trueadmRich-Harris
andauthored
fix: bail-out of hydrating head if no anchor is found (#12541)
* fix: bail-out of hydrating head if no anchor is found * add failing test * fix * fix comment --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 5253c8f commit 346cf96

File tree

7 files changed

+35
-6
lines changed

7 files changed

+35
-6
lines changed

.changeset/neat-boxes-chew.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+
fix: bail-out of hydrating head if no anchor is found

packages/svelte/src/internal/client/dom/blocks/svelte-head.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** @import { TemplateNode } from '#client' */
2-
import { hydrate_node, hydrating, set_hydrate_node } from '../hydration.js';
2+
import { hydrate_node, hydrating, set_hydrate_node, set_hydrating } from '../hydration.js';
33
import { empty } from '../operations.js';
44
import { block } from '../../reactivity/effects.js';
55
import { HEAD_EFFECT } from '../../constants.js';
@@ -36,21 +36,30 @@ export function head(render_fn) {
3636
}
3737

3838
while (
39-
head_anchor.nodeType !== 8 ||
40-
/** @type {Comment} */ (head_anchor).data !== HYDRATION_START
39+
head_anchor !== null &&
40+
(head_anchor.nodeType !== 8 || /** @type {Comment} */ (head_anchor).data !== HYDRATION_START)
4141
) {
4242
head_anchor = /** @type {TemplateNode} */ (head_anchor.nextSibling);
4343
}
4444

45-
head_anchor = set_hydrate_node(/** @type {TemplateNode} */ (head_anchor.nextSibling));
46-
} else {
45+
// If we can't find an opening hydration marker, skip hydration (this can happen
46+
// if a framework rendered body but not head content)
47+
if (head_anchor === null) {
48+
set_hydrating(false);
49+
} else {
50+
head_anchor = set_hydrate_node(/** @type {TemplateNode} */ (head_anchor.nextSibling));
51+
}
52+
}
53+
54+
if (!hydrating) {
4755
anchor = document.head.appendChild(empty());
4856
}
4957

5058
try {
5159
block(() => render_fn(anchor), HEAD_EFFECT);
5260
} finally {
5361
if (was_hydrating) {
62+
set_hydrating(true);
5463
head_anchor = hydrate_node; // so that next head block starts from the correct node
5564
set_hydrate_node(/** @type {TemplateNode} */ (previous_hydrate_node));
5665
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
test(assert, target, snapshot, component, window) {
5+
assert.equal(window.document.querySelectorAll('meta').length, 2);
6+
}
7+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<meta name="description" content="some description"> <meta name="keywords" content="some keywords">

packages/svelte/tests/hydration/samples/head-missing/_override_head.html

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<svelte:head>
2+
<meta name="description" content="some description" />
3+
<meta name="keywords" content="some keywords" />
4+
</svelte:head>
5+
6+
<div>Just a dummy page.</div>

packages/svelte/tests/hydration/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,14 @@ const { test, run } = suite<HydrationTest>(async (config, cwd) => {
5353
});
5454

5555
const override = read(`${cwd}/_override.html`);
56+
const override_head = read(`${cwd}/_override_head.html`);
5657

5758
fs.writeFileSync(`${cwd}/_output/body.html`, rendered.html + '\n');
5859
target.innerHTML = override ?? rendered.html;
5960

6061
if (rendered.head) {
6162
fs.writeFileSync(`${cwd}/_output/head.html`, rendered.head + '\n');
62-
head.innerHTML = rendered.head;
63+
head.innerHTML = override_head ?? rendered.head;
6364
}
6465

6566
config.before_test?.();

0 commit comments

Comments
 (0)