Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
L

lighterhtml

> 编程语言
Open source

The hyperHTML strength & experience without its complexity

759 stars0 likes2 views
WebsiteGitHub

About

The hyperHTML strength & experience without its complexity

lighterhtml

Social Media Photo by Kristine Weilert on Unsplash

Community Announcement

Please ask questions in the dedicated discussions repository, to help the community around this project grow ♥


The hyperHTML strength & experience without its complexity

  • faster than hyperHTML ⚡️
  • simpler than lit-html
  • fueling both neverland and heresy

Looking for something even smaller?

If you want 90% of functionalities offered by lightetrhtml for 2/3rd of its size, check µhtml, which is also used in "micro custom elements" µce library, hence µce-template too, plus "micro land" µland


4.2 Highlights

  • the new ?boolean=${value} syntax from µhtml has landed in lighterhtml too. Feel free to read this long discussion to better understand why this syntax is better, or necessary.

faster than hyperHTML

Even if sharing 90% of hyperHTML's code, this utility removed all the not strictly necessary parts from it, including:

  • no Component, no define and/or intents, no connect or disconnect, and no promises (possibly in later on), everything these days can be easily handled by hooks, as example using the dom-augmentor utility
  • html content is never implicit, since all you have to do is to write html before any template when you need it. However, the {html: string} is still accepted for extreme cases.

Removing these parts made the output smaller in size (less than 6K) but it also simplified some underlying logic.

Accordingly, lighterhtml delivers raw domdiff and domtagger performance in an optimized way.

If you don't believe it, check the DBMonster benchmark

simpler than lit-html

In lit-html, the html function tag is worthless, if used without its render.

In lighterhtml though, the html.node or svg.node tag, can be used in the wild to create any, one-off, real DOM, as shown in this pen.

javascript
// lighterhtml: import the `html` tag and use it right away
import {html} from '//unpkg.com/lighterhtml?module';

// a one off, safe, runtime list 
const list = ['some', 'nasty', 'list'];
document.body.appendChild(html.node`
  
${list.map(text => html.node`
    
${text}

  `)}
  

`);

Strawberry on top, when the html or svg tag is used through lighterhtml render, it automatically creates all the keyed performance you'd expect from hyperHTML wires, without needing to manually address any reference: pain point? defeated!

Available as lighterhtml-plus too!

If you are looking for Custom Elements like events out of the box, lighterhtml-plus is your next stop

How to import lighterhtml/-plus

Following, the usual multi import pattern behind every project of mine:

  • via global lighterhtml CDN utility: , and const {render, html, svg} = lighterhtml
  • via ESM CDN module: import {render, html, svg} from 'https://unpkg.com/lighterhtml?module'
  • via ESM bundler: import {render, html, svg} from 'lighterhtml'
  • via CJS module: const {render, html, svg} = require('lighterhtml')

What's the API ? What's in the export ?

The module exports the following:

  • html tag function to create any sort of HTML content when used within a render call. It carries two extra utilities, html.for(ref[, id]), to hard-reference a specific node, and html.node to create one-off dom nodes in the wild without using the render.
  • svg tag function to create any sort of SVG content when used within a render call. It carries two extra utilities, svg.for(ref[, id]), to hard-reference a specific node, and svg.node to create one-off dom nodes in the wild without using the render.
  • render(node, fn|Hole) to pollute a node with whatever is returned from the fn parameters, including html or svg tagged layout
  • Hole class for 3rd parts (internal use)

You can test live a hook example in this Code Pen.

