htmのバインディングを作った話
はじめに
こんにちはくつしたです。
今回リアクティブな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