Choo.js: Fun functional programming


世の中では、VueやReactなどのイケイケドンドンFFWが大人気だが、それ以外にも様々なフロントエンド・フレームワークがひっそりと息をしている。ということで、今回はChoo.jsという4kbの小さなフレームワークの紹介をしようと思う。

Choo.jsとは

GithubのREADMEにA 4kb framework for creating sturdy frontend applicationsと書いてあるように、Choo.jsは小さく始めながらも堅牢なフロントエンド・アプリケーションを作れるような機能を提供するフレームワークだ。

Choo.js自体はたった200行程度のソースコードから成り立っていて、アプリケーションが提供しているルーティングの機能や、EventEmitterなどは、nanorouternanobusなどのいくつかの異なる小さなモジュールの組み合わせから成り立っている。Choo.jsのエコシステムの一部として、これらのモジュールはFramework-agnostic1であるということを主眼に置いており、できるだけ粗結合で依存の少ないコードベースであるということを謳っている。

使ってみる

チュートリアルということでWebpackBinをここでは使う。
以下のURLをブラウザから開けば実際に動かしたりソースコードをいじったりできる。
https://www.webpackbin.com/bins/-L-V4Vk0BYQ12x5CR7Fs

ちなみにこんなアプリケーションを作ります

HTML

HTMLは以下の数十行だけでよい。メインの実装はJS側で行う。

index.html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8"/>
  </head>
  <body>
    <div id="app"></div>
    <script src="main.js"></script>
  </body>
</html>

JS

JS側で実際にChooを使ったアプリケーションの構築を行っていく

Choo.jsにはビューストアという2つの概念しか無いと行っても、ほぼほぼ差し支えない。

ビュー関数はES6のタグテンプレート・リテラルを用いてDOMを構造を返すだけの純粋な関数だ。その定義の中でのボタンのクリックやなんらかの変更を、引数として受け取っているemit関数に渡して呼び出すことで、ストアが変更をキャッチできるようになる。

ビュー関数
//
// ◎ビュー定義関数
// state(ステート)を受け取り、DOMを返す関数としてビューを定義
//
function mainView (state, emit) {

  // ES6のタグテンプレート・リテラルでDOM構造を定義できる
  return html`
    <div>
      <h1>Title: ${state.title}</h1>
      <input type="text" value="${state.title}" oninput=${update} />
    </div>
  `

  // 引数から受け取っているemit関数を呼び出すことでストアの更新をトリガできる
  function update(e) {
    emit('update', e.target.value)
  }
}

ストアはEventEmitterのインスタンスとしてemitterを受け取るので、それに対してリスナを登録してやるだけでよい。renderをemitすることでビューにステートを反映できる。

また、自前でイベントを登録する以外にも、DOMがマウントされたときに呼び出される'DOMContentLoaded'などのライフサイクル・ハンドラも用意されている2

ストア関数
//
// @ストア定義関数
// 引数で受け取るstateとemitterに対してストアとイベントハンドラを定義する
//
function titleStore (state, emitter) {

  // stateに属性を生やす形でデフォルト・ステートを定義
  state.title = 'Not quite set yet'

  // ビュー関数からemitでトリガされるイベントハンドラを定義
  emitter.on('update', function (title) {
    state.title = title

    // ビューの更新を実行
    emitter.emit('render')
  })
}

これらの一連の流れをひとつにまとめると、このような感じになる。

main.js
const html = require("choo/html");
const choo = require("choo");
const app = choo();

function mainView (state, emit) {
  return html`
    <div>
      <h1>Title: ${state.title}</h1>
      <input type="text" value="${state.title}" oninput=${update} />
    </div>
  `

  function update (e) {
    emit('update', e.target.value)
  }
}

function titleStore (state, emitter) {
  state.title = 'Not quite set yet'

  emitter.on('update', function (newTitle) {
    state.title = newTitle
    emitter.emit('render')
  })
}

app.use(titleStore)
app.route('/', mainView)
app.mount('#app')

余談: ミドルウェア

上のコードではストアをapp.use()を使って登録しているが、厳密にはchoo.jsのストアはミドルウェアのひとつとして作られているに過ぎない。Choo.jsでは、stateemitterを受け取る関数であれば、なんでもuseメソッドを使って登録することができる。

ミドルウェア例
function YourMiddleware() {
  return function (state, emitter) {
    // do your work here
  }
}

app.use(YourMiddleware());

公式で容易されているchoo-devtoolsなどのモジュールも同じような形で利用できるようになっている

choo-devtool
const choo = require('choo');
const devtools = require('choo-devtools');

const app = choo()
if (process.env.NODE_ENV !== 'production') {
  app.use(devtools())
}
app.mount('body')

choo-cliを使う

そもそもフロントエンドは開発環境を構築するのがダルい。Webpackの設定ファイルなど書きたくない。そんな人のためにchoo-cliというスキャフォルディングツールが用意されているので、それを使って新しくプロジェクトを生成することもできる。

choo-cliでアプリケーションを作成
$ choo new example-choo-app
$ cd example-choo-app

choo-cliで生成されるアプリケーションにはデフォルトでbankai3という、フロントエンドアプリケーションのバンドリングから開発用サーバの起動までをワンストップでやってくれる便利なツールが追加され、以下のようなスクリプトが使えるようになる

開発用サーバを起動
$ npm start
アプリケーションをビルド
$ npm run build

まとめ

個人的によさそうだなと思ったところは以下の3つ

コードベースが小さく、困ってもソースを読めばなんとかなる

  • フレームワークの実装があまりにも巨大すぎると、issueやStackoverflowにないような困りごとを自分でソースコードを読んで解決するのはなかなか難しかったり、解決できてもPRの粒度を小さく保つのが難しくなったりする。加えて、OSSなソースコードリーディングにも最適。

ビューが基本的に関数というアプローチ

  • Choo.jsにおいて、ビューはクラスのインスタンスでもなんでもなく、単なるDOM構造を返すだけの関数でしか無い。つまり常にステートレスで、責務が明確に分離されている。純粋な関数として実装されるのでテストも容易な上、DIされるのはEventEmitterだけなのでモックも簡単。

最低限のEventEmitterだけが用意されている

  • Choo.jsにはビューとストアという2つのレイヤしか用意されていない。そして、ストアは単なるEventEmitterでしかない。ここに自前でFluxアーキテクチャを実装してもいいし、これをこのままつかってもよい。データ・フローの実装への自由度が高い。

ぜひ気になったら触ってみてください


  1. 特定のフレームワークに依存しないこと 

  2. https://github.com/choojs/choo#events 

  3. https://github.com/choojs/bankai