• Stars
    star
    707
  • Rank 61,870 (Top 2 %)
  • Language
    JavaScript
  • License
    ISC License
  • Created over 4 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A micro HTML/SVG render

µhtml

Downloads build status Coverage Status CSP strict

snow flake

Social Media Photo by Andrii Ganzevych on Unsplash

micro html is a ~2.5K lighterhtml subset to build declarative and reactive UI via template literals tags.

📣 Community Announcement

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


V3.0 Update

  • removed IE and legacy Edge compatibility
  • simplified template parsing
  • cleaned up dependencies

V2.8 Update

  • added µhandlers foreign export to enable arbitrary attributes handling!
import {html, foreign} from 'uhtml';

const handler = (node, name, value) => {
  // P, any, {data: 123}
  console.log(node, name, value);
  // return null/undefined to remove it
  return value.data;
};

html`<p any=${foreign(handler, {data: 123})}>foreign</p>`;

V2.5 Update

  • an interpolated value, within a DOM element, can now be a function, enabling a world of µhtml extending possibilities, including intents, hence aligning the behavior with both lighterhtml and hyperHTML. That is: <el>${callback}</el>! The callback will be invoked with the comment pin/placeholder as unique argument, where its parentNode would be the element containing such comment, if needed, and its returned value will be passed along the same mechanism that resolves already all other cases.

V2.4 Update

  • a new ?attribute=${value} prefix, with a question mark, has landed, after this long debate, and based to the fact µhtml never wants to be ambiguous. However, the mighty lit-html put an end to the debate, disambiguating through a ? question mark prefix, which explicits developers intents, and works well across browsers. So that's it: whenever you expect an attribute to be in, or out, use ?name=${value} and win the day 🥳

V2.2 Update

  • the new.js file has been renamed as es.js to align with other modules of mine that follow the same pattern.
  • this module now exports its very same utilities via uhtml/async, in order to automatically resolve asynchronous values passed along the template. Please note this means that exported render, html, and svg tags, are all asynchronous, hence these all return a promise.
  • the async.js file is now published too, compatible with ES2015+ browsers (no async / await used)

How To

Example

import {render, html, svg} from 'uhtml/async';

render(document.body, html`a${Promise.resolve('b')}c`);

How To Use µhtml

Install the module via npm i uhtml and consume it like so:

import {render, html, svg} from 'uhtml';
// const {render, html, svg} = require('uhtml');

render(document.body, html`<h1>Hello 👋 µhtml</h1>`);

Alternatively you can use a CDN such as unpkg, as shown in this demo.

<script src="https://unpkg.com/uhtml">/* global uhtml */</script>
<!-- or -->
<script type="module">
import {render, html, svg} from 'https://unpkg.com/uhtml?module';
</script>

API Documentation

Most information about µhtml is written in the documentation file, but the following gives essential details.

API Summary

The module exports the following functionalities:

  • a render(where, what) function to populate the where DOM node with what content, which can be a DOM node, or the return value of html and svg tags. The render function returns the where DOM node itself.
  • an html template literal tag, to produce any sort of HTML content.
  • an svg template literal tag, to produce any sort of SVG content.
  • both html and svg implement a .for(reference[, id]) template tag function for keyed weak relationships within the node. Please don't overuse this feature, as 90% of the time is not necessary, and it could make the rendering slower than it should be. Also, consider the ref attribute, in case a reference to the current node is needed at any time.
  • both html and svg implement a .node template tag function for one-off HTML or SVG creation. Please don't use html.node one off nodes within render(...) calls, as this utility exists to help create fragments or nodes that should be manually added to the DOM, and not through render calls.
About Attributes

Any element can have one or more attributes, either interpolated or not.

render(document.body, html`
  <div id="main"
        class=${`content ${extra}`}
        data-fancy=${fancy}>
    <p contenteditable=${editable}
        @click=${listener}
        onclick=${listener}
        class="${['container', 'user'].join(' ')}">
      Hello ${user.name}, feel free to edit this content.
    </p>
  </div>
`);

