A lightweight, Snabbdom-style virtual DOM. It represents your UI as plain JavaScript objects
(VNodes), diffs an old tree against a new one, and applies only the differences to the real
DOM.
init() builds the reconciler and returns a patch function. The bundled modules
(class / props / attrs / style / events) are registered automatically.
patch(oldVnode, newVnode) mounts a tree (when oldVnode is a real DOM element) or diffs
two vnode trees and updates the DOM in place.
h(sel, dataOrChildren?, children?) is the hyperscript helper that builds VNodes.
Note: in this implementation init() takes no arguments: the module set is fixed and
registered internally, so the individual *Module exports do not need to be passed to
init. They are exported for reference and inspection.
import { init, h } from 'ranuts/vnode';// init() returns a `patch` function.// The bundled modules (class, props, attrs, style, events) are registered automatically.const patch = init();const container = document.getElementById('app');// Build a vnode treelet vnode = h('div#app.container', { style: { color: 'red' } }, [ h('h1', 'Hello vnode'), h('button', { on: { click: () => console.log('clicked') } }, 'Click me'),]);// Initial render: pass a real DOM element as the old vnode to mount into itpatch(container, vnode);// Later: build an updated tree and patch the previous vnode into it.// Only the differences (text, style, listeners) are applied to the DOM.const newVnode = h('div#app.container', { style: { color: 'green' } }, [ h('h1', 'Hello again'), h('button', { on: { click: () => console.log('clicked') } }, 'Updated'),]);patch(vnode, newVnode);vnode = newVnode; // keep the latest tree for the next patch
// tag onlyh('div');// tag + datah('div', { class: { active: true } });// tag + a single text childh('span', 'hello');// tag + children arrayh('ul', [h('li', 'one'), h('li', 'two')]);// tag + data + childrenh('a', { attrs: { href: '/home' } }, 'Home');// CSS-style selectors set id and classesh('div#main.card.large', 'content'); // <div id="main" class="card large">content</div>// SVG namespaces are applied automatically when the selector starts with "svg"h('svg', { attrs: { width: 100, height: 100 } }, [h('circle', { attrs: { cx: 50, cy: 50, r: 40 } })]);
Returned by init(). On the first call, pass a real DOM Element as oldVnode to mount the
tree into it. On later calls, pass the previous VNode to diff-and-update in place. Returns
the new VNode, which you keep as the "old" value for the next call.
HTML attributes set via setAttribute (true/false toggle the attr)
Record<string, string | number | boolean>
attributesModule
class
Conditional classes: a name → boolean map
Record<string, boolean>
classModule
style
Inline styles: a name → value map (--var keys use CSS variables)
Record<string, any>
styleModule
on
Event listeners: event → handler (or an array of handlers)
Record<string, Function | Function[]>
eventListenersModule
key
Stable identity used by the diff algorithm to match/reorder children
string | number
(diff core)
ns
Namespace URI (set automatically for SVG subtrees)
string
(diff core)
hook
Per-vnode lifecycle hooks (Hooks)
Hooks
(type surface: see note)
Note: hook and the Hooks type are part of the public type surface. This trimmed
implementation drives the DOM through the module lifecycle (create / update /
destroy); per-vnode data.hook callbacks are not invoked by the current patch loop.