Docs / Core / Rendering & VNodes
ven.js
Rendering & VNodes
Build a virtual DOM with venjs.<tag>() and mount it with venjs.render(). In a multi-page app each page file exports a component and the router renders the active one.
venjs.createElement(tag, props, ...children)
Creates a VNode. It is also exposed as window.v. Every venjs.div(), venjs.h1(), etc. is just a thin call to this.
venjs.createElement(tag, props, ...children) → VNode
// All equivalent
venjs.createElement("div", { class: "x" }, "hi");
venjs.div({ class: "x" }, "hi");
v("div", { class: "x" }, "hi");
Children
- Children are flattened recursively (
Infinity) andnull/undefinedentries are removed. - Primitive children (strings, numbers) become text VNodes automatically.
- Nested arrays are supported:
venjs.ul({}, [items.map(...), footer]).
VNode shape
{ tag, key, props, children, dom }
| Field | Meaning |
|---|---|
tag | HTML tag name, or "TEXT_ELEMENT" for text nodes. |
key | Optional stable key for list reconciliation. |
props | Attributes, style, events, ref, onMount, onUnmount. |
children | Array of child VNodes (text nodes included). |
dom | Set by the engine after the node is created in the real DOM. |
venjs.render(container, componentFactory)
Mounts a component factory into a container element and keeps it patched. On the first run it clears the container and appends the DOM; on subsequent runs it diffs and patches.
const unmount = venjs.render(container, factory);
| Argument | Description |
|---|---|
container | A real DOM element (e.g. document.getElementById("app")). |
componentFactory | A function returning a VNode, or a plain VNode. Pass a function so it re-evaluates on each effect run. |
It returns an unmount function that stops the internal effect, calls onUnmount hooks, and clears the container.
const stop = venjs.render(app, MyComponent);
// later
stop(); // tear down
Note:
venjs.render, venjs.mount, and venjs.ven are equivalent aliases. Prefer venjs.render.Props & attributes
The engine classifies props into events, properties, and lifecycle hooks:
- Events — any key starting with
on(onclick,oninput,onmouseenter). Listeners are added/removed on change. - Properties — everything else that is not
children,key,ref,onMount,onUnmount, or an event. These map to DOM properties or attributes. - Lifecycle —
onMount,onUnmount.
Special prop handling
| Prop | Behavior |
|---|---|
class / className | Sets dom.className. |
style | Object form is diffed against the previous style; removed keys are reset to "". |
innerHTML | Set directly via dom.innerHTML. |
value, checked, selected, disabled | Set as DOM properties (so inputs stay controlled). |
ref | A function called with the real DOM node: ref: (el) => .... |
key | Used for stable list reconciliation. |
onMount / onUnmount | Lifecycle callbacks receiving (dom, vnode). |
venjs.input({
class: "field",
style: { color: "teal", padding: "8px" },
value: name.value,
oninput: (e) => (name.value = e.target.value),
ref: (el) => el.focus(),
onMount: (el) => console.log("mounted", el),
onUnmount: (el) => console.log("unmounted", el)
})
Reconciliation engine
patch(parent, oldVNode, newVNode, index) updates the real DOM minimally:
- New node with no old → create and append, then fire
onMount. - Old node removed → fire
onUnmountand remove. - Different tag/key → replace and swap mount/unmount.
- Text node → update
nodeValueonly when changed. - Same node → update props, then recurse into children. Excess old children are removed backwards to keep indices stable.