diff --git a/.changeset/stupid-rivers-stare.md b/.changeset/stupid-rivers-stare.md new file mode 100644 index 0000000000..d1d730cd21 --- /dev/null +++ b/.changeset/stupid-rivers-stare.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: provide more hydration mismatch coverage diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 2063027782..94864fecef 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -327,9 +327,8 @@ export function RegularElement(node, context) { // set the value of `hydrate_node` to `node.content` if (node.name === 'template') { needs_reset = true; - + child_state.init.push(b.stmt(b.call('$.hydrate_template', arg))); arg = b.member(arg, b.id('content')); - child_state.init.push(b.stmt(b.call('$.reset', arg))); } process_children(trimmed, () => b.call('$.child', arg), true, { diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 5f0880a676..82ad0df613 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -30,21 +30,38 @@ export let hydrate_node; /** @param {TemplateNode} node */ export function set_hydrate_node(node) { + if (node === null) { + w.hydration_mismatch(); + throw HYDRATION_ERROR; + } + return (hydrate_node = node); } export function hydrate_next() { - if (hydrate_node === null) { + return set_hydrate_node(/** @type {TemplateNode} */ (hydrate_node.nextSibling)); +} + +/** @param {TemplateNode} node */ +export function reset(node) { + if (!hydrating) return; + + // If the node has remaining siblings, something has gone wrong + if (hydrate_node.nextSibling !== null) { w.hydration_mismatch(); throw HYDRATION_ERROR; } - return (hydrate_node = /** @type {TemplateNode} */ (hydrate_node.nextSibling)); + + hydrate_node = node; } -/** @param {TemplateNode} node */ -export function reset(node) { +/** + * @param {HTMLTemplateElement} template + */ +export function hydrate_template(template) { if (hydrating) { - hydrate_node = node; + // @ts-expect-error TemplateNode doesn't include DocumentFragment, but it's actually fine + hydrate_node = template.content; } } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index edd50bff32..d1885e7d00 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -66,7 +66,7 @@ export { bind_focused } from './dom/elements/bindings/universal.js'; export { bind_window_scroll, bind_window_size } from './dom/elements/bindings/window.js'; -export { next, reset } from './dom/hydration.js'; +export { hydrate_template, next, reset } from './dom/hydration.js'; export { once, preventDefault, diff --git a/packages/svelte/tests/hydration/samples/safari-borking/_config.js b/packages/svelte/tests/hydration/samples/safari-borking/_config.js new file mode 100644 index 0000000000..cf22ff2c85 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/safari-borking/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + expect_hydration_error: true +}); diff --git a/packages/svelte/tests/hydration/samples/safari-borking/_expected.html b/packages/svelte/tests/hydration/samples/safari-borking/_expected.html new file mode 100644 index 0000000000..3f23afe0bf --- /dev/null +++ b/packages/svelte/tests/hydration/samples/safari-borking/_expected.html @@ -0,0 +1 @@ +