uhtml: This sequence of renders crashes in both V3 and V4
I’m monkey testing my app and have uncovered a sequence of renders that will crash both V3 and V4. I apologize for the complexity of this example; this is the shortest sequence of renders that I have found that causes it.
The error is
DOMException: Failed to execute 'setStartAfter' on 'Range': the given Node has no parent.
at range_default (http://localhost:5173/node_modules/.vite/deps/uhtml.js?v=3ae075b9:68:11)
at remove (http://localhost:5173/node_modules/.vite/deps/uhtml.js?v=3ae075b9:77:55)
at DocumentFragment.replaceWith (http://localhost:5173/node_modules/.vite/deps/uhtml.js?v=3ae075b9:106:5)
at Object.hole (http://localhost:5173/node_modules/.vite/deps/uhtml.js?v=3ae075b9:271:9)
at unroll (http://localhost:5173/node_modules/.vite/deps/uhtml.js?v=3ae075b9:425:21)
at unrollValues (http://localhost:5173/node_modules/.vite/deps/uhtml.js?v=3ae075b9:436:19)
at unroll (http://localhost:5173/node_modules/.vite/deps/uhtml.js?v=3ae075b9:411:18)
at unrollValues (http://localhost:5173/node_modules/.vite/deps/uhtml.js?v=3ae075b9:436:19)
at unrollValues (http://localhost:5173/node_modules/.vite/deps/uhtml.js?v=3ae075b9:438:7)
at unroll (http://localhost:5173/node_modules/.vite/deps/uhtml.js?v=3ae075b9:411:18)
start.js
import { html, render } from "uhtml";
import { data } from "./data.js";
/**
* This tree is a very simplified version of my internal data structure
* @typedef {Object} TreeBase
* @property {string} className
* @property {Object} props
* @property {TreeBase[]} children
* @property {string} id
*
* @typedef {import("uhtml").Hole} Hole
*/
/**
* Wrap the code for a node into a component
* @param {TreeBase} node
* @param {Hole} body
* @returns {Hole}
*/
function component(node, body) {
return html`<div class=${node.className} id=${node.id} style=${""}>
${body}
</div>`;
}
/**
* A Stack has mulitple children
* @param {TreeBase} node
* @returns {Hole}
*/
function Stack(node) {
return component(
node,
html`${node.children.map((child) => html`<div>${content(child)}</div>`)}`,
);
}
/**
* A Gap is simply a spacer
* @param {TreeBase} node
* @returns {Hole}
*/
function Gap(node) {
return component(node, html`<div />`);
}
/**
* Invoke the correct template for each node
* @param {Object} node
* @returns {Hole}
*/
function content(node) {
if (node.className == "Gap") return Gap(node);
else if (node.className == "Stack") return Stack(node);
throw new Error("should not happen");
}
// cycle through the trees
let index = 0;
let step = 1;
function rep() {
console.log({ index });
try {
render(document.body, content(data[index]));
} catch (e) {
console.error(e);
clearInterval(timer);
}
index += step;
index = index % data.length;
}
const timer = setInterval(rep, 100);
data.js: this defines the sequence of frames that cause the problem. Each array element is a tree.
export const data = [
{
// 9
className: "Stack",
children: [
{
className: "Stack",
children: [
{
className: "Stack",
children: [],
id: "TreeBase-117",
},
{
className: "Stack",
children: [],
id: "TreeBase-118",
},
{
className: "Stack",
children: [
{
className: "Stack",
children: [],
id: "TreeBase-122",
},
],
id: "TreeBase-121",
},
],
id: "TreeBase-5",
},
],
id: "TreeBase-4",
},
{
// 8
className: "Stack",
children: [
{
className: "Stack",
children: [
{
className: "Stack",
children: [],
id: "TreeBase-117",
},
{
className: "Stack",
children: [],
id: "TreeBase-118",
},
{
className: "Stack",
children: [],
id: "TreeBase-121",
},
],
id: "TreeBase-5",
},
],
id: "TreeBase-4",
},
{
// 7
className: "Stack",
children: [
{
className: "Stack",
children: [
{
className: "Stack",
children: [
{
className: "Gap",
children: [],
id: "TreeBase-120",
},
],
id: "TreeBase-117",
},
{
className: "Stack",
children: [],
id: "TreeBase-118",
},
{
className: "Stack",
children: [],
id: "TreeBase-121",
},
],
id: "TreeBase-5",
},
],
id: "TreeBase-4",
},
{
// 6
className: "Stack",
children: [
{
className: "Stack",
children: [
{
className: "Stack",
children: [
{
className: "Gap",
children: [],
id: "TreeBase-120",
},
],
id: "TreeBase-117",
},
{
className: "Stack",
children: [],
id: "TreeBase-118",
},
{
className: "Gap",
children: [],
id: "TreeBase-119",
},
{
className: "Stack",
children: [],
id: "TreeBase-121",
},
],
id: "TreeBase-5",
},
],
id: "TreeBase-4",
},
];
About this issue
- Original URL
- State: closed
- Created 6 months ago
- Comments: 15 (8 by maintainers)
To whom it might concern, the MR to update the well known benchmark is here: https://github.com/krausest/js-framework-benchmark/pull/1576
Right now, I can see with keyed results that latest is scoring 1.00 VS 1.02 while non-keyed is mostly the same, but on memory consumption it wins by 0.01 margin in both cases, last time I’ve checked.
I’ll try to answer this evening as I might have a “good solution fit them all” thing but I need to test it … until I am sure how I want to resolve these issues there’s not much point in defining anything as even those cases might be allowed, as it used to be in V3. A bit of patience, right now I think you perfectly got how the current revision works though 👍