µhtml v2.5: a 3 LOC change revolution

Andrea Giammarchi
2 min readFeb 17, 2021
Photo by ALAN DE LA CRUZ on Unsplash

Heavily inspired by hyperHTML intents ability, exploring a way to easily migrate from hyperHTML to uhtml, this 3 LOC commit enabled exponential features within the lightest, fastest, template literal engine, of its kind, out there.

The TL;DR version of this post, is that µhtml now accepts interpolations callbacks as elements content, passing along the comment node used to hold whatever value such callback will return: strings, numbers, arrays, … whatever!

A wild uhtml-intents helper appears

Still based on few, size-irrelevant, extra lines of code, the uhtml-intents module does nothing more than register an intent, and be sure this gets executed while rendering.

import {define, intent} from 'uhtml-intents';define('case', value => { ... });render(where, html`
<what>${intent({case: value})}</what>
`);

Above pseudo-code kinda explains it all:

  • you define an intent by name, and provide the callback that should be executed whenever such name is a key of the object passed along
  • the callback gets executed with the value related to the intent, and it can return anything compatible with uhtml engine
  • the last argument of said callback, will always be the comment node used as placeholder in the living DOM tree, so that literally anything can be done at this point, including setting attributes to its parentNode

A few examples

The i18n live example, is a rip off the readme:

import {define, intent} from 'uhtml-intents';const i18n = {
greetings: {
it: 'Ciao!',
en: 'Hello!'
}
};
// user preference
let lang = 'it';
// intent definition
define('i18n', key => {
return i18n[key][lang];
});
// intent in action!
render(document.body, html`
<div>
${intent({i18n: 'greetings'})}
</div>
`);

… but the truth is that .. much more could be done!

The unsafe intent should speak for itself:

define('unsafe', HTML => html([HTML]));render(element, html`
<div>${intent({unsafe: SSR_HTML})}</div>
`);

But also an attributes modifier should help understanding:

define('attributes', (values, {parentNode}) => {
for (const key of Object.keys(values))
parentNode.setAttribute(key, values[key]);
});
render(element, html`
<div>
${intent({attributes: {
'data-test': testValue,
thing: thingValue
}})}
</div>
`);

And more …

If you understood the power of intents in declarative template literals based libraries, you might be pleased to know that uhtml-intents actually works, out of the box, with both lighterhtml and hyperHTML too, because uhtml finally got aligned with the callback within elements interpolations, hence we could share intents across libraries 🥳

--

--

Andrea Giammarchi

Web, Mobile, IoT, and all JS things since 00's. Formerly JS engineer at @nokia, @facebook, @twitter.