西日暮里.rbでSprocketsを捨てたいという内容のLTをした

西日暮里.rbの1周年記念LT大会でSprocketsを捨てたいというLTをさせてもらった。

かれこれ2週間ぐらい前の話。ただでピザ食べられて、ビールも飲めて、ほんと最高だった。

資料

www.slideshare.net

今回資料結構詳しく書いた(発表する時間が全然足りなかった。。反省。)ので、内容は置いといて気持ちを中心に追記する。

Sprocketsを捨てたいという気持ちについて

最近のフロントエンド衰勢はほんとに早い。
気を抜くとすぐ「まだxxで消耗してるの??」と言われて、むきー!となる。

あと煽り属性強めな人多い。(それはそれでいいところ、というか僕は好き。でもある程度の煽り耐性必要。)

そんな環境の中でSprocketsというのは、はっきりいってしまえば邪魔。

reactを使うためには、react-railsを使わなければいけない?
browserifyを使うために、browserify-railsを使わなければいけない?

そんな世界はもういやだ!

このruby gems達は果たして、常に最新を追っていけるのか。
reactやbrowserifyならば、今は人気もあって、メンテしていくモチベーションもあるだろう。
しかし、それがどこまで続くのかは保証がない。

ライブラリが廃れても、今走っているプロダクトは止まれない。
好きなライブラリを使う!という意思表示は、プロポーズと似ている。

一度選んだからには、真摯に付き合わなくてはならない。(とはいえ移行計画は進めつつ。。)

例えgemは最新を追っていたとしても、依存関係のあるgemやRailsのバージョンなどが、なんらかの事情ですぐに最新にあげるのが難しい場合もあるだろう。

Railsのバージョンとgemのバージョンが合わなくて最新ライブラリ使えません。 ><

そんなハメには陥りたくないんです!!

という気持ち。

資料の最後に書いた課題について

以下の3つが課題かなー、と資料に書いた。

  1. 移行するにあたってSprocketsの付与しているmd5フィンガープリントをどうするか
  2. Sprocketsのrequireディレクティブをどうやって撲滅するか
  3. gemにassetsが含まれているやつらの対処

その後の展望的なものを備忘録的に記す。

移行するにあたってSprocketsの付与しているmd5フィンガープリントをどうするか

もちろんgulpタスクでファイルの末尾に何らかの文字列を付与するのは簡単。

なんだけど、それを愚直にやってしまうと、Railsのasset_pathヘルパーメソッドが効かなくなるという問題が発生する。

会社でこれ問題だなーって話してたら、先輩とか同僚とかに色々アイディアもらった。
「筋がいいなー、これがいいなー」と思ったのは、gulpでビルドした時に付与した文字列を一旦どこかに出力しておいて、それをRailsが見に行くようにするというアイディア。

この仕組み作れればこれはなんとかなりそう。今度asset_pathメソッドの仕組み読んでみて、実装を模索しよう。

Sprocketsのrequireディレクティブをどうやって撲滅するか

Sprocketsのrequireディレクティブとbrowserifyのrequireは共存できない。
Sprocketsのrequireディレクティブが存在する状態でbrowserifyかけると、Sprocketsのrequireディレクティブは死ぬ。

これを解決するためには、browserify-railsを使うか、単純にファイル単位で置き換えて、置き換えできたものにだけbrowserifyをかければ良い。

ファイル単位に置き換えていくのはつらい作業だけど、xxxx-railsなgemは使いたくない。必要なのは気合いと根性だ。

gemにassetsが含まれているやつら

jsライブラリなら殆どはnpmにあるので困らないと思う。 だけどviewをgenerateする機能があって、styleもあててるようなやつはどうするか。 (管理画面作る系のgemにありがちな気がする。。)

資料にも書いたけど、そのgemやめるのが近道。それかそのgemのassetsだけリポジトリにコミットして管理する荊の道を歩くか。

Sprockets完全撤廃にこだわらないのであれば、この解決だけSprocketsでもいいかも。

最後に

西日暮里.rb、1周年ほんとにおめでとうございます!

braidという世界最高のバンド

まじで最高だった。人生で一番興奮したライブだったかもしれない。

braid好きすぎるので懐古厨になってしまって、昔の曲はやっぱり良いけど最近のやつはなー、とか言ってしまうのを危惧していたけれど、そんなのは完全に杞憂で、新旧どっちも最高だった。

新しめの曲はライブだとCDとは全然違う熱量があって、一言で言えばキレッキレッだった。

ライブ会場でたまたま大学の先輩と会って、終わった後にご飯食べに行ったのだけど、その人は11年ぶりに観たらしい。11年という重みすごい。

その先輩とも最高のライブだった余韻を共有できた。

11年経っても色あせてないのすごい。

braidは長く活動休止してたけど、みんな現役でやってるもんな。

とにかく最高だった。あとbobと握手できてテンションあがった。

pumaに学ぶrubyのDSLの作り方

調べごとがあって、pumaのソースを読んだ。

そこでpumaの設定ファイルに書いてあるDSLの適用の処理が勉強になったので書く。

(puma: v2.11.0 時点)

pumaのソース

puma/dsl.rb at master · joe-re/puma · GitHub

ここでやっている。

主要な処理だけ抜き出した

主要な処理だけ抜き出して、少しだけ修正して分かりやすくした。

pumaに学ぶRubyのDSLの作り方

これを実行すると以下になる。

[~/src/sandbox/ruby_dsl]$ ruby dsl.rb
blockも渡せる
#<DSL:0x007fc39a1fc740 @options={:foo=>"test", :boolean_value=>true}>

config.rbで設定した値が適用されたのが分かる。

いいところ

configファイルを間違って記述したときに、分かりやすい形でエラーを返してくれる。

config.rbを書き換える。

foo 'test'

block_test do
  puts 'blockも渡せる'
end

# コメントは無視される

# boolean_test
boolean_toast # typo!

実行する

[~/src/sandbox/ruby_dsl]$ ruby dsl.rb
blockも渡せる
config.rb:10:in `_load_from': undefined local variable or method `boolean_toast' for #<DSL:0x007f91c106c570> (NameError)
...

config.rbの10行目のboolean_toastが違うよ!と言ってくれる。賢い。

Rubyで書ける

DSLとは言え、コメントとかブロック書くときにRubyのルールで書けるのはうれしい。

実装が簡単

分かりやすいメタプログラミング

解説

DSLクラスの_load_formメソッド内のinstance_evalに注目する。

instance_evalはrubyのBasicObjectに定義されているので、全てのオブジェクトで使えるインスタンスメソッドだ。

evalと同様、文字列が渡されたときはそれをRubyのコードとして評価するのだけど、違うのはinstance_evalのレシーバが評価のコンテキストになるということ。

この場合、レシーバを書かずにインスタンスメソッド内で実行しているので、レシーバはselfになる。

下と同じ。

self.instance_eval(File.read(path), path) if path

第1引数には、File.read(path)でファイルを読み込んだ文字列が渡されている。DSLクラスのインスタンスをコンテキストにして、この文字列が評価される。よって、config.rbに書いたDSLと同名のDSLクラスのインスタンスメソッドが呼ばれる。

instance_evalメソッドの第2引数にはファイルパスを渡していて、これのおかげでエラーが分かりやすくなる。

渡したファイルパスで実行されているようにスタックトレースを差し替えてくれるのだ。

instance method BasicObject#instance_eval

終わりに

unicornも読んでみたらほぼ同じ仕組みだった。

デザインパターンなのか?今日学んだパターンの名前を僕はまだ知らない。