What's different from hyperHTML ?

  • the wired content is not strongly referenced as it is for hyperHTML.wire(ref[, type:id]) unless you explicitly ask for it via html.for(ref[, id]) or svg.for(ref[, id]), where in both cases, the id doesn't need any colon to be unique. This creates content hard wired whenever it's needed.
  • the ref=${object} attribute works same as React, you pass an object via const obj = useRef(null) and you'll have obj.current on any effect. If a callback is passed instead, the callback will receive the node right away, same way React ref does.
  • if the attribute name is aria, as in aria=${object}, aria attributes are applied to the node, including the role one.
  • deprecated: if the attribute name is data, as in data=${object}, the node.dataset gets populated with all values.
  • if the attribute name is .dataset, as in .dataset=${object}, the node.dataset gets populated with all values.
  • intents, hence define, are not implemented. Most tasks can be achieved via hooks.
  • promises are not in neither. You can update asynchronously anything via hooks or via custom element forced updates.
  • the onconnected and ondisconnected special events are available only in lighterhtml-plus. These might come back in the future but right now dom-augmentor replaces these via useEffect(callback, []). Please note the empty array as second argument.
  • an array of functions will be called automatically, like functions are already called when found in the wild
  • the Component can be easily replaced with hooks or automatic keyed renders
  • if a listener is an Array such as [listener, {once: true}], the second entry of the array will be used as option.
javascript
const {render, html} = lighterhtml;

// all it takes to have components with lighterhtml
const Comp = name => html`
Hello ${name}!
`;

// for demo purpose, check in console keyed updates
// meaning you won't see a single change per second
setInterval(
  greetings,
  1000,
  [
    'Arianna',
    'Luca',
    'Isa'
  ]
);

function greetings(users) {
  render(document.body, html`${users.map(Comp)}`);
}

Documentation

Excluding the already mentioned removed parts, everything else within the template literal works as described in hyperHTML documentation.

A basic example

Live on Code Pen.

javascript
import {render, html} from '//unpkg.com/lighterhtml?module';

document.body.appendChild(
  // as unkeyed one-off content, right away 
  html.node`any one-off content!

// the *deprecated* dataset special case
html`

// the *new* dataset special case
html`

This means the previous data=${...} behavior should be substituted with .dataset=${...} and it's now possible to better reflect declarative intents in nodes, simplifying both data-* attributes and aria-* ones.

Please note using data-name=${value}, as well as aria-name=${value} is still handled like any other regular attribute, hence it will work as expected, actually faster when the values don't change frequently, as both aria and data special cases simply loop through the object keys and assign their values to node's attributes.

V2.1 Introducing A New Listener Feature

Until version 2.1, there was no way to define different options for any listener. The el.addEventListener(type, listener, false) was the only kind of operation possible behind the scene.

In v2.1 though, whenever a second option is needed, it is now possible to pass an Array instead, in the form [listener, {once: true}], as example, or [listener, true] to use capture instead of bubbling, and so on and so forth.

Bear in mind, specially for the once case, if the listener is different per each update, like onclick=${[() => stuff(), {once: true}]}, it will be set each time that update happens, so that in this case is better to use always the same listener, either via outer scope callback, or via reference, using useRef and the handleEvent pattern, as example.

If you never needed to add a different second option, there is nothing you should do, everything will work exactly as it did before.

V2 Breaking Changes & Improvements

Breaking

  • dropped the ambiguous ability to produce nodes when no render(...) is invoked. When needed, which is the minority of the cases, you need to explicitly use html.node or svg.node, instead of just html or svg. For every other cases, use render(where, what).
  • the render(where, what) does not need a callback anymore. You can now render(node, html` content `) right away. If a callback is provided, that will still be invoked.
  • removed useHook as it's unnecessary since you can use useRef through html.for(...) or svg.for(...) within any useRef provided by your library of choice (i.e. dom-augmentor)
  • the recently introduced inner.html/svg has been removed, as completely unnatural and error prone (just use html anywhere, it'll work).

Improvements

  • a fundamental core-logic implementation that was trashing any node after one or more collections has been refactored and fixed. The current logic create a stack per each array found down the rendering road, isolating those DOM updates per stack. This means that performance have been improved, and GC operations reduced.
  • html and svg template literals tags, now offer both .for(ref[, id]) and .node, to either retain the same content (keyed render) or create fresh new nodes out of the box as one-off operation (via .node).
  • slightly reduced code size, which is always nice to have, after a refactoring

V1 Changes + New Feature

Removed transform export and made default domtagger customizable via custom export.

…

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

JavaScript

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言