Skip to content

Instantly share code, notes, and snippets.

@audinue
Created May 16, 2024 05:22
Show Gist options
  • Save audinue/2e48ae6f86f14b61b5cd8a5e7b707007 to your computer and use it in GitHub Desktop.
Save audinue/2e48ae6f86f14b61b5cd8a5e7b707007 to your computer and use it in GitHub Desktop.
Immediate mode DOM diffing (a.k.a. incremental DOM). Applicable to native platforms (WinForms, Android)
import { patch, begin, end, text } from "./immediate.js";
var count = 0;
function tick(event) {
if (event.type === "click") {
if (event.target.id === "increment") {
count++;
} else if (event.target.id === "decrement") {
count--;
}
}
patch(document.body, function () {
begin("p");
{
text("Count: ");
text(count);
}
end();
begin("p");
{
begin("button", { id: "increment" });
{
text("Increment");
}
end();
begin("button", { id: "decrement" });
{
text("Decrement");
}
end();
}
end();
begin("ul");
for (var i = 0; i < count; i++) {
begin("li");
{
text("Item ");
text(i);
}
end();
}
end();
});
}
addEventListener("DOMContentLoaded", tick);
addEventListener("click", tick);
var parent;
var stack;
export function patch(container, render) {
parent = container;
stack = [container.firstChild];
render();
clearRemainder();
}
export function begin(tag, attrs) {
patchTag(tag, attrs);
parent = getCurr();
stack.push(parent.firstChild);
}
export function end() {
clearRemainder();
parent = parent.parentNode;
stack.pop();
moveNext();
}
export function text(value) {
var curr = getCurr();
if (!curr) {
var node = document.createTextNode(value);
parent.appendChild(node);
setCurr(node);
} else if (curr.nodeType !== 3) {
var node = document.createTextNode(value);
parent.replaceChild(node, curr);
setCurr(node);
} else if (curr.data !== String(value)) {
curr.data = String(value);
}
moveNext();
}
export function tag(tag, attrs) {
patchTag(tag, attrs);
moveNext();
}
function getCurr() {
return stack[stack.length - 1];
}
function setCurr(curr) {
stack[stack.length - 1] = curr;
}
function moveNext() {
stack[stack.length - 1] = getCurr().nextSibling;
}
function createElement(tag, attrs) {
var el = document.createElement(tag);
for (var name in attrs) {
el.setAttribute(name, attrs[name]);
}
return el;
}
function clearRemainder() {
var curr = getCurr();
while (curr) {
var next = curr.nextSibling;
parent.removeChild(curr);
curr = next;
}
}
function patchTag(tag, attrs) {
var curr = getCurr();
if (!curr) {
var el = createElement(tag, attrs);
parent.appendChild(el);
setCurr(el);
} else if (curr.localName !== tag) {
var el = createElement(tag, attrs);
parent.replaceChild(el, getCurr());
setCurr(el);
} else {
for (var name in attrs) {
var value = String(attrs[name]);
if (curr.getAttribute(name) !== value) {
curr.setAttribute(name, value);
}
}
for (var i = curr.attributes.length - 1; i > -1; i--) {
var name = curr.attributes[i].name;
if (!(name in attrs)) {
curr.removeAttribute(name);
}
}
}
}
@audinue
Copy link
Author

audinue commented May 16, 2024

Why is this worse than model based DOM diffing (virtual, real)?

  • Higher code footprint.
  • You can't use that hyper-fast isEqualNode.
  • Unable to utilize deferred rendering (it's immediate duh!).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment