◆ 書き方が好みの方でいいと思う

lit-html も 1.0 がリリースされたことですし lit-html と hyperhtml を比較してみます

作るもの

単純なページということで次の機能があります

  • input が並ぶ
  • add first / add last ボタンで最初と最後に input を追加できる
  • input はテキストボックスと削除ボタンのセット
  • テキストボックスに入力するとリアルタイムで下の pre タグのプレビューに反映される
  • 削除ボタンを押すとその input のセットが消える

lit-html

<!doctype html>
<script type="module">
import { html, render } from "https://unpkg.com/lit-html?module"

const data = {
items: [{ text: "" }]
}

const tpl = values => html`
<div>
<button @click=${h.addFirst}>Add first</button>
<button @click=${h.addLast}>Add last</button>
</div>
<div>${values.items.map(itemtpl)}</div>
<pre>${values.items.map(e => e.text).join("\n")}</pre>
`

const itemtpl = (item, index) => html`
<div>
<input @input=${h.oninput} data-index=${index} .value=${item.text}>
<button @click=${h.ondelete}>Del</button>
</div>
`

const withUpdate = fn => event => {
fn(event)
update()
}

const h = {
addFirst: withUpdate(() => data.items.unshift({ text: "" })),
addLast: withUpdate(() => data.items.push({ text: "" })),
oninput: withUpdate(event => data.items[event.target.dataset.index].text = event.target.value),
ondelete: withUpdate(event => data.items.splice(event.target.dataset.index, 1)),
}

const update = () => render(tpl(data), document.body)
update()
</script>

hyperhtml

<!doctype html>
<script type="module">
import { bind, wire } from "https://cdn.jsdelivr.net/npm/hyperhtml@2.25.5/esm.js"

const data = {
items: [{ text: "" }]
}

const render = (values, elem) => bind(elem)`
<div>
<button onclick=${h.addFirst}>Add first</button>
<button onclick=${h.addLast}>Add last</button>
</div>
<div>${values.items.map(itemtpl)}</div>
<pre>${values.items.map(e => e.text).join("\n")}</pre>
`

const itemtpl = (item, index) => wire(item)`
<div>
<input oninput=${h.oninput} data-index=${index} value=${item.text}>
<button onclick=${h.ondelete}>Del</button>
</div>
`

const withUpdate = fn => event => {
fn(event)
update()
}

const h = {
addFirst: withUpdate(() => data.items.unshift({ text: "" })),
addLast: withUpdate(() => data.items.push({ text: "" })),
oninput: withUpdate(event => data.items[event.target.dataset.index].text = event.target.value),
ondelete: withUpdate(event => data.items.splice(event.target.dataset.index, 1)),
}

const update = () => render(data, document.body)
update()
</script>

違い

見ての通り 全体的にはほぼ同じような作りで作れます
画面表示 (DOM) 操作のライブラリなので データ構造やイベントハンドラでのデータの更新処理は同じものなので data, withUpdate, h については完全に同じです
今回の機能では違いは主に 2 つです

属性名

hyperhtml は HTML の記述方法に合わせた感じで on*** でリスナ登録ができ プロパティも普通に名前を書くだけです
プロパティがないなら属性になります

lit-html では HTML と少し離れますが わかりやすくプレフィックスをつけます
リスナでは @ から始め プロパティでは . から始めます
なにもないと属性で ? から始まると値を boolean 型とみなして属性の有無を決定します

? はプロパティに true/false を設定すればよさそうなので 一見いらなさそうですが 自作要素だと属性とプロパティが対応してるとも限りませんし lit-html のほうがライブラリの都合に合わせなくとも良い部分では優れてると言えます
その他は on と @ など個人の好みになる部分が多そうです
lit-html でこの方針に変わる頃に @ などはデフォルトで変更できるようにするみたいな話があったので変えたいなら変えれるのかもしれません

render

lit-html では html タグでテンプレートを作り それを render 関数に入れて指定の HTML 要素 に render します
ルート部分もそのパーツも問わず html タグでテンプレートを作ります

hyperhtml では bind 関数に HTML 要素を渡して返されるタグを使います
テンプレートが返ってくるわけではなくタグをテンプレートストリングで呼び出したタイミングで更新されます
テンプレート各部分が render 関数になります
また HTML 要素に bind しないパーツは wire 関数に参照を渡して返されるタグを使います

lit-html のほうが素直な感じで初めて使ったときのわかりやすさは上です
なれてしまうとどっちでも良い感がでてくるのでやっぱり好み次第だと思います