2011-09-04

Rubyのexecjsがすごい件

できることと起動方法とエンジンの違い

  • RubyスクリプトからJavaScriptコードを実行できる
  • V8, node, spidermonkey, rhino などの中からそのとき利用できるエンジンを autodetect して実行してくれる
  • 環境変数からエンジンを指定できる
    • ExecJS::Runtimes の中で定義されている RubyRacer や Node の名前で export EXECJS_RUNTIME=Node などと指定する
    • 例えば Rhino は therubyrhino gem に依存する。こうした依存 gem は自動では入らないので注意が必要
    • 何の gem も準備していなければ execjs 1.2.4 の段階では node.js, JavaScriptCore, SpiderMonkey, JScript の順で利用可能かどうか探してくれる。
  • 当然上に挙げた ExternalRuntime は外部プロセスの起動が入るので重い。Node よりも TheRubyRacer で V8 を直接叩いた方が速い。

※ 手元でいくつかのファイルを jshint に掛けるツールを動かしたら TheRubyRacer で 0.14s 掛かる処理が Node だと 2.28s 掛かった。だいたいこの数値で安定しているので、16倍くらい違う。数が多くなればなるほど差は開く。

Windows の人も node.exe に PATH が通ってれば node.js で動かせる。残念ながら今のところ Windows では npm は動かないので node.js の旨味がだいぶ減ってしまっているんだけど、execjs を使えば node.exe を使うスクリプトを自由に実行することができる。

というわけで手始めに jshint4rというものを作りました。使ってみてね。

簡単な使い方

  • exec
  • eval
  • compile & call

の3つの使い方がある。

副作用だけで希望の処理が実現できるならそのまま exec にコードを与えれば ok. 戻り値が欲しい場合は eval で呼ぶ。

ExecJS.exec('1 + 1') # => nil
ExecJS.eval('1 + 1') # => 2

中で

(function( ... ){})()

の中に source を入れて実行するだけなのが exec. この前に return を置いて値を返すのが eval.

call は eval の応用みたいな感じで、compile 済みの JS ファイルの中のある function を call する格好になる。例えば

context = ExecJS.compile('function add() { return 1 + 1 }')
context.call('add') # => 2

呼び出し方は

context.call( FUNCTION, ARG1, ARG2, ... )

みたいになる。たぶんちょっとやってみればすぐに分かると思う。

気をつけなきゃいけないのは compile に文字列を与えるところかな。だから例えば jshint に JavaScript のコードを与えたい場合は

context = ExecJS.compile( File.read( /PATH/TO/jshint.js ) )
context.call( 'JSHINT', File.read( /PATH/TO/SRC ), opts )

みたいな形になる。(ただし、JSHINT 関数は true か false しか返さないため、このままでは使いものにならない。)

JavaScript 側に特別手を入れずに Ruby でその実行結果を活かそうと思ったら eval か call を使う形になるのではなかろうか。

まとめ

compile とかうまく隠蔽すればかなり自然に Ruby コードの中から JavaScript を実行してその結果を利用することができると思う。TheRubyRacer を使えばオーバーヘッドもまったく気にならない。

いやこれはすごいな。JavaScript は今後もどんどん応用範囲が広がって行くと思うんだけど、その成果を丸ごと(対応していないオブジェクトはダメだけど)いただくことができる。なんて贅沢。

おまけ

Rails 3.1 から execjs を利用しているため、何らかの JavaScript Runtime がインストールされていないと Rails 環境を起こすことができない。

Windows の場合は WSH が、Mac の場合は JavaScriptCore が最初から入っているので問題ない。Linux の場合はなんらかの JavaScript 実行環境が必要になる。node.js をやっている人はこれを利用するので問題ないけど、普段そこまで JavaScript に入れ込んでない人は TheRubyRacer でも TheRubyRhino でも入れるとよい。いちばん簡単に入るのはたぶん SpiderMonkey じゃないかな。結構いろんな環境でサクっとインストールできるはず。

[2012-06-11 追記] 2012-05-20 の commit で SpiderMonkeyRuntime が deprecated となり、環境変数 EXECJS_RUNTIME で明示しない限り起動することができなくなりました。バージョンで言うと v1.4.0 以降。Linux 環境では node.js とか Rhino とか入れて使うようにしていこうぜ、という方針のようです。

cf. Deprecate SpiderMonkey runtime · 71fe9e8 · sstephenson/execjs

同時に Johnson も Mustang も deprecated になってますね。

About

例によって個人のなんちゃらです