htm is an implementation of JSX-like syntax in plain JavaScript, using Tagged Templates.
It lets your build apps using Preact/React/etc directly in the browser.
JSX can be converted to htm with only a few tiny modifications.
Templates are parsed by the browser's HTML parser and cached, achieving minimal overhead.
htm is just 650 bytes standalone, or only 500 bytes when used with Preact! (through the magic of gzip 🌈)
The syntax is inspired by lit-html, but includes features familiar to anyone who works with JSX:
- Rest spread:
<div ...${props}> - Self-closing tags: `
- Components:
<${Foo}>(whereFoois a component reference) - Boolean attributes:
<div draggable />
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:
- HTML's optional quotes:
<div class=foo> - HTML's self-closing tags:
<img src=${url}> - Optional end-tags:
<section><h1>this is the whole template! - Component end-tags:
<${Footer}>footer content<//> - Support for HTML comments:
<div><!-- don't delete this! --></div>
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.
htm is published to npm, and accessible via the unpkg.com CDN:
For npm:
npm i htmTo hotlink:
import { html, render } from 'https://unpkg.com/htm?module'Curious to see what it all looks like? Here's a working app! It's just an HTML file, there is 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?module';
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="MyApp: ${page}" />
<ul>
${todos.map(todo => html`
<li>{todo}</li>
`)}
</ul>
<button onClick=${this.addTodo.bind(this)}>Add Todo</button>
<${Footer}>footer content here<//>
</div>
`;
}
}
render(html`<${App} page="To-Do's" />`, document.body);
</script>
</html>How nifty is that?
