A tiny Hiccup-style view library with independent entry points for keyed DOM diffing, one-off DOM creation, and server-side HTML rendering.
npm install @jdlanglois/viewimport { render } from "@jdlanglois/view";
render(["section#counter.panel",
{ "aria-live": "polite" },
["p", `Count: ${count}`],
items.map(item => ["span.item", { key: item.id }, item.label]),
null,
["button", { onClick: increment }, "Increment"],
], target);Repeated renders into the same target preserve compatible elements and update
attributes, event listeners, text, and children. Stable scalar keys identify
list children. A parent can instead derive child keys with { key: attrs => Number(attrs.id) }. Keys must be unique among siblings.
import { dom } from "@jdlanglois/view/dom";
const button = dom(["button#save.primary", { onClick: save }, "Save"]);
toolbar.append(button);A single element view returns a detached Element. A primitive returns Text;
multiple roots or an empty value return a DocumentFragment. This entry has no
render state or diffing code.
import { html } from "@jdlanglois/view/server";
const output = html(["article.card", ["h1", title], ["p", body]]);html() needs no DOM. It escapes text and attribute values, omits event
handlers and keys, supports boolean and style attributes, concatenates fragment
roots, and emits HTML void elements without closing tags. Raw HTML is not
accepted, so dynamic values are escaped by default.
Complete ESM entry sizes from the minified production build:
| Entry | Minified | Minified + gzip |
|---|---|---|
| Diffing renderer | 3,823 B | 1,677 B |
| One-off DOM | 2,245 B | 997 B |
| Server renderer | 2,010 B | 984 B |
- The first array item is an Emmet-style selector such as
div#app.panel. - An optional attributes object follows the selector.
- Remaining values are children; nested arrays are flattened.
null,undefined, andfalsechildren are omitted.- Strings, numbers, and bigints become text.
npm run typecheck
npm test
npm run buildMIT