These are the rules to follow for attributes:

  • interpolated attributes don't require the usage of quotes, but these work either way. name=${value} is OK, and so is name="${value}" or even name='${value}'
  • you cannot have sparse attribute interpolations: always use one interpolation to define each attribute that needs one, but never write things like style="top:${x};left${y}" as the parser will simply break with the error bad template. Use template literals within interpolations, if you want to obtain the exact same result: style=${`top:${x};left${y}`}
  • if the passed value is null or undefined, the attribute will be removed. If the value is something else, it will be set as-is as the value. If the attribute was previously removed, the same attribute will be placed back again. If the value is the same as it was before, nothing happens
  • if the attribute name starts with on or @, as example, onclick=${...} or @click=${...}, it will be set as a listener. If the listener changes, the previous one will be automatically removed. If the listener is an Array like [listener, {once:true}], the second entry of the array will be used as the listener's options.
  • if the attribute starts with a . dot, as in .setter=${value}, the value will be passed directly to the element per each update. If such a value is a known setter, either native elements or defined via Custom Elements, the setter will be invoked per each update, even if the value is the same
  • new: if the attribute starts with a ? question mark, as in ?hidden=${value}, the value will be toggled, accordingly to its truthy or falsy, value.
  • if the attribute name is ref, as in ref=${object}, the object.current property will be assigned to the node, once this is rendered, and per each update. If a callback is passed instead, the callback will receive the node right away, similar to how React ref does. Please note that conditional renders will not cleanup the reference, if this is not assigned to the new node.
  • if the attribute name is aria, as in aria=${object}, aria attributes are applied to the node, including the role one.
  • if the attribute name is .dataset, as in .dataset=${object}, the node.dataset gets populated with all values.

The following is an example of both the aria and data cases:

// the aria special case
html`<div aria=${{labelledBy: 'id', role: 'button'}} />`;
//=> <div aria-labelledby="id" role="button"></div>

// the data special case
html`<div .dataset=${{key: 'value', otherKey: 'otherValue'}} />`;
//=> <div data-key="value" data-other-key="otherValue"></div>
About HTML/SVG Content

It is possible to place interpolations within any kind of node, and together with text or other nodes too.

render(document.body, html`
  <table>
    ${lines.map((text, i) => html`
      <tr><td>Row ${i} with text: ${text}</td></tr>
    `)}
  </table>
`);

There are only few exceptional nodes that do not allow sparse content within themselves:

  • <plaintext>${content}</plaintext>, deprecated, yet it cannot contain comments
  • <script>${content}</script>, it can contain comments, but only whole text can be replaced
  • <style>${content}</style>, it cannot contain comments
  • <textarea>${content}</textarea>, same as above
  • <title>${content}</title>, same as above
  • <xmp>${content}</xmp>, same as above

The following is an example on how to populate these nodes (wrong + right way):

// DON'T DO THIS
render(document.body, html`
  <style>
    body { font-size: ${fontSize}; }
  </style>
  <textarea>
    Write here ${user.name}
  </textarea>
`);

// DO THIS INSTEAD
render(document.body, html`
  <style>
  ${`
    body { font-size: ${fontSize}; }
  `}
  </style>
  <textarea>
  ${`
    Write here ${user.name}
  `}
  </textarea>
`);

Beside nodes where the content will inevitably be just text (e.g., as it is for style or textarea), every other interpolation can contain primitives as strings, numbers, booleans, or the returned value of html or svg, plus regular DOM nodes.

The only special case are Array of either primitives, or returned values from html or svg.

render(document.body, html`
  <ul>
    <li>This is ${'primitive'}</li>
    <li>This is joined as primitives: ${[1, 2, 3]}</li>
    ${lines.map((text, i) => html`
      <li>Row ${i} with content: ${text}</li>
    `)}
  </ul>
`);
About Rendering Content

The second what argument of the render(where, what) signature can be either a function, whose return value will be used to populate the content, the result of html or svg tags, or a DOM node, so that it is possible to render within a render.

const Button = selector => {
  const button = document.querySelector(selector);
  return count => render(button, html`Clicks: ${count}`);
};

const Clicker = selector => {
  const button = Button(selector);
  return function update(count) {
    return render(document.body, html`
      <div onclick=${() => update(++count)}>
        Click again:
        ${button(count)}
      </div>
    `);
  };
}

const clicker = Clicker('#btn-clicker');
clicker(0);
About keyed renders

µhtml html and svg tags implement exactly the same API offered by lighterhtml.

This means that both html.for(reference[, id]) and svg.for(reference[, id]) will weakly relate the node with the reference and an optional unique id, instead of using its internal auto-referenced algorithm.

