a gist to recap the current status, also available as library picker!
do one thing only and do it well
- µhtml (HTML/SVG auto-keyed and manual keyed render)
- augmentor (hooks for anything)
- wickedElements (custom elements without custom elements ... wait, what?)
compromise between small and features rich
- µce Custom Elements via µhtml (see a comparison w/ lit-element)
- µce-template a toolless Vue 3 like approach
- lighterhtml (HTML/SVG auto-keyed and manual keyed render)
- hyperHTML (HTML/SVG with no auto-keyed but optional manually keyed render)
- HyperHTMLElement (custom elements via hyperHTML)
- dom-augmentor (augmentor with DOM auto-hooked useEffect)
integrated libraries
- hookedElements (wickedElements + augmentor)
- neverland (lighterhtml + dom-augmentor)
- µland (µhtml + dom-augmentor)
- heresy and heresy-ssr (lighterhtml + augmentor + Custom Elements)
you choose what to use
- wickedElements & µhtml or lighterhtml (easy)
- hookedElements & µhtml or lighterhtml (even easier)
- dom-augmentor & µhtml or lighterhtml (not easy at all, try µland or neverland instead)
- native Custom Elements and µhtml or lighterhtml are an option too
µhtml | lighterhtml | hyperHTML | |
---|---|---|---|
released | early 2020 | late 2018 | early 2017 |
browsers compatibility | IE11+ & mobile | IE9+ & mobile | IE9+ & mobile |
brotli min & transpiled | 2.8K | 4.8K | 6.8K |
brotli min & ecmascript | 2.5K | 4.2K | not built |
performance | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
simple/friendly API | ✔ | ✔ | |
sparse attributes | ✔ | ✔ | |
events attribute | ✔ | ✔ | ✔ |
events options | ✔ | ✔ | |
style special case |
opt-in: µstyler | ✔ | ✔ |
aria special case |
✔ | since v3 | |
ref special case |
✔ | ✔ | |
.dataset=${obj} helper |
✔ | ✔ | |
data & props setters |
✔ | ||
direct .setters=${...} |
✔ | ✔ | since v2.32 |
boolean ?attr=${...} |
✔ | since v4.2 | since v2.34 |
has own components too | ✔ | ||
user-land defined intents | ✔ | ||
auto keyed nodes | ✔ | ✔ | |
manually keyed nodes | ✔ | ✔ | ✔ |
Custom Elements compat. | ✔ | ✔ | ✔ |
Shadow DOM compat. | ✔ | ✔ | ✔ |
NodeJS SSR compat. | µhtml-ssr / µcontent | heresy-ssr | viperHTML |
hooks friendly | ✔ | ✔ | ✔ |
<self-closing-tags /> |
✔ | ✔ | ✔ |
attributes callbacks | ref only |
✔ | ✔ |
content callbacks | ✔ | ✔ | |
promises as content | ✔ | ||
promises placeholders | ✔ | ||
dis/connected events | only version plus | ✔ | |
automatic fragments | ✔ | ✔ | ✔ |
interpolated HTML content | ✔ | ✔ | ✔ |
HTML injection safety | ✔ | ✔ | ⚠ |
Companions & wrappers | 1, 2, 3, 4, 5, 11 | 1, 4, 5, 6, 7, 8 | 1, 9 |
Features Score | 30 | 33.5 | 26.5 |
In hyperHTML an array of strings, or primitives, used as interpolation is automatically injected as HTML. This was an early design/feature mistake, non existent in other libraries.
Both lighterhtml and hyperHTML accepts an explicit {html: ...}
interpolation when injecting HTML is meant, hence not accidental, while uhtml can either use ref=callback(node)
or use html([txt])
instead of a template literal to parse [txt]
as if it was a template literal, but it's parsed each time if the array is different each time, hence discouraged, yet possible.
- vanilla JS
- µce / µce-template
- µhtml-ssr / µcontent
- wickedElements
- hookedElements
- neverland
- heresy
- heresy-ssr
- HyperHTMLElement
- µland
In older to newer library order
- hyperHTML: early 2017, first of a kind, early design, battle tested
- lighterhtml: late 2018, much better DX and easier to integrate than hyperHTML
- µhtml: early 2020, it's an essential subset of lighterhtml
- shipped to millions since 2018, enterprise grade
- today is best used via its HyperHTMLElement helper
- it included too many features that are rarely used
- it's features complete, mostly maintenance only
- it has a viperHTML SSR counter part, but it's been deprecated
- smaller than hyperHTML but even if it shares most of its core, it's faster in most cases
- it has a superior DX compared to hyperHTML
- almost every internal behavior can be customized
- it plays better with hooks and
ref=${...}
pattern
- it's an essential lighterhtml subset, for half of the size
- you can start small, and switch to lighterhtml without touching code
- the only cons is that is a subset with its own constrains and/or limited features
Isn't all this hard to maintain?
Pretty much all my libraries share the same code behind the scene. The difference is in the user-facing API and the pattern provided by such API (hooks vs Custom Elements vs just render, etc).
If you find a bug in heresy, as example, and it comes from augmentor, everything else hooks based will be patched automatically.
Same goes for lighterhtml and hyperHTML, sharing domdiff module and much more: if I fix something there it'll propagates everywhere else.
uhtml is a little exception to the latter case but ... well, it's tiny indeed, so it's the easiest to patch/maintain, specially 'cause it's code is mostly a copy/paste from lighterhtml, so that if I fix one, the other would pseudo-automatically follow.
What does auto-keyed mean?
All render engines avoid DOM trashes as much as they can. There are basically two ways to do this: use an index, automatically recycling the same node with such index, or use an explicit reference, manually coupling a specific part of the stack to such reference.
µhtml and lighterhtml support both approaches: in the former case, each node keeps being updated with new info, if found at the same position it was before, and only if the the new info changed, keeping data and UI strictly decoupled.
On the other hand, all rendering engines allows explicit references: same reference gets same node/stack each time, and DOM nodes are moved around whenever the reference is found in another position (i.e. list of items).
In the µhtml and lighterhtml case, the index, auto-keyed, approach, is the default, but you can always use html.for(...)
or svg.for(...)
to switch to the keyed pattern, following an example:
// auto-keyed lists example
render(viewNode, html`
<ul>
${items.map(item => html`<li>${item.text}</li>`)}
</ul>
`);
// manually keyed lists example
render(viewNode, html`
<ul>
${items.map(
item => html.for(item)`<li>${item.text}</li>`
)}
</ul>
`);
However, if the list of items have a unique id, you can always use <li data-id=${item-id}>
in case you need to retrieve the associated data later on.
@shannonmoeller
hyperHTML is the LTS library, I touch it, or add new features, only when it's absolutely necessary. As we use it in production, it's the most battle-tested one of them all, since 2018.
lighterhtml was, in 2018, the experimental playground to see what could've changed in hyperHTML, and it's been used in various projects without issues. As it's the most feature-complete for modern Web development, and used within different pattern-enablers projects, it's likely to replace in the long term hyperHTML, as its Developer Experience is too superior to compete with hyperHTML.
uhtml is a rip-off of lighterhtml, basically a copy/paste of the core, and it's going to be my favorite libarry to showcase anything about the Web, as I know well its contrains, that come from initial hyperHTML idea so it's natural for me to work with, but I also want the Web to be able to bring in 2.5K and solve forever Web UI common challenges with such tiny snowflake that is the result of 3 years of thinking, and using daily, all these patterns.
As all these share most of their core logic and libraries, it's not a big issue to keep all of these up to date, or bugs free, and indeed the bugs count for these libarries is close to zero (actually only 2 in hyperHTML, but nothing crucial there, and zero for the rest).