🎼

ビルド不要で簡単にWeb Componentsできる軽量フレームワークTonic

2022/05/20に公開

Tonicとは

TonicはHTMLで使えるコンポーネントを作るための軽量フレームワークです。

https://tonicframework.dev/

上記サイトでは特徴として以下のようなものがあげられています。

  • 1ファイル、1クラスのみ(HTMLエスケープ用文字列クラスもおまけっぽくついてます)。本体は350行程度。
  • ビルドツール不要
  • ネイティブWeb Componentsを使用
  • JAMStackにも最適
  • クライアントでもサーバでも同様に動作
  • コンポジション指向(?)
  • Event delegation(イベント移譲)を標準でサポート
  • サンプルも豊富

何をやっているかはソースを読んでいただいた方が早いかもと思いますが、本当にHTMLEelementを継承したTonicクラスを作ってるだけ、と言っても過言ではないでしょう。

ちなみに「サンプル豊富」というのは、Tonicで作った公式コンポーネント集のことだと思われます。

https://github.com/socketsupply/components

間違ってもSPAとかを作るためのツールではないと思いますが(routerとかstate managementとかはないので。custom element内での状態管理くらいは普通にできます)、とりあえずHTMLタグとちょっとしたCSS+JSをまとめて使い回すためにコンポーネントぽくしたい、それだけのためにビルドパイプラインとか用意したり専用の環境を使ったりするのはだるい、その辺のHTTPしゃべる奴と秒で動かせるくらいのやつが欲しい、というくらいのカジュアルな用途には重宝しそうです。

簡単な例

とりあえず雑に動かすには、CDNを使うとHTMLファイル1つから試せます。

sample1.html
<html>
  <head>
    <title>Tonic Sample 1</title>
    <script type="module">
      import Tonic from "https://cdn.skypack.dev/@optoolco/tonic"

      class HelloWorld extends Tonic {
        render() {
          return this.html`<div>Hello, ${this.props.name ?? 'World'}!</div>`
        }
      }
      Tonic.add(HelloWorld);
    </script>
  </head>
  <body>
    <div>
      <hello-world></hello-world>
      <hello-world name="Jane"></hello-world>
    </div>
  </body>
</html>

これをブラウザで表示させると、以下のように出力されます。

Hello, World!
Hello, Jane!

簡単ですね。
ちなみに簡単な場合は関数でも定義できます。

sample2.html
<html>
  <head>
    <title>Tonic Sample 2</title>
    <script type="module">
      import Tonic from "https://cdn.skypack.dev/@optoolco/tonic"

      function HelloWorld() {
        return this.html`<div>Hello, ${this.props.name ?? 'World'}!</div>`
      }
      Tonic.add(HelloWorld);
    </script>
  </head>
  <body>
    <div>
      <hello-world></hello-world>
      <hello-world name="Jane"></hello-world>
    </div>
  </body>
</html>

もちろんJSファイルを分割しても動きます。

sample3.js
import Tonic from "https://cdn.skypack.dev/@optoolco/tonic"

class HelloWorld extends Tonic {
  render() {
    return this.html`<div>Hello, ${this.props.name ?? 'World'}!</div>`
  }
}
Tonic.add(HelloWorld);
sample3.html
<html>
  <title>Tonic Sample 3</title>
  <body>
    <div>
      <hello-world></hello-world>
      <hello-world name="Jane"></hello-world>
    </div>
    <script type="module" src="./sample3.js"></script>
  </body>
</html>

カウンターを作る

もうちょっと複雑な例として、カウンターを書いてみます。

counter.html
<html>
  <head>
    <title>Tonic Sample Counter</title>
    <script type="module">
      import Tonic from "https://cdn.skypack.dev/@optoolco/tonic"

      class SimpleCounter extends Tonic {
        constructor () {
          super()
          this.state = {counter: 0}
        }

        click(e) {
          const el = Tonic.match(e.target, '[data-event]')
          if (!el) {
            return
          } else if (el.dataset.event === '+') {
            this.state.counter++;
            this.reRender();
          } else if (el.dataset.event === '-') {
            this.state.counter--;
            this.reRender();
          }
        }

        render() {
          const n = this.state.counter.toString()
          return this.html`<div>
                             <button data-event="-" id="minus">-1</button>
                             <span>${n}</span>
                             <button data-event="+" id="plus">+1</button>
                           </div>`
        }
      }
      Tonic.add(SimpleCounter);
    </script>
  </head>
  <body>
    <div>
      <simple-counter id="c"></simple-counter>
    </div>
  </body>
</html>

constructor()はWeb Components一般のお約束ですし、render()は先ほども出てきたので、click()以外は特に説明するまでもないかと思います。
このカウンタのボタンは「+1」と「-1」の2つありますが、そのハンドラであるclick()は一つだけです。これはEvent delegationを使って子のspan要素のイベントを親のsimple-counter要素に移譲させて扱っているためです。
親はどの子(子孫)から来たのかわからないので、Tonic.match()とdata属性を使って判別しています。なお、このためにbutton要素にはid属性を付与する必要があります。

さらに実用的なサンプルとしては公式コンポーネント集を参考にすると良さそうです。

まとめ

このライブラリを作っているSocket Supply Co.は基本的には昔ながらの(というと語弊があるかもしれませんが)「P2PでWebを分散させよう」みたいな、いまどきのWeb3には1ミリも共感できない人でも納得の分散システムを作ろうとしている会社のようです。

その意味ではこのTonicは余芸のようなものかもしれませんが、一応会社のブログでも紹介されています。この中の一文も面白いです。

  • 思想的指導者なし
  • コアチームなし
  • Patreonなし
  • サブスクリプションモデルなし
  • 広告なし
  • エンタープライズエディションなし
  • マルチトラックのカンファレンスなし
  • カルトなし
  • 単なる仕事を片付けるためのツール

なかなかの書きっぷりにほっこりしますね。

そしてproduction-readyなくらいに完成度も十分で、設計も片付いており、今後大々的に変更をアナウンスすることもきっとない、というやる気のなさを見せつつ、だからこそ長期的にも安心して使えるとアピールしています。

いろんな意味で独特のツールで面白いですね。

Discussion