render(document.body, html`
  <ul>
    ${items.map(item => html.for(item)`
      <li>Keyed row with content: ${item.text}</li>
    `)}
  </ul>
`);
About data purity

On more than one occasion developers got bitten by the fact µhtml can produce some cryptic error when null, or empty content is provided as interpolation/hole.

The problem is pretty simple:

  • don't loop / pass data that should not be rendered
  • sanitize data upfront instead of expecting this library to magically understand where, and how, such data should not be rendered

A basic example would be the following:

const dirtyData = [
  {name: 'first'},
  null,
  {name: 'second'}
];

render(document.body, html`
  <ul>
    ${dirtyData.map(item => {
      // âš  this should not happen in the first place!
      if (!item)
        return null;
      return html`<li>${item.name}</li>`;
    })}
  </ul>
`);

There are at least two workarounds to consider:

  • use data.filter(item => validate(item)).map(...)
  • return an actual node:
  if (!item)
    return html`<!--ignore-->`;

Between these two workarounds, I believe the cleanest one is the former: sanitize data upfront!

The benefits are:

  • there are no unnecessary nodes in the DOM
  • there are no weird cases to consider
  • the mapping is pure: data in, node out
About Custom Elements

Custom Elements are available either via µce (micro custom elements, a 3K library based on µhtml), or via vanilla JS, as demoed in WebComponents.dev.

Compatibility

This module works in IE11, Edge, and every other Desktop to Mobile browser, including KaiOS.


µhtml vs lighterhtml

You could read an exhaustive summary of features differences, but the first thing to keep in mind, is that lighterhtml is at par with uhtml features, but not vice-versa, meaning if you need anything more, you can always switch to lighterhtml later on, and without changing a single line of code.

The following is a list of other points to consider when choosing µhtml over of lighterhtml (or vice-versa).

Differences from lighterhtml
  • there are no sparse attributes, each attribute must have a single interpolated value: attribute=${value} is OK, attribute="${a}${b}" is not, and attribute="some ${'partial'}" is not allowed neither.
  • the interpolations are simple: primitive, or array of primitives, and nodes, or array of nodes.
  • the style attribute is not special at all: if you want to pass objects there, please transform these as you prefer.
  • the domdiff has been replaced with udomdiff, with a new blazing fast and super small diffing algorithm written from scratch
  • the template argument is not normalized. If you target browsers with issue with such argument, please be sure you transpile your code with latest Babel before shipping to production
  • no domtagger whatsoever, you can't change the current behavior of the library in any way
Similar or better than lighterhtml
  • uhtml should not suffer any of the IE11/Edge issues, or invalid SVG attributes warnings, as the parsing is done differently 🎉
  • nested html and svg are allowed like in lighterhtml. The version 0 of this library didn't allow that, hence it was more "surprise prone". uhtml in that sense is more like a drop-in replacement for lighterhtml, and vice-versa
  • keyed results via htmlfor(...) or svg.for(...), as well as one-off node creation, via html.node or svg.node are the same found in lighterhtml
  • both aria=${object}, and ref=${...} special attributes work same as lighterhtml
  • the .dataset=${object} helper works the same as lighterhtml
  • the .property=${...} direct setter is still available
  • self closing nodes are also supported, go wild with <custom-elements /> or even <span />
  • the wire parsing logic has been simplified even more, resulting in slightly better bootstrap and update performance
  • it's half the production size of lighterhtml, mostly because ...
  • there are no 3rd party dependencies, except for µparser, µdomdiff, and for @ungap/create-content, needed only for IE11, but removable via @ungap/degap, same way I've done it here, or babel-plugin-remove-ungap. The compressed final size difference is just around ~0.2K though.
µhtml library goals
  • be an essential/ideal companion for wickedElements, hookedElements, or any third party that would like a lighterhtml like API, without the extra weight
  • keep it as simple as possible, but not simpler
  • see if there is room for improvements in lighterhtml whenever uhtml simplifications allow to be ported there

V2 Breaking Change

The recently introduced data helper could conflict with some node such as <object>, hence it has been replaced by the .dataset utility. Since element.dataset = object is an invalid operation, the sugar to simplify data- attributes is now never ambiguous and future-proof: <element .dataset=${...} /> it is.

More Repositories

1

hyperHTML

A Fast & Light Virtual DOM Alternative
HTML
3,028
star
2

