弊社は今週はハックウィークといって通常業務止めて周辺ツール作ったりいつもはできないことをやる週として割り振られている。(その割には昨日Contributionの計算の変更とかリリースしてた気がするが)
それで仕事で使ってた browserify_rails が遅すぎて辛かったので、趣味でいつも使ってた watchify をRailsにインテグレーションするgemを作ってみた。
はじめて作ったgemなんで手加減して https://github.com/increments/brwy_rails 。
brwy は browserify_watchify_rails が長すぎて略したけど意味わからん気がする。
watchify とは
browserifyと違ってコンパイル後もコンパイラプロセスが中間状態を保持したまま常駐して、依存のファイル監視して差分ビルドを行ってくれる。仕組み知ってたら速くなるのわかると思う。
babel使ってたらbabelがモジュール分割されすぎてて初期化が遅いのがボトルネックになるんだが、それもメモリに常駐してるのでスキップできる。
使い方
# 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
を参照)をつけて呼び出す。
<%= 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 の書き方
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
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 がコンパイル中でファイルが空ならコンパイルを終わるまで待つ という実装をしてみた。
感想
日常であんまりRuby書かないので、雑に r7kamura にやりたいことに対するエントリポイントを聞いたりして色々学習過程をスキップできたけど、Railsの気持ちみたいなもの、まだよくわからなくて、俺Railsのことちゃんとわかってなかった…