add test, fix

pull/17587/head
Rich Harris 6 months ago
parent 6657490351
commit 8a243a3141

@ -122,6 +122,10 @@ export function child(node, is_text) {
return text;
}
if (is_text) {
merge_text_nodes(/** @type {Text} */ (child));
}
set_hydrate_node(child);
return child;
}
@ -258,3 +262,24 @@ export function set_attribute(element, key, value = '') {
}
return element.setAttribute(key, value);
}
/**
* Browsers split text nodes larger than 65536 bytes when parsing.
* For hydration to succeed, we need to stitch them back together
* @param {Text} text
*/
export function merge_text_nodes(text) {
if (/** @type {string} */ (text.nodeValue).length < 65536) {
return;
}
let next = text.nextSibling;
while (next !== null && next.nodeType === TEXT_NODE) {
next.remove();
/** @type {string} */ (text.nodeValue) += /** @type {string} */ (next.nodeValue);
next = text.nextSibling;
}
}

@ -9,7 +9,8 @@ import {
create_element,
create_fragment,
create_comment,
set_attribute
set_attribute,
merge_text_nodes
} from './operations.js';
import { create_fragment_from_html } from './reconciler.js';
import { active_effect } from '../runtime.js';
@ -312,20 +313,7 @@ export function text(value = '') {
node.before((node = create_text()));
set_hydrate_node(node);
} else {
// Browsers may split text nodes > 65536 characters into multiple consecutive text nodes
// during HTML parsing. We need to merge them into a single text node.
var next = get_next_sibling(node);
var merged_text = /** @type {Text} */ (node).textContent;
while (next !== null && next.nodeType === TEXT_NODE) {
merged_text += /** @type {Text} */ (next).textContent;
var to_remove = next;
next = get_next_sibling(next);
to_remove.remove();
}
// Update the text node with the merged content
/** @type {Text} */ (node).textContent = merged_text;
merge_text_nodes(/** @type {Text} */ (node));
}
assign_nodes(node, node);

@ -0,0 +1,24 @@
import { test } from '../../assert';
// Browsers split text nodes > 65536 characters into multiple consecutive text nodes
// during HTML parsing. This test verifies that hydration correctly merges them.
const LARGE_TEXT = 'x'.repeat(70000);
export default test({
mode: ['hydrate'],
skip_mode: ['client'],
props: {
text: LARGE_TEXT
},
async test({ assert, target }) {
const [p] = target.querySelectorAll('p');
// The text content should be preserved after hydration
assert.equal(p.textContent?.trim(), LARGE_TEXT);
// After hydration, there should be only one text node (plus possible comment nodes)
const textNodes = [...p.childNodes].filter((node) => node.nodeType === 3);
assert.equal(textNodes.length, 1, `Expected 1 text node, got ${textNodes.length}`);
}
});

@ -0,0 +1,5 @@
<script>
let { text } = $props();
</script>
<p>{text}</p>

@ -213,7 +213,7 @@ async function run_test(
}
// uncomment to see what was generated
// fs.writeFileSync(`${test_dir}/_actual.js`, build_result.outputFiles[0].text);
// fs.writeFileSync(`${test_dir}/_output/bundle-${hydrate}.js`, build_result.outputFiles[0].text);
const test_result = await page.evaluate(
build_result.outputFiles[0].text + ";test.default(document.querySelector('main'))"
);

Loading…
Cancel
Save