My FE/DOM Libraries

October 4, 2022 · View on GitHub

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

µhtmllighterhtmlhyperHTML
releasedearly 2020late 2018early 2017
browsers compatibilityIE11+ & mobileIE9+ & mobileIE9+ & mobile
brotli min & transpiled2.8K4.8K6.8K
brotli min & ecmascript2.5K4.2Knot built
performance⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
simple/friendly API
sparse attributes
events attribute
events options
style special caseopt-in: µstyler
aria special casesince v3
ref special case
.dataset=${obj} helper
data & props setters
direct .setters=${...}since v2.32
boolean ?attr=${...}since v4.2since 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 / µcontentheresy-ssrviperHTML
hooks friendly
<self-closing-tags />
attributes callbacksref only
content callbacks
promises as content
promises placeholders
dis/connected eventsonly version plus
automatic fragments
interpolated HTML content
HTML injection safety
Companions & wrappers1, 2, 3, 4, 5, 111, 4, 5, 6, 7, 81, 9
Features Score3033.526.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
- - -

F.A.Q.

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.