Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fix: improve hydration of altered html#16226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
7nik wants to merge3 commits intomain
base:main
Choose a base branch
Loading
fromfix-15337
Open
Show file tree
Hide file tree
Changes from1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
NextNext commit
fix: improve hydration of altered html
  • Loading branch information
@7nik
7nik committedJun 24, 2025
commit6cc509b4e1e4c3239ef566b20dc645ffc3db3857
5 changes: 5 additions & 0 deletions.changeset/angry-pigs-float.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: improve hydration of altered html
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@ import { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.
import * as b from '#compiler/builders';
import { is_custom_element_node } from '../../../../nodes.js';
import { build_template_chunk } from './utils.js';
import { ELEMENT_NODE, TEXT_NODE } from '#client/constants';

/**
* Processes an array of template nodes, joining sibling text/expression nodes
Expand All@@ -25,15 +26,19 @@ export function process_children(nodes, initial, is_element, context) {
/** @type {Sequence} */
let sequence = [];

/** @param {boolean} is_text */
function get_node(is_text) {
/**
* @param {boolean} is_text
* @param {number} node_type
**/
function get_node(is_text, node_type) {
if (skipped === 0) {
return prev(is_text);
}

return b.call(
'$.sibling',
prev(false),
b.literal(node_type),
(is_text || skipped !== 1) && b.literal(skipped),
is_text && b.true
);
Expand All@@ -44,7 +49,10 @@ export function process_children(nodes, initial, is_element, context) {
* @param {string} name
*/
function flush_node(is_text, name) {
const expression = get_node(is_text);
const expression = get_node(
is_text,
name === 'text' ? TEXT_NODE : name === 'node' ? 0 : ELEMENT_NODE
);
let id = expression;

if (id.type !== 'Identifier') {
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,8 @@ 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}
Expand DownExpand Up@@ -58,6 +59,41 @@ export function head(render_fn) {

try {
block(() => render_fn(anchor), HEAD_EFFECT);
if (hydrating) {
if (hydrate_node === null || /** @type {Comment} */ (hydrate_node).data !== HYDRATION_END) {
hydration_mismatch();
throw HYDRATION_ERROR;
}
}
} catch (error) {
// re-mount only this svelte:head
if (was_hydrating && head_anchor && error === HYDRATION_ERROR) {
// Here head_anchor is the node next after HYDRATION_START
/** @type {Node | null} */
let prev = head_anchor.previousSibling;
/** @type {Node | null} */
let next = head_anchor;
// remove nodes that failed to hydrate
while (
prev !== null &&
(prev.nodeType !== COMMENT_NODE || /** @type {Comment} */ (prev).data !== HYDRATION_END)
) {
document.head.removeChild(prev);
prev = next;
next = get_next_sibling(/** @type {Node} */ (next));
}
if (prev?.parentNode) document.head.removeChild(prev);
if (next !== null) {
// allow the next head block try to hydrate
head_anchor = set_hydrate_node(/** @type {TemplateNode} */ (next));
}

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);
Expand Down
28 changes: 22 additions & 6 deletionspackages/svelte/src/internal/client/dom/operations.js
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,9 @@ import { hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
import { DEV } from 'esm-env';
import { init_array_prototype_warnings } from '../dev/equality.js';
import { get_descriptor, is_extensible } from '../../shared/utils.js';
import { TEXT_NODE } from '#client/constants';
import { COMMENT_NODE, TEXT_NODE } from '#client/constants';
import { HYDRATION_END, HYDRATION_ERROR } from '../../../constants.js';
import { hydration_mismatch } from '../warnings.js';

// export these for reference in the compiled code, making global name deduplication unnecessary
/** @type {Window} */
Expand DownExpand Up@@ -158,26 +160,40 @@ export function first_child(fragment, is_text) {
/**
* Don't mark this as side-effect-free, hydration needs to walk all nodes
* @param {TemplateNode} node
* @param {number} node_type
* @param {number} count
* @param {boolean}is_text
* @param {boolean}add_text
* @returns {Node | null}
*/
export function sibling(node, count = 1,is_text = false) {
let next_sibling = hydrating ? hydrate_node : node;
export function sibling(node,node_type,count = 1,add_text = false) {
var next_sibling = hydrating ? hydrate_node : node;
var last_sibling;

while (count--) {
last_sibling = next_sibling;
next_sibling = /** @type {TemplateNode} */ (get_next_sibling(next_sibling));
if (
(next_sibling === null && !add_text) ||
(next_sibling?.nodeType === COMMENT_NODE &&
/** @type {Comment} */ (next_sibling).data === HYDRATION_END)
) {
hydration_mismatch();
throw HYDRATION_ERROR;
}
}

if (!hydrating) {
return next_sibling;
}

if (hydrating && node_type !== 0 && !add_text && next_sibling?.nodeType !== node_type) {
hydration_mismatch();
throw HYDRATION_ERROR;
}

// if a sibling {expression} is empty during SSR, there might be no
// text node to hydrate — we must therefore create one
if (is_text && next_sibling?.nodeType !== TEXT_NODE) {
if (add_text && next_sibling?.nodeType !== TEXT_NODE) {
var text = create_text();
// If the next sibling is `null` and we're handling text then it's because
// the SSR content was empty for the text, so we need to generate a new text
Expand All@@ -192,7 +208,7 @@ export function sibling(node, count = 1, is_text = false) {
}

set_hydrate_node(next_sibling);
return/** @type {TemplateNode} */ (next_sibling);
return next_sibling;
}

/**
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -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'));
}
});
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -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">
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
<!--[--><meta name="count" content="1" /><!----><!--]--><!--[--><meta name="count" content="1" />
<meta name="count" content="1" /><!----><!--]--><!--[--><meta
name="count"
content="1"
/><!----><!--]-->
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
<script>
let { children } = $props();
</script>
<svelte:head>
{@render children()}
</svelte:head>
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -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>
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -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);
}
});
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
<meta name="description" content="some description"> <meta name="keywords" content="some keywords">
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
<!--[--><meta name="description" content="some description" />
<meta name="foreign" content="alien" /> <meta name="keywords" content="some keywords" /><!--]-->
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -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>
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -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);
}
});
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
<!--[--><meta name="description" content="some description"> <meta name="keywords" content="some keywords">
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
<!--[--><meta name="description" content="some description" /><!--]-->
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -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>
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,11 +19,11 @@ export default function Await_block_scope($$anchor) {

$.reset(button);

var node = $.sibling(button, 2);
var node = $.sibling(button,0,2);

$.await(node, () => $.get(promise), null, ($$anchor, counter) => {});

var text_1 = $.sibling(node);
var text_1 = $.sibling(node, 3);

$.template_effect(() => {
$.set_text(text, `clicks: ${counter.count ?? ''}`);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,7 +27,7 @@ export default function Bind_component_snippet($$anchor) {
}
});

var text_1 = $.sibling(node);
var text_1 = $.sibling(node, 3);

$.template_effect(() => $.set_text(text_1, ` value: ${$.get(value) ?? ''}`));
$.append($$anchor, fragment);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,17 +12,17 @@ export default function Main($$anchor) {

$.set_attribute(div, 'foobar', x);

var svg = $.sibling(div, 2);
var svg = $.sibling(div,1,2);

$.set_attribute(svg, 'viewBox', x);

var custom_element = $.sibling(svg, 2);
var custom_element = $.sibling(svg,1,2);

$.set_custom_element_data(custom_element, 'fooBar', x);

var div_1 = $.sibling(custom_element, 2);
var svg_1 = $.sibling(div_1, 2);
var custom_element_1 = $.sibling(svg_1, 2);
var div_1 = $.sibling(custom_element,1,2);
var svg_1 = $.sibling(div_1,1,2);
var custom_element_1 = $.sibling(svg_1,1,2);

$.template_effect(() => $.set_custom_element_data(custom_element_1, 'fooBar', y()));

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,19 +12,19 @@ export default function Nullish_coallescence_omittance($$anchor) {

h1.textContent = 'Hello, world!';

var b = $.sibling(h1, 2);
var b = $.sibling(h1,1,2);

b.textContent = '123';

var button = $.sibling(b, 2);
var button = $.sibling(b,1,2);

button.__click = [on_click, count];

var text = $.child(button);

$.reset(button);

var h1_1 = $.sibling(button, 2);
var h1_1 = $.sibling(button,1,2);

h1_1.textContent = 'Hello, world';
$.template_effect(() => $.set_text(text, `Count is ${$.get(count) ?? ''}`));
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,11 +12,11 @@ export default function Purity($$anchor) {
$.untrack(() => Math.max(0, Math.min(0, 100)))
);

var p_1 = $.sibling(p, 2);
var p_1 = $.sibling(p,1,2);

p_1.textContent = ($.untrack(() => location.href));

var node = $.sibling(p_1, 2);
var node = $.sibling(p_1,0,2);

Child(node, { prop: encodeURIComponent('hello') });
$.append($$anchor, fragment);
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp