pull/16226/merge
7nik 3 days ago committed by GitHub
commit 722a18f92a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: improve hydration of altered html

@ -3,10 +3,11 @@ import { hydrate_node, hydrating, set_hydrate_node, set_hydrating } from '../hyd
import { create_text, get_first_child, get_next_sibling } from '../operations.js';
import { block } from '../../reactivity/effects.js';
import { COMMENT_NODE, HEAD_EFFECT } from '#client/constants';
import { HYDRATION_START } from '../../../../constants.js';
import { HYDRATION_END, HYDRATION_ERROR, HYDRATION_START } from '../../../../constants.js';
import { hydration_mismatch } from '../../warnings.js';
/**
* @type {Node | undefined}
* @type {Node | null | undefined}
*/
let head_anchor;
@ -32,7 +33,7 @@ export function head(render_fn) {
// There might be multiple head blocks in our app, so we need to account for each one needing independent hydration.
if (head_anchor === undefined) {
head_anchor = /** @type {TemplateNode} */ (get_first_child(document.head));
head_anchor = get_first_child(document.head);
}
while (
@ -40,7 +41,7 @@ export function head(render_fn) {
(head_anchor.nodeType !== COMMENT_NODE ||
/** @type {Comment} */ (head_anchor).data !== HYDRATION_START)
) {
head_anchor = /** @type {TemplateNode} */ (get_next_sibling(head_anchor));
head_anchor = get_next_sibling(head_anchor);
}
// If we can't find an opening hydration marker, skip hydration (this can happen
@ -58,6 +59,43 @@ export function head(render_fn) {
try {
block(() => render_fn(anchor), HEAD_EFFECT);
check_end();
} catch (error) {
// Remount only this svelte:head
if (was_hydrating && head_anchor != null) {
hydration_mismatch();
// Here head_anchor is the node next after HYDRATION_START
/** @type {Node | null} */
var node = head_anchor.previousSibling;
// Remove nodes that failed to hydrate
var depth = 0;
while (node !== null) {
var prev = /** @type {TemplateNode} */ (node);
node = get_next_sibling(node);
prev.remove();
if (prev.nodeType === COMMENT_NODE) {
var data = /** @type {Comment} */ (prev).data;
if (data === HYDRATION_END) {
depth -= 1;
if (depth === 0) break;
} else if (data === HYDRATION_START) {
depth += 1;
}
}
}
// Setup hydration for the next svelte:head
if (node === null) {
head_anchor = null;
} else {
head_anchor = set_hydrate_node(/** @type {TemplateNode} */ (node));
}
set_hydrating(false);
anchor = document.head.appendChild(create_text());
block(() => render_fn(anchor), HEAD_EFFECT);
} else {
throw error;
}
} finally {
if (was_hydrating) {
set_hydrating(true);
@ -66,3 +104,11 @@ export function head(render_fn) {
}
}
}
// treeshaking of hydrate node fails when this is directly in the try-catch
function check_end() {
if (hydrating && /** @type {Comment|null} */ (hydrate_node)?.data !== HYDRATION_END) {
hydration_mismatch();
throw HYDRATION_ERROR;
}
}

@ -0,0 +1,18 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
expect_hydration_error: true,
test(assert, target, snapshot, component, window) {
assert.equal(window.document.querySelectorAll('meta').length, 5);
const [button] = target.getElementsByTagName('button');
button.click();
flushSync();
/** @type {NodeList} */
const metas = window.document.querySelectorAll('meta[name=count]');
assert.equal(metas.length, 4);
metas.forEach((meta) => assert.equal(/** @type {HTMLMetaElement} */ (meta).content, '2'));
}
});

@ -0,0 +1,2 @@
<!--[--><meta name="count" content="1"><!----><!--]--><!--[--><meta name="count" content="1"><!----><!--]-->
<!----><meta name="count" content="1"> <meta name="will-be-missing"> <meta name="count" content="1">

@ -0,0 +1,5 @@
<!--[--><meta name="count" content="1" /><!----><!--]--><!--[--><meta name="count" content="1" />
<meta name="count" content="1" /><!----><!--]--><!--[--><meta
name="count"
content="1"
/><!----><!--]-->

@ -0,0 +1,6 @@
<script>
let { children } = $props();
</script>
<svelte:head>
{@render children()}
</svelte:head>

@ -0,0 +1,19 @@
<script>
import Head from './head.svelte';
let count = $state(1);
</script>
<Head>
<meta name="count" content={count}>
</Head>
<Head>
<meta name="count" content={count}>
<meta name="will-be-missing">
<meta name="count" content={count}>
</Head>
<Head>
<meta name="count" content={count}>
</Head>
<button onclick={() => count++}>inc</button>

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
expect_hydration_error: true,
test(assert, target, snapshot, component, window) {
assert.equal(window.document.querySelectorAll('meta').length, 2);
}
});

@ -0,0 +1 @@
<meta name="description" content="some description"> <meta name="keywords" content="some keywords">

@ -0,0 +1,2 @@
<!--[--><meta name="description" content="some description" />
<meta name="foreign" content="alien" /> <meta name="keywords" content="some keywords" /><!--]-->

@ -0,0 +1,10 @@
<script>
let content = "some keywords"
</script>
<svelte:head>
<meta name="description" content="some description" />
<meta name="keywords" {content} />
</svelte:head>
<div>Just a dummy page.</div>

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
expect_hydration_error: true,
test(assert, target, snapshot, component, window) {
assert.equal(window.document.querySelectorAll('meta').length, 2);
}
});

@ -0,0 +1 @@
<!--[--><meta name="description" content="some description"> <meta name="keywords" content="some keywords">

@ -0,0 +1 @@
<!--[--><meta name="description" content="some description" /><!--]-->

@ -0,0 +1,6 @@
<svelte:head>
<meta name="description" content="some description" />
<meta name="keywords" content="some keywords" />
</svelte:head>
<div>Just a dummy page.</div>
Loading…
Cancel
Save