OpalでRubyをJavaScriptにコンパイルするにも書いた通り、opal
コマンドを使うとRubyスクリプトをJavaScriptに変換できます。
Opalはどちらかというとブラウザ上で実行することを想定して作られているようなのですが、Nodeでも実行できます。試してみましょう。
コンパイルしたファイルを読み込んで実行する
まずは素朴に変換したJSを実行してみます。サンプルとして以下のようなRubyスクリプトfoo.rbを使います。
class Foo
def self.foo
p "hello"
end
end
opalでfoo.rbをJSにコンパイルするには以下のようにします。
$ opal -c foo.rb > foo.rb.js
変換されたJSは大きいので、先頭部分20行のみを表示してみます。
$ head -20 foo.rb.js
(function(undefined) {
// @note
// A few conventions for the documentation of this file:
// 1. Always use "//" (in contrast with "/**/")
// 2. The syntax used is Yardoc (yardoc.org), which is intended for Ruby (se below)
// 3. `@param` and `@return` types should be preceded by `JS.` when referring to
// JavaScript constructors (e.g. `JS.Function`) otherwise Ruby is assumed.
// 4. `nil` and `null` being unambiguous refer to the respective
// objects/values in Ruby and JavaScript
// 5. This is still WIP :) so please give feedback and suggestions on how
// to improve or for alternative solutions
//
// The way the code is digested before going through Yardoc is a secret kept
// in the docs repo (https://github.com/opal/docs/tree/master).
if (typeof(this.Opal) !== 'undefined') {
console.warn('Opal already loaded. Loading twice can cause troubles, please fix your setup.');
return this.Opal;
}
このJSスクリプトをNodeで実行してみます。以下のようなスクリプトを用意します。
const Opal = require('./foo.rb.js').Opal;
Opal.Foo.$foo();
-c
オプションのみをつけてopalを実行した結果のJSスクリプトをrequireで読み込むと、上記のようにOpalオブジェクトが取り出せます。このOpalに対し、Rubyのクラス名とメソッド名を与えると、Rubyの実行ができます。気をつけて見ると分かる通り、メソッド名に$
をつけるのがポイントです。クラス名には不要です。
実行すると以下のように正しく動きます。
$ node test.js
"hello"
RubyスクリプトとOpal本体を個別に読み込んで実行する
opalでRubyをJavaScriptにコンパイルするで書いたような、Opal本体を取り込まないようにコンパイルされたスクリプトを、Opal本体とは別々に読み込んで実行することもできます。
先ほどのfoo.rbを、Opal本体を含まないJSにするには以下のように実行します。
$ opal -cOE foo.rb > foo.rb.js
こうすると、以下のようなJSファイルが生成されます。
/* Generated by Opal 0.10.2 */
(function(Opal) {
var self = Opal.top, $scope = Opal, nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice, $klass = Opal.klass;
Opal.add_stubs(['$p']);
return (function($base, $super) {
function $Foo(){};
var self = $Foo = $klass($base, $super, 'Foo', $Foo);
var def = self.$$proto, $scope = self.$$scope, TMP_1;
return (Opal.defs(self, '$foo', TMP_1 = function ːfoo() {
var self = this;
return self.$p("hello");
}, TMP_1.$$arity = 0), nil) && 'foo'
})($scope.base, null)
})(Opal);
続いてOpal本体です。厳密にOpal本体だけをJSにするのは難しそうなのですが、多少余計な部分を含んでもいいのであれば、以下のようにできます。
$ opal -cE -e 'nil' > opal.js
こうすると、opal.jsにOpal本体が入ります。
このopal.jsの最後の部分は以下のようになります。
$ tail opal.js
self.$require("corelib/process");
return self.$require("corelib/unsupported");
})(Opal);
/* Generated by Opal 0.10.2 */
(function(Opal) {
var self = Opal.top, $scope = Opal, nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice;
return nil
})(Opal);
このうちの/* Generated by Opal 0.10.2 */
以下のfunctionが「余計な部分」なのですが、特に悪さをするわけではないので、そのままにしておいて構いません。
ここまでで2つのファイル、opal.jsとfoo.rb.jsができました。これを読み込んで実行するには以下のようにします。
const Opal = require('./opal.js').Opal;
require('./foo.rb.js');
Opal.Foo.$foo();
1行目では先ほどと似たような形でOpalオブジェクトを呼び出しています。
2行目でfoo.rb(から変換されたfoo.rb.js)を読み込んでいます。
最後にFooクラスのfooメソッドを呼び出しています。
test.jsを実行してみます。
$ node test.js
"hello"
正しく実行され、"hello"
と表示されます。