marlun / uhtml

A micro HTML/SVG render

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

µhtml

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.

How To Use µhtml

Install the module via npm i uhtml and consume it as such:

// as ECMAScript standard module
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 Summary & Compatibility

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

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 returning value of html and svg tags. The render function returns the where DOM node itself.
  • a html template literal tag, to produce any sort of HTML content
  • a svg template literal tag, to produce any sort of SVG content
  • both html and svg implements a .for(reference[, id]) template tag function for keyed weak relationships within the node
  • both html and svg implements a .node template tag function for one-off HTML or SVG creation

API Documentation

Following a detailed information about µhtml usage and its constrains.

About Attributes

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

render(document.body, html`
  <div id="main"
        class=${`content ${extra}`}
        data-fancy=${fancy}>
    <p contenteditable=${editable}
        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 ways. 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 breaks with an error bad template. Use template literals within interpolations, if you want to obtain 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 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, as example, onclick=${...}, it will be set as listener. If the listener changes, the previous one will be automatically removed
  • 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 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
  • 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
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 two exceptional nodes that do not allow sparse content within themselves: the style element, and the textarea one.

// 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 be inevitably just text, like it is for style or textarea, as example, every other interpolation can contain primitives, as strings, numbers, or even 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, which returning 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 exact 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 Custom Elements

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


µ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 pair 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.

Following a list of other points to consider when choosing µhtml instead of lighterhtml (or vice-versa).

Differently 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
Similarly 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
  • the ref=${...} attribute works same as lighterhtml, enabling hooks, or React style, out of the box
  • 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 of lighterhtml production size, mostly because ...
  • there are no 3rd parts dependencies, except for @ungap/create-content, needed 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 ~0.2K though.
µhtml library goals
  • be an essential/ideal companion for wickedElements, hookedElements, or any third part 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

About

A micro HTML/SVG render

License:ISC License


Languages

Language:JavaScript 94.6%Language:HTML 5.4%