blog

[mithriljs]
mithriljs + rails で mithriljs を体感してみる

はじめに

この記事は、foodison advent calendar 2015 の 15日目の記事です。

どうも。師走の速さと忙しさに驚きを隠し切れない vimtaku です。
今日は、最近触ってて楽しい mithril.js について書いていきます。

mithril.js とは?

Mithril is a client-side MVC framework - a tool to organize code in a way that is easy to think about and to maintain.

mithriljs はクライアントサイド MVC フレームワーク、とのこと。

mithril.js の学習曲線について

mithril.js は、少ない学習コストで使うことができると言われています。
実際しばらく使ってみた結果、かなり少ない学習コストで、かなり楽しく早く書けると思います。
vue.js も学習コストが少ないと言われてますが、 mithril.js はもっとラクです(私感です)。

やってみよう

題材

本の一覧みたいなのがサーバにあって、それをクライアントが取得し、ブラウザの中でインクリメンタルサーチするような
ものを考えてみます。
mithriljs の素早く書ける感じとか、軽い感じとかが少しでも伝わればいいかなといった次第です。

まずは rails new します

# 作成
rails new mithriljs-rails-sample -d mysql
vim Schemafile
create_table :books, force: true do |t|
  t.string :name, null: false, default: ''
  t.timestamps null: false
end
vim Gemfile
# mysql2 の rails バージョンによって必要みたいです
gem 'mysql2', '~> 0.3.20'

# 大好きな ridgepole
gem 'ridgepole'

# 今回はとりあえず Bowerfile に書いて手頃に使える bower-rails を使用します。
gem 'bower-rails'
bundle install

ridgepole を bin に入れておく

bundle binstubs ridgepole

DB系。Schemafile を反映

be rake db:create
bin/ridgepole -c config/database.yml --apply -f Schemafile

モデルとかコントローラとかを用意

bundle exec rails generate scaffold --no-migration books name:string

JS系用意

be rails g bower_rails:initialize json
be rake bower:install

vendor/assets/bower_components/ 以下に出来てます。

ll vendor/assets/bower_components/
total 0
drwxr-xr-x  16 vimtaku  staff  544 12 15 19:23 bootstrap
drwxr-xr-x   7 vimtaku  staff  238 12 15 19:23 jquery
drwxr-xr-x  26 vimtaku  staff  884 12 15 19:23 mithriljs

application.js にファイルを読み込むためのコードを書きます。

//= require mithriljs/mithril.min.js

サーバ起動。

bundle exec rails server --port=3004 --bind=0.0.0.0

book 一覧をサーバから取得する処理を書いてみる

app/assets/javascript/books.coffee

$(->
  Book = (data)->
    console.log(data)
    @name = m.prop(data.name || '')
    return this

  BookList = {
    controller: ->
      vm = BookList.vm
      vm.entries = m.prop([])
      m.request({
        method: "GET",
        url: "/books.json",
        type: Book
      }).then((got) =>
        vm.entries(got)
      )
    vm: {
    }
    view: (ctrl) ->
      vm = BookList.vm
      return m('ul',
        vm.entries().map((e,i)->
          return m('li', e.name())
        )
      )
  }

  InitialView = {
    controller: ->
    view: (ctrl) ->
      return m('div')
  }

  m.mount(document.getElementById('book-list'), BookList)
)

とりあえずこんな感じで書いてみました。
マウントされて、controller が最初に処理されます。
そこで、 localhost の book.json にリクエストを送って、
promise で帰ってきたデータをそのまま Book クラス(っぽいやつ)に渡し、
view model の entries に詰めときます。
view は ul のあとに、 li で 取ってきた本の name をリストで表示するシンプルなものです。

ここでテストデータを適当に入れておきます。

1.upto(5000).each do |x| Book.create(name: "ジョジョの奇妙な冒険#{x}巻") end

リロードして表示すると大体こんな感じになります。
https://i.gyazo.com/f9a5b4bc414f56781012a6740cb174f2.png

この流れでそのまま input 要素と、 filter を加えたものがこちらです。

$(->
  Book = (data)->
    @name = m.prop(data.name || '')
    return this

  BookList = {
    vm: {
      searchText: m.prop('')
    }
    controller: ->
      vm = BookList.vm
      vm.entries = m.prop([])
      m.request({
        method: "GET",
        url: "/books.json",
        type: Book
      }).then((got) =>
        vm.entries(got)
      )
    view: (ctrl) ->
      vm = BookList.vm
      inputView = m('input', {
        oninput: m.withAttr("value", vm.searchText)
      }, value: vm.searchText())
      return [inputView,
        m('ul',
          vm.entries().filter((e) ->
            if vm.searchText() == ''
              return true
            e.name().match( vm.searchText() )
          ).map((e,i)->
            return m('li', e.name())
          )
        )
      ]
  }

  m.mount(document.getElementById('book-list'), BookList)
)

これで完成です。
コード的にはグローバル色が強くイケてない感じのコードですが、
このコードの短さ、シンプルさを考えるとすごいのではないでしょうか。
5000件のレコードも一瞬でフィルタすることができています。(Mac の一番良いいい + vimperator を使ってますが…)

大体こんな感じです。 https://i.gyazo.com/a676422458d6a9c55e0b1c8ce2a17592.png

全差分です

https://github.com/vimtaku/mithriljs-rails-sample

念のため。

所感

mithriljs について書いてる記事ではさんざん言われていると思いますが
SPA を作るのに適した機能がかなり揃っていると思います。
古き良き javascript の世界に、強力なアーミーナイフが登場したような、
jquery しかない世界に underscore.js を導入した時のような、
そういったひらめきのようなものを得られると思います。
(typescript 興味がありますがまだ触ってません.. ウッ..)

スピードが求められるようなプロトタイプを作るには持って来いだと思います。
DB と連携付きでここまでの機能が1時間そこそこでできる時代になったのは、とても感慨深いです。

mithril.js を触ってて思うのは、コードが自分の手の中にある感じがするな〜、ということです。
フレームワークを使っててありがちなのが、流れにそってやったら動いたー、けどよくわからんみたいな現象が、
あまりないように思います。それはやはり api がシンプルで、少ないことが寄与してるのでしょう。

mithril.js は複雑さと戦うためにはあまり好ましくないように思います。
まだ詳しくは見れてないですが、僕が観測している範囲では
reactjs + flux フレームワークの redux は複雑さと戦うために良い解のように思えます。

今度はこの辺りも触って記事にできればと思います。

終わりに

foodison のミッションは「世界の食をもっと楽しく」です。
このミッションに共感するようなエンジニアの方は、是非一度遊びに来てください!
昨日からかちどきのオフィスに移転して勤務が始まっています。
採用関連に関してもっと知りたい方は こちらの wantedly を参照してください。