@thi.ng/hdom
As of 2018-03-03 this package is now called @thi.ng/hdom, formerly @thi.ng/hiccup-dom
About
Lightweight reactive DOM components using only vanilla JS data structures
(arrays, objects, closures, iterators), based on
@thi.ng/hiccup.
Benefits:
- Use the full expressiveness of ES6/TypeScript to define, annotate & document components
- Clean, functional component composition and reuse
- No pre-processing / pre-compilation steps
- No string parsing / interpolation steps
- Less verbose than HTML, resulting in smaller file sizes
- Static components can be distributed as JSON (or dynamically compose components, based on JSON data)
- Supports SVG, arbitrary elements, attributes, events
- CSS conversion from JS objects
- Suitable for server side rendering (by passing the same data structure to @thi.ng/hiccup's
serialize()
) - Fairly fast (see benchmark example below)
- Only ~10KB minified
import * as hiccup from "@thi.ng/hiccup";
import * as hdom from "@thi.ng/hdom";
const greeter = (name) => ["h1.title", "hello ", name];
const counter = (i = 0) => {
return () => ["button", { onclick: () => (i++) }, `clicks: ${i}`];
};
const app = () => {
return ["div#app", [greeter, "world"], counter(), counter(100)];
};
hdom.start(document.body, app());
hdom.createDOM(document.body, hdom.normalizeTree(app()));
console.log(hiccup.serialize(app()));
Live demo | standalone example
No template engine & no precompilation steps needed, just use the full
expressiveness of ES6/TypeScript to define your DOM tree. The additional
benefit of using TypeScript is that your UI components can become strongly
typed, since they're just normal functions, can use generics, overrides,
varargs etc.
The actual DOM update is based on the minimal edit set of the recursive
difference between the old and new DOM trees (both nested JS arrays).
Components can be defined as static arrays, closures or objects with life cycle
hooks (init, render, release).
The syntax is inspired by Clojure's
Hiccup and
Reagent projects, however the latter is a
wrapper around React, whereas this library is standalone, more lowlevel &
less opinionated.
If you're interested in using this, please also consider the
@thi.ng/atom and
@thi.ng/rstream
packages to integrate app state handling, event streams & reactive value
subscriptions. More examples are forthcoming...
Status
This project is currently still in BETA. The overall "API" is stable, but there's still further work planned on optimization and generalization beyond the standard browser DOM use cases. Furthermore, the project has been used for several projects in production since 2016.
Installation
yarn add @thi.ng/hdom
New since 2018-03-15: You can now create a preconfigured app skeleton
using @thi.ng/atom, @thi.ng/hdom & @thi.ng/router using the
create-hdom-app project generator:
yarn create hdom-app my-app
cd my-app
yarn install
yarn start
Usage examples
Even though the overall approach should be obvious from the code examples
below, it's recommended to first study the
@thi.ng/hiccup
reference. It's also important to point out, that this project currently
has some differences as to how some attribute and iterables are treated and/or
are supported in general. This project also has additional features (e.g. life
cycle hooks), which aren't needed for the static serialization use cases of
hiccup. Both experiments started in early 2016, but have somewhat evolved
independently and require some conceptional synchronization.
Dataflow graph SVG components
This is a preview of the upcoming @thi.ng/estuary package:
Source | Live demo
Todo list
A fully documented todo list app with undo / redo feature is here:
Source | Live demo
Cellular automata
Source | Live demo
SVG particles
Source | Live demo
JSON based components
Source | Live demo
Basic usage patterns
The code below is also available as standalone project in: /examples/dashboard
Live demo here
import { start } from "@thi.ng/hdom";
const box = (prefix, body) =>
["div",
{
style: {
display: "inline-block",
background: "#ccc",
width: "30%",
height: "40px",
padding: "4px",
margin: "2px",
"text-align": "center"
}
},
["strong", prefix], ["br"], body];
const counter = (id, from = 0, step = 1) => () => box(id, (from += step).toLocaleString());
const timer = () => box("time", new Date().toLocaleTimeString());
const app = (() => {
const users = counter("users");
const profits = counter("$$$", 1e6, 99);
return () => ["div", ["h1", "Dashboard"], users, profits, timer];
})();
window.addEventListener("load", () => start("app", app));
@thi.ng/rstream integration
TODO example forthcoming...
Benchmark
A stress test benchmark is here: /examples/benchmark
Live demo here
Based on user feedback collected via
Twitter, performance
should be more than acceptable for even quite demanding UIs. In the 192/256
cells configurations this stress test causes approx. 600/800 DOM every single
frame, something very unlikely for a typical web app. In Chrome 64 on a MBP2016
this still runs at a pretty stable 30fps (50 frame SMA).
Authors
License
© 2016 - 2018 Karsten Schmidt // Apache Software License 2.0