Skip to content

Commit 24dccce

Browse files
committed
fix: pre tag with single \n incorrect hydration
1 parent 1d773ef commit 24dccce

File tree

7 files changed

+54
-10
lines changed

7 files changed

+54
-10
lines changed

.changeset/unlucky-nails-stare.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: `pre` tag with single `\n` incorrect hydration

packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -393,13 +393,21 @@ export function RegularElement(node, context) {
393393
arg = b.member(arg, 'content');
394394
}
395395

396-
process_children(trimmed, (is_text) => b.call('$.child', arg, is_text && b.true), true, {
397-
...context,
398-
state: child_state
399-
});
396+
process_children(
397+
trimmed,
398+
(is_text) => b.call('$.child', arg, is_text && b.true),
399+
true,
400+
{
401+
...context,
402+
state: child_state
403+
},
404+
node.name === 'pre'
405+
);
400406

401407
if (needs_reset) {
402-
child_state.init.push(b.stmt(b.call('$.reset', context.state.node)));
408+
child_state.init.push(
409+
b.stmt(b.call('$.reset', context.state.node, node.name === 'pre' ? b.true : undefined))
410+
);
403411
}
404412
}
405413

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import { build_template_chunk, build_update } from './utils.js';
1414
* @param {(is_text: boolean) => Expression} initial
1515
* @param {boolean} is_element
1616
* @param {ComponentContext} context
17+
* @param {boolean} [is_pre]
1718
*/
18-
export function process_children(nodes, initial, is_element, { visit, state }) {
19+
export function process_children(nodes, initial, is_element, { visit, state }, is_pre = false) {
1920
const within_bound_contenteditable = state.metadata.bound_contenteditable;
2021
let prev = initial;
2122
let skipped = 0;
@@ -62,7 +63,13 @@ export function process_children(nodes, initial, is_element, { visit, state }) {
6263
*/
6364
function flush_sequence(sequence) {
6465
if (sequence.every((node) => node.type === 'Text')) {
65-
skipped += 1;
66+
// a pre tag for some reason skips the first text node if it's a single
67+
// \n so if that's the case we need to not increase skipped or hydration
68+
// will fail because `child` will return the actual child and `sibling will
69+
// return a text node
70+
if (!(is_pre && sequence.length === 1 && sequence[0].raw === '\n')) {
71+
skipped += 1;
72+
}
6673
state.template.push(sequence.map((node) => node.raw).join(''));
6774
return;
6875
}

packages/svelte/src/internal/client/dom/hydration.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,24 @@ export function hydrate_next() {
4343
return set_hydrate_node(/** @type {TemplateNode} */ (get_next_sibling(hydrate_node)));
4444
}
4545

46-
/** @param {TemplateNode} node */
47-
export function reset(node) {
46+
/**
47+
* @param {TemplateNode} node
48+
* @param {boolean} [is_pre]
49+
*/
50+
export function reset(node, is_pre = false) {
4851
if (!hydrating) return;
4952

53+
let sibling = get_next_sibling(hydrate_node);
54+
// if the first text child of a pre tag is a single \n
55+
// we don't skip to the sibling (for some reason if the firstChild
56+
// of a pre tag is a single \n text node it's skipped by the browser)
57+
// it might happen that the at this point there's still a \n sibling
58+
// in that case is fine to get the next sibling and check for that
59+
if (is_pre && sibling) {
60+
sibling = get_next_sibling(sibling);
61+
}
5062
// If the node has remaining siblings, something has gone wrong
51-
if (get_next_sibling(hydrate_node) !== null) {
63+
if (sibling !== null) {
5264
w.hydration_mismatch();
5365
throw HYDRATION_ERROR;
5466
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { test } from '../../test';
2+
3+
export default test({});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<!--[--><pre><div><span></span></div>
2+
</pre><!--]-->
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
let name = $state('');
3+
</script>
4+
5+
<pre>
6+
<div><span>{name}</span></div>
7+
</pre>

0 commit comments

Comments
 (0)