function createElement(tag, attrs, ...children) { return { tag, attrs, children } } function createNode(vdom) { if (typeof vdom === "string") { return document.createTextNode(vdom); } else { var node = document.createElement(vdom.tag); Object.keys(vdom.attrs).forEach(k => { node.setAttribute(k, vdom.attrs[k]); }); vdom.children.forEach(c => { node.appendChild(createNode(c)); }); return node; } } function insertNode(parent, child, idx) { var sibling = parent.childNodes[idx]; if (sibling) { parent.insertBefore(child, sibling); } else { parent.appendChild(child); } } function replaceNode(parent, child, idx) { var sibling = parent.childNodes[idx]; if (sibling) { parent.replaceChild(child, sibling); } else { parent.appendChild(child); } } function updateNode(node, vdomNew, vdomOld) { Object.keys(vdomNew.attrs) .concat(Object.keys(vdomOld.attrs)) .forEach(key => { if (vdomNew.attrs[key]) { node.setAttribute(key, vdomNew.attrs[key]); } else { node.removeAttribute(key); } }); vdomNew.children.forEach((child, idx) => { updateDOM(node, child, vdomOld.children[idx], idx); }); for (var i = vdomNew.children.length; i < vdomOld.children.length; i++) { node.removeChild(node.childNodes[i]); } } function updateDOM(parent, vdomNew, vdomOld, idx) { if (!vdomOld) { insertNode(parent, createNode(vdomNew), idx) } else if (typeof vdomNew === "string") { if (vdomNew !== vdomOld) { replaceNode(parent, createNode(vdomNew), idx); } } else { if (vdomNew.tag === vdomOld.tag) { updateNode(parent.childNodes[idx], vdomNew, vdomOld); } else { replaceNode(parent, createNode(vdomNew), idx); } } } function render(el, vdomNew, vdomOld) { updateDOM(el, vdomNew, vdomOld, 0); return vdomNew; } ////////////////// // Sample usage // ////////////////// function renderExample(el, vdom1, vdom2) { el.innerHTML = ""; var v1 = render(el, vdom1, null); setTimeout(() => { render(el, vdom2, v1); }, 1000); } function createButton(text, target, vdom1, vdom2) { var el = document.createElement("button"); el.innerText = text; el.addEventListener("click", e => { e.preventDefault(); renderExample(target, vdom1, vdom2); }); return el; } (function () { document.body.innerHTML = "
"; var demo = document.getElementById("demo"); var controls = document.getElementById("controls"); controls.appendChild( createButton( "Basic update", demo, createElement("h1", {class: "heading"}, "Hello world!"), createElement("h1", {class: "heading"}, "Good bye") ) ); controls.appendChild( createButton( "Add element", demo, createElement("h1", {class: "heading"}, "Hello world!"), createElement("h1", {class: "heading"}, "Hello ", createElement("strong", {}, "WORLD!")) ) ); controls.appendChild( createButton( "Remove element", demo, createElement("h1", {class: "heading"}, "Hello ", createElement("strong", {}, "WORLD!")), createElement("h1", {class: "heading"}, "Hello world!") ) ); controls.appendChild( createButton( "Change tag", demo, createElement("h1", {class: "heading"}, "Hello world!"), createElement("p", {}, "Here's a paragraph") ) ); }());