-- Views
September 05, 26
スライド概要
RubyKaigi 2026 follow up ( https://rhc.connpass.com/event/392503/ ) の発表資料です。
RubyKaigi 2026で発表したRuby's Blanket(ruby.wasm上でJavaScriptからRubyを操作するAPIを充実させていくプロジェクト)の進捗報告です。
Ruby’s blanket 進捗報告 2026/09/05 RubyKaigi 2026 follow up
Self Introduction Luxiar co., Ltd. Shigeru Nakajima, Chief Architect ledsun on GitHub and Twitter ko.rb organizer 9/14 meetup in Tokyo 株式会社ラグザイアはRubyKaigi 2026 follow upのスポンサーです。 1/28
目次 Ruby::Box in the Browser require_relative in the Node.js Ruby’s Blanket Ruby’s Blanketに限定せずにruby.wasmの進捗を話します。 2/28
Ruby::Box in the Browser
ブラウザ上のruby.wasmでRuby::Box動きます。
<script
src="https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@latest/dist/browser.script.iife.js"
data-env='{"RUBY_BOX":"1"}'
></script>
<script type="text/ruby">
box = Ruby::Box.new
box.eval <<~RUBY
X = 123
RUBY
puts Ruby::Box.enabled? #=> true
puts box::X
#=> 123
</script>
3/28
data-env ポイントはdata-env属性。 data-env='{"RUBY_BOX":"1"}' 任意の環境変数を設定できます。 RUBY_FIBER_MACHINE_STACK_SIZEも変更可能 前田修吾さんはRUBY_FIBER_MACHINE_STACK_SIZEを設定できなくて、2022年? の忘年会のビンゴにつかうのを諦めた 4/28
背景 browser.script.iife.js では Ruby VM 起動後に ユーザーコードが動く Ruby::BoxはRuby VM起動時に環境変数が必要 ビルドし直せばできるけど・・・ ruby.wasmをフルビルドすると20分・・・ 5/28
解決策 ビルドしないで任意の環境変数が設定できる! 6/28
data-env の使い方
<script
src="https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@latest/dist/browser.script.iife.js"
data-env='{"RUBY_BOX":"1"}'
></script>
scriptタグにdata-env属性を追加
JSON形式で環境変数を指定
キーと値は文字列(数値不可)
7/28
require_relative in the Node.js
Node.js上のruby.wasmでrequire_relativeが動
きます。
const { vm } = await DefaultRubyVM(module, {
preopens: {
"/app": "./app",
},
});
vm.eval(`require_relative "/app/main"`);
ブラウザではすでにrequire_relativeは使えます。
8/28
preopens ポイントはpreopensオプション。 preopens: { "/app": "./app", }, 任意のローカルディレクトリをマウントできる 9/28
没案 ブラウザはfetchを使ったJS::RequireRemoteを実装 Nodeもfs.readFileSyncを使って独自ローダーを RubyKaigi 2026で動かしたデモはこの方式でした。 10/28
JS::RequireRemote方式のデメリット Node.jsプロセスが参照できるディレクトリがす べて読めてしまう Node.jsのセキュリティモデルに依存 ロード処理が自前で、CRubyと挙動が変わる fetchしか使えないブラウザでは妥協できる ファイルシステムが使えるNode.jsならCRuby と同じ挙動を期待 11/28
解決策 WASIのpreopensオプションを指定! 12/28
preopensオプションの使い方
const { vm } = await DefaultRubyVM(module, {
preopens: {
"/app": "./local_dir",
},
});
ruby.wasmのVMの起動オプションを追加
JSON形式で指定
キーが仮想パス、値がローカルディレクトリ
13/28
Ruby’s Blanket おさらい 14/28
Ruby-to-JavaScript vs JavaScript-to-Ruby Feature eval Method Call Property Access Object Conversion Load Ruby Scripts R→J Yes Yes Yes Yes browser J→R Yes call N/A string No 15/28
Currently, JavaScript calls Ruby using eval const script = ` class Dog def name()= 'john' end Dog.new ` const dog = vm.eval(script); dog.call("name").toString(); // => "John" 16/28
RubyKaigi 2026でのデモ vm.eval(` require "js/require_local/relative_shim" JS::RequireLocal.instance.base_dir = "require_local" require_relative "./dog" `); const dog = vm.eval("Dog.new"); console.log(dog.call("vow").toString()); // woof console.log(dog.call("vow").toString()); // woof console.log(dog.call("count_of_vows").toString()); // 2 17/28
preopensをつかえば vm.eval("require_relative './dog'"); const dog = vm.eval("Dog.new"); console.log(dog.call("vow").toString()); // woof console.log(dog.call("vow").toString()); // woof console.log(dog.call("count_of_vows").toString()); // 2 18/28
JavaScriptからRubyのメソッド呼び出ししたい // before user.call("name"); user.call("greeting"); // after user.name(); user.greeting(); 19/28
RbValueのProxy化 RbValueオブジェクト(Rubyの値をラップする JavaScriptのオブジェクト)に、JavaScriptのProxy というメタプログラミング機能を使って、Rubyのメ ソッドを動的に探せば実現可能。 仕様の整合性に考えることがたくさん 20/28
メソッド探索順序 1. RbValue オブジェクトに定義されたJavaScript プロパティ 2. RbValue のJavaScript API 3. RbValue のプロトタイプチェーンで定義された JavaScriptプロパティ 4. Rubyメソッドへの転送を禁止する予約名 5. Rubyメソッド 21/28
予約名とは? JavaScriptのランタイムやツールが暗黙に呼ぶ名前 then toJSON inspect Rubyオブジェクトに同名のメソッドが定義されてい たら隠す(undefinedを返す)。 RbValueオブジェクトに動的に定義されたプロパ ティがあればそれを返す。 22/28
JavaScriptのセッターは? user.name = "Bob"; JavaScriptのプロパティを更新する。 Rubyのメソッドを呼ばない。 value.call("name=", newName) よびだければcallを使う。 JavaScriptのセッターは動的なメソッド定義にもなる 23/28
JavaScriptのメソッドオブジェクトとして使える? const foo = value.foo; foo(); 考慮しない! 24/28
メソッドに渡す引数はJavaScriptの値? // before array.push(vm.eval("123")); // after array.push(123); 次のチャレンジとする! 25/28
JavaScriptのコールバック関数の場合は? // before JS.global.receive( JS::Object.wrap(User.new("Alice")) ) // after JS.global.receive(User.new("Alice")) 次のチャレンジとする! 26/28
JavaScriptのAPIでRubyのメソッド探せる?
typeof value.foo;
if (value.foo) {}
探せない。
value.call("respond_to?", vm.eval('"foo"'));
callを使う
27/28
おわり ご清聴ありがとうございました。 28/28