Railsのプロジェクトを書いていたところ、ある1つのGemの存在を知りましたが、そこから派生していろいろなことに考えが思い至っていきました。
therubyracer
とは
Railsでは、JavaScriptやCSSといったアセットの生成用としてSprocketsという仕組みが組まれていますが、CoffeeScript、Uglifier、autoprefixerなど、アセットのコンパイルにはJavaScriptで書かれたコードを多用するものなので、execjs
という仕組みが入っています(過去に自分が書いたもの)。
そして、JavaScriptを動かすランタイムが必要なのですが、Unix系では多くの場合、V8エンジンを組み込んだtherubyracer
がよく使われて…いました。
問題点
therubyracer
はV8を細かく制御できる…のはいいのですが、細かなところまで使っているのが仇となって、
- テストも回しづらい
- V8のアップデートに追随するのが困難
というような状況になり、ついにはセキュリティ問題も含んだ古いV8でしか動かない状態が固定化してしまいました。
新顔のmini_racer
ここで登場したのがdiscourse/mini_racerです。自称「最低限のブリッジ」を謳ってはいますが、ふつうにJavaScriptのコードを呼んで実行する以上のこともできますし、よほど極限的な用途でもない限り事足ります。
ブリッジ部分をシンプルにしたことで、V8のバージョン依存性も緩やかになり、バージョンアップに追随しやすくなっています。そして、libv8
自体もバージョンが上がったことで、インストール性が改善されているようです。
シンプルさの価値
Unix哲学にも、「一つのことを行い、またそれをうまくやるプログラムを書け。」という言葉があります。「RubyからJavaScriptを実行するランタイム」の場合、大半のユーザーが求めるものは「execjs
で実行できること」でしょうから、あまり複雑なバインディングを取らずに簡潔化する、mini_racer
の戦略も間違いなく合理的なものです。
もっとシンプルにしようとすると、別な問題が
とはいえ、libv8
もまだまだ巨大なプロダクトで、Rubyに組み込むのがうまくいかないこともあります。これをシンプル化する方策もいくつか考えられます。
- Duktapeのような、もっと軽量で組み込みに向いたJavaScriptエンジンを使う
- 別にインストールしたNode.jsを呼び出す形にして、組み込みの煩雑さを回避する
これらの方策でシンプルさは増しますが、引き換えに速度面ではハンデを負うことになります。これは多くの場合に問題となることですが、速度と性能のバランスも時を減れば移り変わっていくので、最適な形を選ぶのが難しいものです。
ただ、execjs
のようなレイヤーが入っていれば、使うランタイムも適宜切り替えられますので、このような抽象化レイヤーを入れて部品を入れ替え可能にしておく、というのもあとあと役に立ちうる設計です。
別解
もはやSprocketsには見切りをつけて、アセット類はすべてWebpackに任せる、というような分業も1つの方法かもしれません。ただ、CommonJSやES Modulesで標準化されているJavaScriptはいいとして、CSSをWebpackに載せてしまうと、Webpackが廃れたときに行き詰まる危険性が考えられます。