@ledsun blog

無味の味は佳境に入らざればすなわち知れず

ruby.wasmのrequire_relativeでfetchする

ruby.wasmをブラウザで動かす時require_relativeを相対パスへのfetchに置き換えられる? - @ledsun blogruby.wasm ハックアイデアを思いつきました。 実際にやってみます。

packages/npm-packages/ruby-wasm-wasi/src/browser.script.ts に次のようなコードを足すとrequire_relative関数にモンキーパッチを当てられます。

  vm.eval(`
    require "js"
    module Kernel
      def require_relative(relative_feature)
        JS.global.fetch(relative_feature + '.rb')
      end
    end
  `);

ブラウザ上でhogeモジュールをダウンロードしているところ

fetchしたは良いものの、ダウンロードしてきたRubyスクリプトを評価する方法がわかりません。 vm.evalに渡せばいいのですが、Rubyスクリプト内からJavaScript内のvm変数を参照する方法がわかりません*1

あるいは、RubyVMの中で評価できればそれでもいいかもしれません。 モンキーパッチを当てる前のrequire_relativeはどうやって取得したRubyスクリプトを評価しているのでしょうか?

Rubyメソッドの実装を参照するためのいくつかの方法 - BOOK☆WALKER Tech Blog を参考にしてrequire_relative のソースコードを探します。

  1. Kernel.#require_relative (Ruby 3.1 リファレンスマニュアル)
  2. module Kernel - RDoc Documentation
  3. ruby/load.c at 4325e90205aa4cd0ea031df1b5e6334bfd9c7e51 · ruby/ruby · GitHub

rb_require_string -> require_internal -> require_internal -> load_iseq_eval -> rb_parser_load_file て、めっちゃC言語の中をさまよっているからRubyスクリプトから呼べなくないですか?

もしかして単にKernel.evalすればいいですか? では、Rubyスクリプト内fetchで取得したファイルから文字列を取り出しましょう。 と、おもいましたが、RubyスクリプトからJavaScriptのfetch関数をawait付きで呼び出したり、thenにコールバック関数を渡す方法がわかりません。

あれ?JavaScriptのグローバルオブジェクトが取れるのであれば、グローバルオブジェクトに関数を生やせば、Rubyスクリプトから呼び出せますか? つまり次のようにrb_require_relativeグローバル関数を定義します。

  const _global = (window) as any
  _global.rb_require_relative = (relative_feature) => {
    fetch(relative_feature + '.rb')
        .then(response => response.text())
        .then(ruby_script => vm.eval(ruby_script))
  }

  vm.eval(`
    require "js"
    module Kernel
      def require_relative(relative_feature)
        JS.global.rb_require_relative(relative_feature)
      end
    end
  `);

そして次のようなhoge.rbをrequire_relative 'hoge'して上げれば

p "hoge loaded!"

こうです!

ブラウザ上でhoge.rbをrequire_relativeしたところ

*1: 後で気がつきました。JS.global[:rubyVM]で参照できます。ハックしてたソースコードのすぐ上に globalThis.rubyVM = vm; って書いてあるの見落としてました。