Skip to content

Instantly share code, notes, and snippets.

@mizchi
Created March 11, 2021 13:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mizchi/030ad2cd39f036729803553a540da802 to your computer and use it in GitHub Desktop.
Save mizchi/030ad2cd39f036729803553a540da802 to your computer and use it in GitHub Desktop.
import { h, render } from "preact";
import { useState } from "preact/hooks";
// Original App
function App() {
const [state, setState] = useState(0);
return h(
"div",
{ class: "root" },
h(
"button",
{
style: { background: "wheat", width: "100px" },
onClick: () => {
console.log("event emitted");
setState(state + 1);
},
},
state.toString()
),
h("div", { class: "text" }, "hello")
);
}
render(h(App, {}), document.body);
// watcher
let n = 1;
const genId = () => (n++).toString();
let manualMutation = false;
function createRefmap(element: HTMLElement) {
const refmap = new Map<string, Node>();
function walk(el: Node) {
const id = genId();
refmap.set(id, el);
if (el instanceof HTMLElement) {
el.dataset.refid = id.toString();
Array.from(el.childNodes).forEach((child) => walk(child as Node));
}
}
manualMutation = true;
walk(element);
manualMutation = false;
return refmap;
}
// todo
function cloneAndApply(
original: HTMLElement,
refmap: Map<string, Node>,
patch: (cloned: HTMLElement) => void,
lastElement: HTMLElement | null = null
) {
lastElement?.remove();
const cloned = original.cloneNode(true) as HTMLElement;
cloned.dataset.cloned = "true";
cloned.style.display = "block"; // TODO: support other display
original.parentNode!.insertBefore(cloned, original);
original.style.display = "none";
// stop recursive
manualMutation = true;
patch(cloned);
manualMutation = false;
// transfer events
cloned.addEventListener("click", (ev) => {
const refid = (ev.target as HTMLElement).dataset.refid as string;
const originalEl = refmap.get(refid);
if (originalEl instanceof HTMLElement) {
originalEl.click();
}
});
return cloned;
}
function replaceNodeWithPatch(
original: HTMLElement,
patch: (cloned: HTMLElement) => void
) {
let refmap = createRefmap(original);
let lastElement: HTMLElement | null = null;
const observer = new MutationObserver((_records) => {
if (manualMutation) return;
manualMutation = false;
refmap = createRefmap(original);
lastElement = cloneAndApply(original, refmap, patch, lastElement);
});
observer.observe(original, {
// attributes: true,
childList: true,
characterData: true,
subtree: true,
});
// start
lastElement = cloneAndApply(original, refmap, patch, lastElement);
return () => {
observer.disconnect();
};
}
const root = document.querySelector(".root")! as HTMLElement;
replaceNodeWithPatch(root, (newRoot) => {
newRoot.querySelector(".text")!.textContent = "hello, patched";
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment