@shferreira/htm
v2.1.4
Published
The Tagged Template syntax for Virtual DOM. Only browser-compatible syntax. - Including Preact X!
Downloads
4
Maintainers
Readme
htm
is JSX-like syntax in plain JavaScript - no transpiler necessary.
Develop with React/Preact directly in the browser, then compile htm
away for production.
It uses standard JavaScript Tagged Templates and works in all modern browsers.
htm
by the numbers:
🐣 < 600 bytes when used directly in the browser
⚛️ < 500 bytes when used with Preact (thanks gzip 🌈)
🥚 < 400 byte htm/mini
version
🏅 0 bytes if compiled using babel-plugin-htm
Syntax: like JSX but also lit
The syntax you write when using HTM is as close as possible to JSX:
- Spread props:
<div ...${props}>
- Self-closing tags:
<div />
- Components:
<${Foo}>
(whereFoo
is a component reference) - Boolean attributes:
<div draggable />
Improvements over JSX
htm
actually takes the JSX-style syntax a couple steps further!
Here's some ergonomic features you get for free that aren't present in JSX:
- No transpiler necessary
- HTML's optional quotes:
<div class=foo>
- Component end-tags:
<${Footer}>footer content<//>
- Syntax highlighting and language support via the lit-html VSCode extension and vim-jsx-pretty plugin.
- Multiple root element (fragments):
<div /><div />
Installation
htm
is published to npm, and accessible via the unpkg.com CDN:
via npm:
npm i htm
hotlinking from unpkg: (no build tool needed!)
import htm from 'https://unpkg.com/htm?module'
const html = htm.bind(React.createElement);
// just want htm + preact in a single file? there's a highly-optimized version of that:
import { html, render } from 'https://unpkg.com/htm/preact/standalone.mjs'
Usage
Since htm
is a generic library, we need to tell it what to "compile" our templates to.
The target should be a function of the form h(type, props, ...children)
(hyperscript), and can return anything.
// this is our hyperscript function. for now, it just returns a description object.
function h(type, props, ...children) {
return { type, props, children };
}
To use that h()
function, we need to create our own html
tag function by binding htm
to our h()
function:
import htm from 'htm';
const html = htm.bind(h);
Now we have an html()
template tag that can be used to produce objects in the format we created above.
Here's the whole thing for clarity:
import htm from 'htm';
function h(type, props, ...children) {
return { type, props, children };
}
const html = htm.bind(h);
console.log( html`<h1 id=hello>Hello world!</h1>` );
// {
// type: 'h1',
// props: { id: 'hello' },
// children: ['Hello world!']
// }
If the template has multiple element at the root level
the output is an array of h
results:
console.log(html`
<h1 id=hello>Hello</h1>
<div class=world>World!</div>
`);
// [
// {
// type: 'h1',
// props: { id: 'hello' },
// children: ['Hello']
// },
// {
// type: 'div',
// props: { class: 'world' },
// children: ['world!']
// }
// ]
Example
Curious to see what it all looks like? Here's a working app!
It's a single HTML file, and there's no build or tooling. You can edit it with nano.
<!DOCTYPE html>
<html lang="en">
<title>htm Demo</title>
<script type="module">
import { html, Component, render } from 'https://unpkg.com/htm/preact/standalone.mjs';
class App extends Component {
addTodo() {
const { todos = [] } = this.state;
this.setState({ todos: todos.concat(`Item ${todos.length}`) });
}
render({ page }, { todos = [] }) {
return html`
<div class="app">
<${Header} name="ToDo's (${page})" />
<ul>
${todos.map(todo => html`
<li>${todo}</li>
`)}
</ul>
<button onClick=${() => this.addTodo()}>Add Todo</button>
<${Footer}>footer content here<//>
</div>
`;
}
}
const Header = ({ name }) => html`<h1>${name} List</h1>`
const Footer = props => html`<footer ...${props} />`
render(html`<${App} page="All" />`, document.body);
</script>
</html>
How nifty is that?
Notice there's only one import - here we're using the prebuilt Preact integration since it's easier to import and a bit smaller.
The same example works fine without the prebuilt version, just using two imports:
import { h, Component, render } from 'preact';
import htm from 'htm';
const html = htm.bind(h);
render(html`<${App} page="All" />`, document.body);
Other Uses
Since htm
is designed to meet the same need as JSX, you can use it anywhere you'd use JSX.
Generate HTML using vhtml:
import htm from 'htm';
import vhtml from 'vhtml';
const html = htm.bind(vhtml);
console.log( html`<h1 id=hello>Hello world!</h1>` );
// '<h1 id="hello">Hello world!</h1>'
Webpack configuration via jsxobj: (details here) (never do this)
import htm from 'htm';
import jsxobj from 'jsxobj';
const html = htm.bind(jsxobj);
console.log(html`
<webpack watch mode=production>
<entry path="src/index.js" />
</webpack>
`);
// {
// watch: true,
// mode: 'production',
// entry: {
// path: 'src/index.js'
// }
// }
Project Status
The original goal for htm
was to create a wrapper around Preact that felt natural for use untranspiled in the browser. I wanted to use Virtual DOM, but I wanted to eschew build tooling and use ES Modules directly.
This meant giving up JSX, and the closest alternative was Tagged Templates. So, I wrote this library to patch up the differences between the two as much as possible. As it turns out, the technique is framework-agnostic, so it should work great with most Virtual DOM libraries.
As of 2.1.0, htm
is stable, well-tested and ready for production use.