Skip to content

Commit 26b95e4

Browse files
committed
fix: improve hydration of svelte head blocks
1 parent b1a8038 commit 26b95e4

File tree

9 files changed

+76
-12
lines changed

9 files changed

+76
-12
lines changed

.changeset/proud-queens-sniff.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: improve hydration of svelte head blocks

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

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrate_nodes } from '../hydration.js';
22
import { empty } from '../operations.js';
33
import { block } from '../../reactivity/effects.js';
4-
import { HYDRATION_START } from '../../../../constants.js';
4+
import { HYDRATION_END, HYDRATION_START } from '../../../../constants.js';
5+
6+
/**
7+
* @type {Node | undefined}
8+
*/
9+
let head_anchor;
10+
11+
export function reset_head_anchor() {
12+
head_anchor = undefined;
13+
}
514

615
/**
716
* @param {(anchor: Node) => import('#client').Dom | void} render_fn
@@ -19,12 +28,31 @@ export function head(render_fn) {
1928
if (hydrating) {
2029
previous_hydrate_nodes = hydrate_nodes;
2130

22-
let anchor = /** @type {import('#client').TemplateNode} */ (document.head.firstChild);
23-
while (anchor.nodeType !== 8 || /** @type {Comment} */ (anchor).data !== HYDRATION_START) {
24-
anchor = /** @type {import('#client').TemplateNode} */ (anchor.nextSibling);
31+
// There might be multiple head blocks in our app, so we need to account for each one needing independent hydration.
32+
if (head_anchor === undefined) {
33+
head_anchor = /** @type {import('#client').TemplateNode} */ (document.head.firstChild);
34+
}
35+
36+
var depth = 0;
37+
38+
while (head_anchor !== null) {
39+
if (head_anchor.nodeType === 8) {
40+
var data = /** @type {Comment} */ (head_anchor).data;
41+
if (data === HYDRATION_START) {
42+
if (depth === 0) {
43+
break;
44+
}
45+
depth++;
46+
}
47+
if (data === HYDRATION_END) {
48+
depth--;
49+
}
50+
}
51+
head_anchor = /** @type {import('#client').TemplateNode} */ (head_anchor.nextSibling);
2552
}
2653

27-
anchor = /** @type {import('#client').TemplateNode} */ (hydrate_anchor(anchor));
54+
head_anchor = /** @type {import('#client').TemplateNode} */ (hydrate_anchor(head_anchor));
55+
head_anchor = /** @type {import('#client').TemplateNode} */ (head_anchor.nextSibling);
2856
} else {
2957
anchor = document.head.appendChild(empty());
3058
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from './dom/hydration.js';
1919
import { array_from } from './utils.js';
2020
import { handle_event_propagation } from './dom/elements/events.js';
21+
import { reset_head_anchor } from './dom/blocks/svelte-head.js';
2122

2223
/** @type {Set<string>} */
2324
export const all_registered_events = new Set();
@@ -175,6 +176,7 @@ export function hydrate(component, options) {
175176
} finally {
176177
set_hydrating(!!previous_hydrate_nodes);
177178
set_hydrate_nodes(previous_hydrate_nodes);
179+
reset_head_anchor();
178180
}
179181
}
180182

packages/svelte/src/internal/server/index.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,7 @@ export function render(component, options) {
207207
on_destroy = prev_on_destroy;
208208

209209
return {
210-
head:
211-
payload.head.out || payload.head.title
212-
? payload.head.title + BLOCK_OPEN + payload.head.out + BLOCK_CLOSE
213-
: '',
210+
head: payload.head.out || payload.head.title ? payload.head.out + payload.head.title : '',
214211
html: payload.out
215212
};
216213
}
@@ -247,7 +244,9 @@ export function escape(value, is_attr = false) {
247244
*/
248245
export function head(payload, fn) {
249246
const head_payload = payload.head;
247+
payload.head.out += BLOCK_OPEN;
250248
fn(head_payload);
249+
payload.head.out += BLOCK_CLOSE;
251250
}
252251

253252
/**
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
let title = $state('Hello world');
3+
let desc = $state('Some description');
4+
</script>
5+
<svelte:head>
6+
<title>{title}</title>
7+
<meta name="description" content={desc}>
8+
<meta name="author" content="@svelteawesome">
9+
</svelte:head>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
html: `<div>Hello</div>`,
5+
6+
async test({ assert, target }) {
7+
assert.htmlEqual(
8+
target.ownerDocument.head.innerHTML,
9+
`<script async="" src="https://www.googletagmanager.com/gtag/js?id=12345"></script><meta content="Some description" name="description"><meta content="@svelteawesome" name="author"><title>Hello world</title>`
10+
);
11+
}
12+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import MetaTag from './MetaTag.svelte'
3+
</script>
4+
<svelte:head>
5+
<script async src="https://www.googletagmanager.com/gtag/js?id=12345"></script>
6+
</svelte:head>
7+
<MetaTag />
8+
9+
<div>Hello</div>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<!ssr:0>
2-
<title>Some Title</title>
32
<link rel="canonical" href="/">
43
<meta name="description" content="some description">
54
<meta name="keywords" content="some keywords">
5+
<title>Some Title</title>
66
<!ssr:0>

playgrounds/demo/src/entry-client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// @ts-ignore
2-
import { mount, unmount } from 'svelte';
2+
import { hydrate, mount, unmount } from 'svelte';
33
// @ts-ignore you need to create this file
44
import App from './App.svelte';
5-
const component = mount(App, {
5+
const component = hydrate(App, {
66
target: document.getElementById('root')!
77
});
88
// @ts-ignore

0 commit comments

Comments
 (0)