fix: `pre` tag with single `\n` incorrect hydration

fix-pre-backslash-n
paoloricciuti 5 days ago
parent 1d773ef3a4
commit 24dccce3a9

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: `pre` tag with single `\n` incorrect hydration

@ -393,13 +393,21 @@ export function RegularElement(node, context) {
arg = b.member(arg, 'content'); arg = b.member(arg, 'content');
} }
process_children(trimmed, (is_text) => b.call('$.child', arg, is_text && b.true), true, { process_children(
...context, trimmed,
state: child_state (is_text) => b.call('$.child', arg, is_text && b.true),
}); true,
{
...context,
state: child_state
},
node.name === 'pre'
);
if (needs_reset) { if (needs_reset) {
child_state.init.push(b.stmt(b.call('$.reset', context.state.node))); child_state.init.push(
b.stmt(b.call('$.reset', context.state.node, node.name === 'pre' ? b.true : undefined))
);
} }
} }

@ -14,8 +14,9 @@ import { build_template_chunk, build_update } from './utils.js';
* @param {(is_text: boolean) => Expression} initial * @param {(is_text: boolean) => Expression} initial
* @param {boolean} is_element * @param {boolean} is_element
* @param {ComponentContext} context * @param {ComponentContext} context
* @param {boolean} [is_pre]
*/ */
export function process_children(nodes, initial, is_element, { visit, state }) { export function process_children(nodes, initial, is_element, { visit, state }, is_pre = false) {
const within_bound_contenteditable = state.metadata.bound_contenteditable; const within_bound_contenteditable = state.metadata.bound_contenteditable;
let prev = initial; let prev = initial;
let skipped = 0; let skipped = 0;
@ -62,7 +63,13 @@ export function process_children(nodes, initial, is_element, { visit, state }) {
*/ */
function flush_sequence(sequence) { function flush_sequence(sequence) {
if (sequence.every((node) => node.type === 'Text')) { if (sequence.every((node) => node.type === 'Text')) {
skipped += 1; // a pre tag for some reason skips the first text node if it's a single
// \n so if that's the case we need to not increase skipped or hydration
// will fail because `child` will return the actual child and `sibling will
// return a text node
if (!(is_pre && sequence.length === 1 && sequence[0].raw === '\n')) {
skipped += 1;
}
state.template.push(sequence.map((node) => node.raw).join('')); state.template.push(sequence.map((node) => node.raw).join(''));
return; return;
} }

@ -43,12 +43,24 @@ export function hydrate_next() {
return set_hydrate_node(/** @type {TemplateNode} */ (get_next_sibling(hydrate_node))); return set_hydrate_node(/** @type {TemplateNode} */ (get_next_sibling(hydrate_node)));
} }
/** @param {TemplateNode} node */ /**
export function reset(node) { * @param {TemplateNode} node
* @param {boolean} [is_pre]
*/
export function reset(node, is_pre = false) {
if (!hydrating) return; if (!hydrating) return;
let sibling = get_next_sibling(hydrate_node);
// if the first text child of a pre tag is a single \n
// we don't skip to the sibling (for some reason if the firstChild
// of a pre tag is a single \n text node it's skipped by the browser)
// it might happen that the at this point there's still a \n sibling
// in that case is fine to get the next sibling and check for that
if (is_pre && sibling) {
sibling = get_next_sibling(sibling);
}
// If the node has remaining siblings, something has gone wrong // If the node has remaining siblings, something has gone wrong
if (get_next_sibling(hydrate_node) !== null) { if (sibling !== null) {
w.hydration_mismatch(); w.hydration_mismatch();
throw HYDRATION_ERROR; throw HYDRATION_ERROR;
} }

@ -0,0 +1,3 @@
import { test } from '../../test';
export default test({});

@ -0,0 +1,2 @@
<!--[--><pre><div><span></span></div>
</pre><!--]-->

@ -0,0 +1,7 @@
<script>
let name = $state('');
</script>
<pre>
<div><span>{name}</span></div>
</pre>
Loading…
Cancel
Save