📘

htmのバインディングを作った話

2022/12/17に公開

https://github.com/kstdx/tml

はじめに

こんにちはくつしたです。
今回リアクティブなhtmを使ったライブラリを作ってみたので記事にしました。

htmについて

htmはjsxの代替ライブラリで、JavaScriptのTagged Template Literalを利用したものです。

例えば

import htm from 'https://unpkg.com/htm?module'

const h = (tag, props, ...children) => { tag, props, children }
const html = htm.bind(h)

console.log(html`
<div class="foo" id="bar">
	<p>Hello, world!</p>
</div>
`)

みたいなふうにして実行すると

{
	"tag": "div",
	"props": {
		"class": "foo",
		"id": "bar"
	},
	"children": [
		{
			"tag": "p",
			"props": null,
			"children": ["Hello, world!"]
		}
	]
}

みたいに変換されるわけです。
じゃあ何がJSXよりいいんだって話なんですが、JSXと違いネイティブに実装されているTTLを使っているのでコンパイル不要でブラウザで使えるし、バインディングをするだけなら簡単ですし、なによりめちゃくら軽いってことです。(本体は600bytes以下らしいです)

なぜライブラリを自作したか

上にあるとおり、htmだけではオブジェクトにコンパイルするだけなのでHTMLに変換ができません。なのでHTMLに変換してくれるvhtmlというライブラリを使おうとやってみたのですが、これがHTML文字列に変換はしてくれますがbutton要素のonClickなどが一切動きませんでした。(そういうバインディングなので当たり前って言えば当たり前ですが)

他にも(P)Reactへのバインディングも公式で見つかりましたが、これはvhtmlとは違いコンポーネントを返すというよりはそのままDOMに出力するフレームワークだったので、自分のユースケースには当てはまらず、そもそもhtmについてのドキュメントもあまりなかったのでいっそのこと自分で作ってみることにしました。

どんなライブラリなのか

htmのオブジェクト(上記参照)をDOM(DocumentFlagment)に変換して返すライブラリです。こうすることで内部的にはElement.addEventListenerを使ってonClickなどのリアクティブな動きをつけることもできます。

また、ライブラリ自体のサイズも小さく(2022/12/17時点764bytes)、とても使いやすいと思います。

現在このライブラリにはstateなどは実装されておらず、後々Web Componentなどを使って実装するつもりです。

Example

import htm from 'https://unpkg.com/htm?module'
import { tag, compile } from '/src/mod.js'

const t = htm.bind(tag)

const items = ['Apple', 'Orange', 'Melon', 'Lemon']

const app = t`
    <>
        <button $click=${() => console.log(items)}>Click to log</button>
        <ul>
            ${items.map((item) => t`<li>${item}</li>`)}
        </ul>
        <div></div>
    </>
`

document.querySelector('#root').appendChild(compile(app))

こんな感じ。div#rootにマウントしています。
これに似たテストをリポジトリにおいてるのでぜひ試してみてください。

Discussion