Rails で watchify と browserify を使い分けられる gem を作った


弊社は今週はハックウィークといって通常業務止めて周辺ツール作ったりいつもはできないことをやる週として割り振られている。(その割には昨日Contributionの計算の変更とかリリースしてた気がするが)

それで仕事で使ってた browserify_rails が遅すぎて辛かったので、趣味でいつも使ってた watchify をRailsにインテグレーションするgemを作ってみた。

はじめて作ったgemなんで手加減して https://github.com/increments/brwy_rails 。

brwy は browserify_watchify_rails が長すぎて略したけど意味わからん気がする。

## watchify とは

browserifyと違ってコンパイル後もコンパイラプロセスが中間状態を保持したまま常駐して、依存のファイル監視して差分ビルドを行ってくれる。仕組み知ってたら速くなるのわかると思う。

babel使ってたらbabelがモジュール分割されすぎてて初期化が遅いのがボトルネックになるんだが、それもメモリに常駐してるのでスキップできる。

## 使い方

```rb
# config/application.rb
config.assets.precompile = ["application.bundle.js", "application.css"]
config.brwy_rails.targets = [
  "app/assets/javascripts/application.js"
]
Rails.application.config.middleware.use BrwyRails.Middleware
# config.brwy_rails.browserify_opts = "-t babelify" # your browserify compile command
# config.brwy_rails.verbose = true
# config.brwy_rails.target_suffix = ".bundle"
# config.brwy_rails.watch = Rails.env.development?
```

view から呼び出すときは、名前に .bundle(`config.brwy_rails.target_suffix` を参照)をつけて呼び出す。

```erb
<%= javascript_include_tag 'application.bundle'%>
```

## 仕組み

- `tmp/cache/brwy_rails` を assets.paths に追加する
- brwy_rails.targets の数だけ watchify プロセスを spawn する
- watchify は `tmp/cache/brwy_rails/*.js`  に `*.bundle.js` を吐き出す
- at_exit で watchify プロセスを全部 kill して終了

事前にプロセスを立ち上げるので、targets で指定した `*.bundle.js` しかコンパイルしない
プロダクションなら単にbrowserify呼んでasset_path 以下に js 吐いて終了。

これでQiitaのjsに適用すると、コンパイルが 10s が 0.5s になった。

## やってないこと

- `browserify_rails` みたいな asset_path をよしなにやってパスを解決する
- `//= require ...` みたいな sprockets require の解決
    - 全部 commonjs でやれ

## TODO

- `brwy_rails:build` を `assets:precompile` の前に流そうと、自分の環境で http://qiita.com/mizchi/items/b802f1853466b1cc54c3 をやってみたのだが、何故か assets:precompile を完全に上書きしてしまって動かなかった。別のRails リポジトリと挙動が違う。 @r7kamura に相談したが不明。たすけて。
- ドッグフーディング => はまだ
- 本当はSprockets依存を捨てたい

## おまけ: はじめての rails gem

- `rails plugin new hogefuga -t` で雛形をつくる
- `lib/hogefuga/railtie.rb` で コンフィギュレーションできる部分を作る(後述)
- `lib/hogefuga.rb` で `require 'hogefuga/railtie'` を追加する
- `test/dummy/` 以下にテスト用の rails があるので `rails s` して動作確認する
- OK?
- https://rubygems.org/ でユーザー登録する
- https://rubygems.org/profile/edit でローカルとの紐付けスクリプトが与えられてるので、それを実行する
- github に push する(これ必須じゃない気がするけどまあやっとけって感じ)
- `*.gemspec` でTODOになってる部分を自分で埋める
- `Rakefile` に `require "bundler/gem_tasks"` を追記する
- `bundle exec rake release`
- おわり

## おまけの Railtie の書き方

```ruby
module Hogefuga
  class Railtie < Rails::Engine
    # config/application.rb とかで設定する
    config.hogefuga = ActiveSupport::OrderedOptions.new # なんでも出来るっぽい雑な Hash
    config.hogefuga.verbose = true
    
    config.after_initialize do
       # 読み込み完了後, config にしたがって何かする 
       puts "Hello hogefuga" if hogefuga.verbose
    end
  end
end
```

これが最小っぽい。Rails::Railtie と Rails::Engine だと Engine の方だと app/ 以下を自動でパスを指して参照できる、みたいな話だった。

Rails Engine については http://railsguides.jp/engines.html 
> エンジン (engine) とは、アプリケーションのミニチュアのようなものであり、ホストアプリケーションに機能を提供します。Railsアプリケーションは実際にはエンジンに「ターボをかけた」ようなものにすぎず、Rails::ApplicationクラスはRails::Engineから多くの振る舞いを継承しています。

## おまけ: RackMiddleware

Rails より下の Rack層でリクエストな内容を覗いたり、書き換えたかったら middleware 作って use する。

最小のmiddleware

```ruby
module Hogefuga
  class Middleware
    def initialize(app)
      @app = app
    end

    def call(env)
      @app.call(env)
    end
  end
end

Rails.application.config.middleware.use BrwyRails.Middleware
````

`def call(env)` のなかで sleep 1 などと書くとすべての request が1秒遅延する。brwy_rails だとこれを利用して **watchify がコンパイル中でファイルが空ならコンパイルを終わるまで待つ** という実装をしてみた。

https://github.com/increments/brwy_rails/blob/master/lib/brwy_rails/middleware.rb

## 感想

日常であんまりRuby書かないので、雑に r7kamura にやりたいことに対するエントリポイントを聞いたりして色々学習過程をスキップできたけど、Railsの気持ちみたいなもの、まだよくわからなくて、俺Railsのことちゃんとわかってなかった…