linkedom

A triple-linked lists based DOM implementation.
HTML
1,156
star
3

document-register-element

A stand-alone working lightweight version of the W3C Custom Elements specification
JavaScript
1,131
star
4

dom4

Modern DOM functionalities for every browser
JavaScript
929
star
5

flatted

A fast and minimal circular JSON parser.
JavaScript
893
star
6

url-search-params

Simple polyfill for URLSearchParams standard
JavaScript
765
star
7

lighterhtml

The hyperHTML strength & experience without its complexity 🎉
JavaScript
702
star
8

JSONH

Homogeneous Collection Compressor
JavaScript
618
star
9

circular-json

JSON does not handle circular references. Now it does
JavaScript
599
star
10

viperHTML

Isomorphic hyperHTML
JavaScript
318
star
11

heresy

React-like Custom Elements via V1 API builtin extends.
JavaScript
271
star
12

es6-collections

Map, WeakMap, and Set fast/simple shim for Harmony collections
JavaScript
253
star
13

neverland

React like Hooks for lighterhtml
JavaScript
241
star
14

wicked-elements

Components for the DOM as you've never seen before
JavaScript
235
star
15

eddy

Event Driven JS
JavaScript
211
star
16

dblite

sqlite for node.js without gyp problems
JavaScript
209
star
17

domdiff

Diffing the DOM without virtual DOM
JavaScript
197
star
18

hyperHTML-Element

An extensible class to define hyperHTML based Custom Elements.
JavaScript
195
star
19

benja

Bootable Electron Node JS Application
194
star
20

uce

µhtml based Custom Elements
JavaScript
183
star
21

usignal

A blend of @preact/signals-core and solid-js basic reactivity API
JavaScript
176
star
22

highlighted-code

A textarea builtin extend to automatically provide code highlights based on one of the languages available via highlight.js
HTML
172
star
23

restyle

JavaScript
167
star
24

testardo

a browser and OS agnostic web driver for mobile and desktop
JavaScript
166
star
25

sqlite-worker

A simple, and persistent, SQLite database for Web and Workers.
JavaScript
159
star
26

augmentor

Extensible, general purpose, React like hooks for the masses.
JavaScript
135
star
27

uhooks

micro hooks: a minimalistic client/server hooks' implementation
JavaScript
127
star
28

polpetta

Polpetta, any folder is served spiced
JavaScript
125
star
29

basicHTML

A NodeJS based, standard oriented, HTML implementation.
JavaScript
123
star
30

udomdiff

An essential diffing algorithm for µhtml.
JavaScript
117
star
31

pocket.io

A minimalistic version of socket.io that weights about 1K instead of 60K.
JavaScript
112
star
32

caller-of

The tiniest yet most powerful JS utility ever :D
HTML
102
star
33

uland

A µhtml take at neverland
JavaScript
101
star
34

uce-template

A Vue 3 inspired Custom Elements toolless alternative.
JavaScript
100
star
35

json.hpack

JSON Homogeneous Collections Packer
C#
95
star
36

html-escaper

A module to escape/unescape common problematic entities done the right way.
JavaScript
95
star
37

wru

essential unit test framework
JavaScript
95
star
38

udomsay

A stricter, signals driven, ESX based library
JavaScript
95
star
39

db

JavaScript
94
star
40

import.js

A dynamic import() polyfill
JavaScript
93
star
41

regular-elements

Custom Elements made available for any node, and through CSS selectors
JavaScript
91
star
42

proxy-pants

Secured and reliable Proxy based utilities for more or less common tasks.
JavaScript
90
star
43

ucompress

A micro, all-in-one, compressor for common Web files.
JavaScript
90
star
44

archibold.io

archibold.io
Shell
85
star
45

jsgtk

A simplified approach to GJS for Node.JS and JavaScript developers.
JavaScript
85
star
46

heresy-ssr

🔥 heresy 🔥 Server Side Rendering
JavaScript
84
star
47

hypersimple

The easiest way to use hyperHTML
JavaScript
83
star
48

asbundle

A minimalistic JS bundler
JavaScript
77
star
49

introspected

Introspection for serializable arrays and JSON friendly objects.
HTML
77
star
50

node-gtk

GNOME Gtk+ bindings for NodeJS
C++
74
star
51

i18n-utils

The i18n tag function utilitities
JavaScript
73
star
52

