Skip to content

Instantly share code, notes, and snippets.

Last active February 6, 2024 15:50
Show Gist options
  • Save WebReflection/761052d6dae7c8207d2fcba7cdede295 to your computer and use it in GitHub Desktop.
Save WebReflection/761052d6dae7c8207d2fcba7cdede295 to your computer and use it in GitHub Desktop.
A recap of my FE / DOM related libraries

My FE/DOM Libraries

a gist to recap the current status, also available as library picker!

Minimalistic Libraries

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?)

Reactive Primitives (client/server)

  • µsignal (a tiny blend of solid-js and preact/signals)
  • µhooks (a React hooks alternative)

Enriched Libraries

compromise between small and features rich

Combo Libraries

integrated libraries

Minimalistic Combo

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

DOM Engines Features Comparison

µ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

About HTML injection safety

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.

Companions & wrappers

  1. vanilla JS
  2. µce / µce-template
  3. µhtml-ssr / µcontent
  4. wickedElements
  5. hookedElements
  6. neverland
  7. heresy
  8. heresy-ssr
  9. HyperHTMLElement
  10. µland

DOM Engines Summary

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

hyperHTML Pros and Cons

  • 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

lighterhtml Pros and Cons

  • 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

µhtml Pros and Cons

  • 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`
    ${ => html`<li>${item.text}</li>`)}

// manually keyed lists example
render(viewNode, html`
      item => html.for(item)`<li>${item.text}</li>`

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.

Copy link

Copy link

@WebReflection that is great. I'm so glad this gets in there. I think there are places where non-keyed benefits are worthwhile but keyed approach is essential.

Aside: I never understood why this feature was not more common in many might as well be made for Web Component libraries. VDOM community went through the same cycle 2014-15ish and landed on keyed as the default for the most part. I think it is even more important here.

But I get that because of being essentially a single pass system you need a special helper method to do keyed instead of just using the language so to speak. This is one of the only things of this nature with this approach so it's a harder sell. Whereas reactive libraries have the same constraint (also single pass on render) but we need many more of those so adding one for mapping isn't that big of a deal.

Copy link

WebReflection commented Feb 27, 2020

@ryansolid it landed, and forced me to create udomdiff, otherwise js-framework-benchmark would've failed big time on keyed.

Well, it ended up even faster than lighterhtml, but the size now is 2.8, instead of 2.5 ... I guess we can live with that.


Keyed Performance

A comparison with uhtml, lighterhtml, hyperHTML, and lit-html


Copy link

Very nice. It looks better across the board especially with reductions in size and memory usage. But definitely nice performance boost, that should move it into the top 15 (skipping reference vanilla implementations) making it the uncontested fastest non-reactive tagged template literal implementation in the benchmark.

Copy link

msand commented Feb 29, 2020

Thanks for the summary @WebReflection fascinating to follow your work. I'm wondering, what it would take for me to integrate react-native-svg, or make it compatible with nativeHTML, perhaps that could increase interest in it? Been thinking about making it compatible with flutter as well, and normal native mobile development. Was intending to look into nativeHTML some time ago, but work and other priorities haven't made that feasible yet. Would you be able to compare it to the architecture of react-native / ViewManagers? Perhaps it can help me make a proof of concept faster.

Copy link

@msand as pity as it sounds, I can't, and don't want to, compete with React native. It's like ... one person against Spartans.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment