npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

mi-html

v0.6.7

Published

html template literal for building reactive web components

Readme

npm-badge types-badge

mi-html

html template literal for building reactive web components

Uses uhtml@4 for html literal and DOM rendering.

Needs 3.8kB minified and gzipped with all bundled dependencies.

Usage

In your project:

npm i mi-html

Create a reactive web-component with mi-html and mi-element.

import { render, html } from 'mi-html'
import { MiElement, define } from 'mi-element'

define(
  'my-counter',
  class extends MiElement {
    static get attributes() {
      return { count: 0 }
      // this.count becomes a signal on instantiation
    }
    render() {
      const template = html`<button @click=${() => this.count++}>
        Clicked ${this.count} times
      </button>`
      render(this.renderRoot, template)
    }
  }
)

html`` In a nutshell

(From uhtml@4)

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:

let el = {}
   ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ render
   ┃                    ┏━━━━━━━━━━━━━━━━━━━ tag
render(document.body, html`
  <div class=${className} ?hidden=${!show}>
         ┃                    ┗━━━━━━━━━━━━━ boolean
         ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ attribute
    <ul @click=${sort} .sort=${order}>
           ┃             ┗━━━━━━━━━━━━━━━━━━ direct
           ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ listener
      ${[...listItems]}
        ┗━━━━━━┳━━━━━┛
               ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━ list
    </ul>
                 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━ ref
    <my-element ref=${el} /> ━━━━━━━━━━━━━━━ self closing
    <p>
      ${show ? `${order} results` : null}
       ┗━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┛
                      ┗━━━━━━━━━━━━━━━━━━━━━ hole
    </p>
  </div>
`);

render

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 'mi-html'

const whom = 'World'

render(document.body, () => html`Hello ${whom}!`)

/** results into
<body>
  Hello World!
<body>
 */

tag

A template literal tag can be either the html or the svg one, both directly exported from this module:

import { html, svg } from 'mi-html'

html`<button />`
svg`<circle />`

boolean

Fully inspired by lit, boolean attributes are simply a toggle indirection to either have, or not, such attribute.

import { render, html } from 'mi-html'

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>
 */

attribute

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:

  • if the exported attr Map knows the attribute, a callback related to it will be used to update
    • aria attribute accepts and handle an object literal with role and other aria attributes
    • class attribute handles a direct element.className assignment or remove the attribute if the value is either null or undefined
    • data attribute accepts and handle an object literal with dataset names to directly set to the node
    • ref attribute handles React like ref property by updating the ref.current value to the current node, or invoking ref(element) when it's a callback
    • style attribute handles a direct element.style.cssText assignment or remove the attribute if the value is either null or undefined
    • it is possible to augment the attr Map with any custom attribute name that doesn't have an already known prefix and it's not part of the already known list (although one could override known attributes too). In this case, attr.set("my-attr", (element, newValue, name, oldValue) => newValue) is the expected signature to augment attributes in the wild, as the stack retains only the current value and it will invoke the callback only if the new value is different.
  • if the attribute is unknown in the attr map, a name in element check is performed once (per template, not per element) and if that's true, a direct assignment will be used to update the value, unless the value is either null or undefined, in which case the attribute is removed if it's not a listener, otherwise it drops the listener:
    • "onclick" in element, like any other native listener, will directly assign the callback via element[name] = value, when value is different, providing a way to simplify events handling in the wild
    • "value" in input, like any other understood accessor for the currently related node, will directly use input[name] = value, when value is different
    • "hidden" in element, as defined by standard, will also directly set element[name] = value, when value is different, somehow overlapping with the boolean feature
    • any other "accessor" in element will simply follow the exact same rule and use the direct element[name] = value, when value is different
  • in all other cases the attribute is set via element.setAttribute(name, value) and removed via element.removeAttribute(name) when value is either null or undefined

direct

A direct attribute is simply passed along to the element, no matter its name or special standard behavior.

import { render, html } from 'mi-html'

const state = {
  some: 'special state'
}

render(
  document.body,
  () => html`<div id="direct" .state=${state}>content</div>`
)

document.querySelector('#direct').state === state
// true

If 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.

listener

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 'mi-html'

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 once

Please 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.

list

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 'mi-html'

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.

ref

Keep references to DOM nodes

let ref = {}
render(document.body, () => html`<div ref=${ref}>Hey</div>`)
// access with `.current`
ref.current.textContent = 'Hi'

self closing

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 'mi-html'

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.

hole

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:

  • a string, a boolean or a number to show as it is on the rendered node
  • null or undefined to signal that hole has currently no content whatsoever
  • an actual instanceof Hole exported class, which is what html or svg tags return once invoked
  • an array that contains a list of instances of Hole or DOM nodes to deal with

reactivity

Signals are a primitive used to automatically react to changes, as opposite of remembering to deal manually with re-renders invokes which is all good but not ideal in terms of DX.

import { effect, createSignal, render, html } from 'mi-html'

const count = createSignal(0)

// render in the body passing a () => html`...` callback
render(
  document.body,
  () => html`
    <button
      onclick=${() => {
        count.value++
      }}
    >
      Clicks: ${count.value}
    </button>
  `
)

constraints

If signals are meant to be used within a template then the render function needs to have a lazy invoke of its content because otherwise signals don't get a chance to subscribe to it.

// ⚠️ DOES NOT CREATE AN EFFECT
render(target, html`${signal.value}`)

// ✔ CREATE AN EFFECT 👍
render(target, () => html`${signal.value}`)

Please note that components that are meant to be rendered within other components, and not stand-alone, passing a non callback as second argument might be even desired so that only the outer top-most render would react to changes.

License

MIT licensed