viper-news

viperHTML version of the Hacker News app.
JavaScript
72
star
53

ucdn

A µcompress based CDN utility, compatible with both Express and native http module
JavaScript
67
star
54

electroff

A cross browser, electron-less helper, for IoT projects and standalone applications.
JavaScript
64
star
55

builtin-elements

A zero friction custom elements like primitive.
JavaScript
62
star
56

nonchalance

The easiest way to augment DOM builtin elements.
JavaScript
61
star
57

universal-mixin

A mixin usable for both generic objects and decorators.
JavaScript
59
star
58

attachshadow

An iframe based Shadow DOM poorlyfill
JavaScript
59
star
59

ucontent

An SSR HTML content generator.
JavaScript
58
star
60

dom-augmentor

Same as augmentor but with DOM oriented useEffect handling via dropEffect.
JavaScript
56
star
61

ascjs

ES2015 to CommonJS import/export transformer
JavaScript
55
star
62

geo2city

Basic offline reverse geocode
JavaScript
53
star
63

vanilla-elements

A Minimalistic Custom Elements Helper.
JavaScript
51
star
64

wrist

Minimalistic utility for generic one/two ways data bindings.
HTML
51
star
65

screenfit

A cross platform, cross WebView, solution to fit 100% any Web page.
HTML
50
star
66

html-parsed-element

A base custom element class with a reliable `parsedCallback` method.
HTML
50
star
67

sqlite-tag

Template literal tag based sqlite3 queries.
JavaScript
49
star
68

jsdon

A DOM serializer based on LinkeDOM idea
HTML
48
star
69

echomd

A terminal oriented MD like syntax
JavaScript
48
star
70

dom-class

A lightweight, cross browser, simplification of WebComponents.
JavaScript
46
star
71

cloner

Cloning ES5+ objects in a shallow or deep way
JavaScript
46
star
72

css-proxied-vars

The easiest way to set, read, or update, CSS variables per each element.
JavaScript
46
star
73

event-target

The EventTarget Class Polyfill.
HTML
46
star
74

hn

Isomorphic Hacker News
JavaScript
45
star
75

proxied-worker

A tiny utility to asynchronously drive a namespace exposed through a Worker.
JavaScript
45
star
76

Database

Web SQL Storage Made Easy
JavaScript
44
star
77

promise

Abortable and Resolvable Promises.
JavaScript
43
star
78

poorlyfills

Simplified, partial, and poor ES6 collections polyfills, targeting IE9+ and older mobile browsers.
HTML
43
star
79

babel-plugin-transform-builtin-classes

A fix for the infamous Babel #4480 bug.
JavaScript
43
star
80

redefine

lightweight utility for smart object properties definition
JavaScript
42
star
81

bidi-sse

Bidirectional Server-sent Events
JavaScript
42
star
82

nativeHTML

coming soon
JavaScript
42
star
83

domtagger

The hyperHTML's template literal parser
JavaScript
41
star
84

hooked-elements

wickedElements 🧙 with render hooks
JavaScript
41
star
85

classtrophobic

Breaking JS Class Constrains
HTML
41
star
86

a-route

Express like routing as Custom Element or standalone
JavaScript
40
star
87

header-snippets

A collection of snippets to put in your header.
HTML
40
star
88

life-diary

your albums, your journey, your data
JavaScript
40
star
89

static.email

The easiest way to send emails on the Web
JavaScript
39
star
90

lazytag

Lazy loading Custom Elements and their styles without even thinking about it.
JavaScript
38
star
91

es-class

ECMAScript 3 to 6 compatible Class definition
JavaScript
38
star
92

tiny-cdn

A tiny static files serving handler
JavaScript
37
star
93

broadcast

Notification channel for the past, the present, and the future.
JavaScript
37
star
94

p-cool

Pretty Cool Elements
JavaScript
36
star
95

monthly

A simplified way to show a calendar month in any console.
HTML
36
star
96

jsx2tag

Enable JSX for Template Literal Tags based projects.
JavaScript
36
star
97

consolemd

Bringing echomd to console.
JavaScript
36
star
98

common-js

CommonJS + module.import() for any Browser
JavaScript
35
star
99

qsa-observer

handle elements lifecycle through CSS selectors
JavaScript
35
star
100

create-viperhtml-app

A basic viperHTML + hyperHTML setup
JavaScript
35
star