| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
parent directory.. | ||||
uhtml (micro µ html) is one of the smallest, fastest, memory consumption friendly, yet zero-tools based, library to safely help creating or manipulating DOM content.
It is entirely Web standards based and it adds just the minimal amount of spices to the templates literals it's able to understand and optimized for either repeated updates or one-off operations.
This page describes, without going into too many details, all the features delivered via this module which is roughly 2.5K once minified and compressed, or even bundled within your project.
The following code is an abstract representation of all features delivered by uhtml and it's explained in details preserving the same order.
You can skip to details directly via the following links:
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ render
┃ ┏━━━━━━━━━━━━━━━━━━━ tag
render(document.body, html`
<div class=${className} ?hidden=${!show}>
┃ ┗━━━━━━━━━━━━━ boolean
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ attribute
<ul @click=${sort} .sort=${order}>
┃ ┗━━━━━━━━━━━━━━━━━━ direct
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ listener
${[...listItems]}
┗━━━━━━┳━━━━━┛
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━ list
</ul>
<my-element /> ━━━━━━━━━━━━━━━━━━━━━━━━━ self closing
<p>
${show ? `${order} results` : null}
┗━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┛
┗━━━━━━━━━━━━━━━━━━━━━ hole
</p>
</div>
`);
To reveal template literal tags within a specific element we need a helper which goal is to understand if the content to render was already known but also, in case it's a hole, to orchestrate a "smart dance" to render such content.
The render exported helper is a function that, given a node where to render such content, returns that very same node with the content in the right place, content returned by the tag used to render.
import { render, html } from 'uhtml';
const whom = 'World';
// direct rendering
render(document.body, html`Hello ${whom}!`);
// using a function (implicitly invoked by render)
render(document.body, () => html`Hello ${whom}!`);
/** results into
<body>
Hello World!
<body>
*/A template literal tag can be either the html or the svg one, both directly exported from this module:
import { html, svg } from 'uhtml';
html`<button />`;
svg`<circle />`;Used directly from both default and uhtml/keyed variant, the returning value will not be a DOM Node, rather a Hole representation of that node once rendered, unless the import was from uhtml/node, which instead creates once DOM nodes hence it could be used with any library or framework able to handle these.
The uhtml/keyed export though also allows to create tags related to a specific key, where the key is a ref / key pair and it guarantees the resulting node will always be the same, given the same ref and the same id.
import { htmlFor, svgFor } from 'uhtml/keyed';
const Button = key => {
const html = htmlFor(Button, key);
return html`<button />`;
};
const Circle = key => {
const svg = svgFor(Circle, key);
return svg`<circle />`;
};
Button('unique-id') === Button('unique-id');
Circle('unique-id') === Circle('unique-id');In keyed cases, the result will always be the same node and not a Hole.
import { htmlFor } from 'uhtml/keyed';
const Button = key => {
const html = htmlFor(Button, key);
return html`<button />`;
};
document.body.append(Button(0));To some extend, uhtml is keyed by default, meaning that given the same template all elements in that template will always be created or referenced once in the stack.
In most common use cases then, using a keyed approach might just be overkill, unless you rely on the fact a node must be the same whenever its attributes or content changes, as opposite of being the previous node with updated values within it.
The use cases that best represent this need are:
There are really not many other edge cases to prefer keyed over non keyed, but whenever you feel like keyed would be better, uhtml/keyed will provide that extra feature, without compromising too much performance or bundle size (it's just ~0.1K increase and very little extra logic involved).
Fully inspired by lit, boolean attributes are simply a toggle indirection to either have, or not, such attribute.
import { render, html } from 'uhtml';
render(document.body, html`
<div ?hidden=${false}>I am visible</div>
<div ?hidden=${true}>I am invisible</div>
`);
/** results into
<body>
<div>I am visible</div>
<div hidden>I am invisible</div>
<body>
*/Every attribute that doesn't have a specialized syntax prefix, such as ?, @ or ., is handled in the following way and only if different from its previous value:
A direct attribute is simply passed along to the element, no matter its name or special standard behavior.
import { render, html } from 'uhtml';
const state = {
some: 'special state'
};
render(document.body, html`
<div id='direct' .state=${state}>content</div>
`);
document.querySelector('#direct').state === state;
// trueIf the name is already a special standard accessor, this will be set with the current value, whenever it's different from the previous one, so that direct syntax could be also used to set .hidden or .value, for input or textarea, but that's just explicit, as these accessors would work regardless that way, without needing special syntax hints and as already explained in the attribute section.
As already explained in the attribute section, common listeners can be already attached via onclick=${callback} and everything would work already as expected, with also less moving parts behind the scene ... but what if the listener is a custom event name or it requires options such as { once: true } ?
This is where @click=${[handler, { once: true }]} helps, so that addEventListener, and removeEventListener when the listener changes, are used instead of direct on*=${callback} assignment.
import { render, html } from 'uhtml';
const handler = {
handleEvent(event) {
console.log(event.type);
}
};
render(document.body, html`
<div @custom:type=${handler}, @click=${[handler, { once: true }]}>
content
</div>
`);
const div = document.querySelector('div');
div.dispatchEvent(new Event('custom:type'));
// logs "custom:type"
div.click();
// logs "click"
div.click();
// nothing, as it was oncePlease note that even if options such as { once: true } are used, if the handler / listener is different each time the listener itself will be added, as for logic sake that's indeed a different listener.
Most of the time, the template defines just static parts of the content and this is not likely to grow or shrink over time but, when that's the case or desired, it is possible to use an array to delimit an area that over time could grow or shrink.
<ul>, <ol>, <tr> and whatnot, are all valid use cases to use a list placeholder and not some unique node, together with <article> and literally any other use case that might render or not multiple nodes in the very same place after updates.
import { render, html } from 'uhtml';
render(document.querySelector('#todos'), html`
<ul>
${databaseResults.map(value => html`<li>${value}</li>`)}
</ul>
`);Please note that whenever a specific placeholder in the template might shrink in the future, it is always possible to still use an array to represent a single content:
html`
<div>
${items.length ? items : [
html`...loading content`
// still valid hole content
// or a direct DOM node to render
]}
</div>
`Please also note that an array is always expected to contain a hole or an actual DOM Node.
Fully inspired by XHTML first and JSX after, any element that self closes won't result into surprises so that custom-elements as well as any other standard node that doesn't have nodes in it works out of the box.
import { render, html } from 'uhtml';
render(document.body, html`
<my-element />
<my-other-element />
`);
/** results into
<body>
<my-element></my-element>
<my-other-element></my-other-element>
<body>
*/Please note this is an optional feature, not a mandatory one: you don't need to self-close standard void elements such as <br>, <link> or others, but you can self-close even these if consistency in templates is what you are after.
Technically speaking, in the template literal tags world all values part of the template are called interpolations.
const tag = (template, interpolations) => {
console.log(template.join());
// logs "this is , and this is ,"
console.log(interpolations);
// logs [1, 2]
};
tag`this is ${1} and this is ${2}`;Mostly because the name Interpolation is both verbose and boring plus it doesn't really describe the value kind within a DOM context, in uhtml the chosen name for "yet unknown content to be rendered" values is hole.
By current TypeScript definition, a hole can be either:
Beside the no tooling needed and standard based approach, so that you can trust this module will last for a very long time out there and very little changes will require your attention in the future, this is a honest list of things this library helps you with daily DOM based tasks:
The tag in template literals tags primitives makes a node unique. This means that anywhere in your code there is a tag with a literal attached, that resulting node will be known, pre-parsed, cache-able, hence unique, in the whole rendering stack.
// a tag receives a unique template + ...values
// were values are known as tag's interpolations
const tag = (template, ...values) => template;
// a literal string passed as tag is always unique and
// indeed in this case the two literals are different!
tag`a` === tag`a`; // this is false, despite the literal content
// in real-world code though, tags are used via callbacks
const a = () => tag`a`;
// so now this assertion would be true instead
a() === a(); // true!If you are following this logic so far, you might as well realize that anything returning a tag also works well:
// invokes the tag with always the same template
// despite one of its interpolations has a different value
[1, 2, 3].map(
index => tag`index ${index}`
);To dig a little further about tags and application usage, this example speaks thousand words!
import { render, html } from 'uhtml';
const App = (results) => {
return html`
<h1>With ${results.length} results:</h1>
<ul onclick=${({target}) => load(target.closest('li').id)}>
${results.map(item => html`
<li id=${item.id}>${item.description}</li>
`)}
</ul>
`;
};
const be = await fetch('./db.php?list=options');
render(document.body, App(await be.json()));This example asks for some result and produces the page content based on such results, replacing the whole body with the requested list of options for that space.
With this code the App returns a known template that can be reused with ease, among sub-templates for any <li> in the list that also benefits from this library performance and weak cache system.
With template literals tags what you see is not a string, rather a template with related values as interpolations that can be parsed and/or manipulated.
Interpolations are never "trashed" as part of the HTML or SVG template content neither, there is a standard TreeWalker that finds "holes" in the template and associates specialized operations per each hole kind: attribute, generic content or a list.
All operation that can also be inferred will be inferred only the first time the template is encountered and a map of updates per targeting node or attributes will be reused every other time.
let i = 0;
const callback = () => { console.log(i++); };
const content = '<unsafe>content</unsafe>';
const vanilla = target => {
target.innerHTML = `
<div onclick=${/* failing */ callback}>
${/* unsafe */ content}
</div>
`;
};
const uhtml = target => {
render(target, html`
<div onclick=${/* working */ callback}>
${/* safe */ content}
</div>
`);
};
// it fails expectations and intents
vanilla(document.body);
// it trashes the previous DOM every time
vanilla(document.body);
vanilla(document.body);
// VS
// it works as expected
uhtml(document.body);
// it doesn't change anything on the body
// and it never trashes the previous content
uhtml(document.body);
uhtml(document.body);There are various VSCode/ium solutions to template literals highlights and these are just a few examples:
Some of these might work with SVG content too but I don't feel like recommending any particular one over others: just try then and chose one 😉
All module variants export an attr Map that contains special attribute cases for aria, class, data, ref and style.
Due different nature of possible content, where SVG elements don't have className or other special accessors like HTML elements do, it is currently only possible to define custom attributes for HTML nodes.
import { render, html, attr } from 'uhtml';
if (!attr.has('custom')) {
attr.add('custom', (element, newValue, name, oldValue) => {
console.log(element); // the div
console.log(newValue); // 1
console.log(name); // "custom"
console.log(oldValue); // any previous value or undefined
// do something with the element and the "custom" attribute
element.setAttribute(name, newValue);
// return the value to retain for future updates
// in case the newValue is different from the oldValue
return newValue;
});
}
const update = i => {
render(document.body, html`
<div custom=${i} />
`);
};
update(1);
// this does nothing as 1 === 1
update(1);
// this passes 2 as newValue and 1 as oldValue
update(2);Absolutely! If a render is within an effect or a computed function and any of the signals changes after some event, everything just works as expected.
import { effect, signal } from 'https://unpkg.com/usignal';
import { render, html } from 'https://unpkg.com/uhtml';
const count = signal(0);
effect(() => {
render(document.body, html`
<button onclick=${() => { count.value++ }}>
${count.value}
</button>
`);
});This question came up more than once and it's about fetching some data from a server, where such data contains valid HTML content to show directly on the page.
As template literal tags are nothing more than functions, it is always possible to somehow bypass the need for a unique template and use an array instead.
import { render, html, svg } from 'uhtml';
const htmlUnsafe = str => html([str]);
const svgUnsafe = str => svg([str]);
render(document.body, htmlUnsafe('<h1>Hello HTML</h1>'));
/** results into
<body>
<h1>Hello HTML</h1>
<body>
*/| Back | FazBrowse Home | New Git URL |