@ragyjs/dom-renderer
v1.4.1
Published
Lightweight DOM rendering package
Maintainers
Readme
@ragyjs/dom-renderer
A tiny DOM renderer for building UI without frameworks.
It converts plain JS objects into real DOM nodes, with support for:
- Attributes & styles
- Children
- Dynamic children with functions
- Event listeners (with debounce & throttle)
- Abortable listeners via
AbortController - Zero dependencies
When to use DomRenderer
DomRenderer is useful when you need to build lightweight UI components without pulling in heavy frameworks.
Installation
npm install @ragyjs/dom-rendererESM-only. Works with Node 18+ or any modern bundler (Vite, Webpack, etc.).
Quick Example
<div id="app"></div>import { DomRenderer } from "@ragyjs/dom-renderer";
const r = new DomRenderer();
const vnode = {
tag: "button",
class: "btn",
text: "Click me!",
on: {
click: () => alert("Hello World"),
},
};
const root = document.getElementById('app');
const node = r.render(vnode);
Array.isArray(node)
? root.append(...node)
: root.append(node);VNode structure
A VNode can be:
- Primitive:
string | number | null | false - Function returning a VNode
- Object with the following keys:
{
tag?: string; // default: 'div'
class?: string;
attr?: Record<string, any>;
style?: Record<string, any>;
text?: string | number;
html?: string; // trusted HTML
children?: VNode | VNode[] | (() => VNode | VNode[]);
on?: Record<string,
Function | {
handler: Function;
debounce?: number; // ms
throttle?: number; // ms
options?: AddEventListenerOptions;
}
>;
ref?: any;
signal?: AbortSignal; // overrides default signal
}API
new DomRenderer(options?)
options.hooks.onRender(ref, el)→ called after each node is created.
render(vnode)
- Returns a
NodeorNode[].
abort()
- Aborts all listeners registered with the renderer’s current signal.
setSignal(signal)
- Use this only if you need to attach a custom signal.
- In most cases, you don’t need it — calling
.abort()on the renderer is enough.
Events
- Simple function:
on: { click: () => console.log("clicked"); } - With debounce/throttle & options:
on: { input: { handler: onInput, debounce: 300, options: { passive: true } } }
Abortable listeners
Each DomRenderer has its own built-in AbortController.
That means you can call .abort() on the renderer directly to remove its listeners — no need to call setSignal() unless you want to share signals across multiple renderers.
const r = new DomRenderer();
const vnode = {
tag: "button",
text: "Abortable",
on: { click: () => console.log("X") },
};
document.body.appendChild(r.render(vnode));
// later
r.abort(); // removes all listeners for this rendererIf you want multiple renderers (or other code) to share the same signal, then you can use setSignal(signal).
But for most usage, .abort() is all you need.
Attributes & Styles
attr: {
id: 'save',
disabled: true,
'data-role': 'primary'
},
style: {
width: '200px',
'--accent': '#09f'
}Children
children: [
{ tag: "h2", text: "Title" },
() => ({ tag: "p", text: "Generated child" }),
];const animals = ["monkey", "cat", "dog", "banana"];
const vnode = {
tag: "ul",
children: [
() =>
animals.map((item) => {
if (item === "banana") {
return {
tag: "li",
text: `${item} is not an animal!`,
};
}
return { tag: "li", text: `I am a ${item}` };
}),
],
};HTML (trusted only)
{
html: "<strong>Trusted</strong> content";
}Be careful: raw HTML can cause XSS if user input is injected.
License
MIT © Ragy — see LICENSE
