Neoclassical Philosophic Musings

    @shferreira/htm

    2.1.4 • Public • Published

    HTM (Hyperscript Tagged Markup) npm

    hyperscript tagged markup demo

    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}> (where Foo 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>

    ⚡️ See live version

    ⚡️ Try this on CodeSandbox

    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.

    Install

    npm i @shferreira/htm

    DownloadsWeekly Downloads

    2

    Version

    2.1.4

    License

    Apache-2.0

    Unpacked Size

    61.7 kB

    Total Files

    30

    Last publish

    Collaborators

    • avatar