@ledsun blog

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

ruby.wasmでオートロードする

kakikataというペン習字練習用紙を印刷するruby.wasmアプリケーションがあります。

ledsun.github.io

複数ページ印刷する機能を追加するついでに、requrie_relativeを使ってファイル分割していました。 やっている内に、単純作業が面倒臭くなってオートローダーが欲しくなりました。 const_missingを使えばできるはずと思って、ひとまず作ってみました。

簡単オートローダー

https://github.com/ledsun/kakikata/blob/9fefffc518025fb559e4b5d7d5c07592b3f72a12/main.rb#L12-L31

# 定数名からモジュールをオートーロードします。
def Object.const_missing(id)
  module_name = id.to_snake_case
  JS::RequireRemote.instance.load(module_name)
  p "#{module_name} loaded!"

  mod = const_get(id)
  # 読み込んだモジュールに、サブモジュールのオートーロードを定義します。
  mod.define_singleton_method(:const_missing) do |sub_id|
    path = self.name.to_s.split('::')
                         .map(&:to_snake_case)
                         .join('/')
    module_name = "#{path}/#{sub_id.to_snake_case}"
    JS::RequireRemote.instance.load(module_name)
    p "#{module_name} loaded!"

    const_get(sub_id)
  end

  mod
end

初めて使う定数の読み出し時にconst_missingが呼ばれます。 定数名からRubyスクリプトのファイルパスを解決して読み込みます。 名前空間入れ子になっている場合は、親モジュールのconst_missingが呼ばれます。 モジュールを読み込んだときに、同様のRubyスクリプト読込処理を定義しています。

今回は比較的シンプルなアプリケーションなので、ルートディレクトリからのロードしか考慮していません。 ディレクトリ内のRubyスクリプトからロードしようとすると、ミスるはずです。 絶対パス指定にすれば良いだけのような気もしますが、試していません。

組み込みのも外部のもgemの読込は考慮していません。 erbをつかっていますが、手動でrequire 'erb'しています。 アプリケーションのコードをネーミングルールに従って読み込みます。

これだけの行数でオートローダーがかけるRubyって、不思議なプログラミング言語ですね。

以前はオートローダーは考えてなかった

僕が、もともと想定していた、ruby.wasmアプリケーションの作り方は、

  1. CRubyで大まかな動きを実装する
  2. ruby.wasmに移植する

でした。 Wordle SearchGitHub - ledsun/tetris: Rubyでテトリスを実装するは、このパターンです。 このパターンでは、オートローダーは要らないと考えていました。

Rails以外のCRubyアプリケーションを作る時にzeitwerkのようなオートローダーを使うのはあまり流行っていなさそうです。 Railsruby.wasmで動かしても、そんなに嬉しくなさそうです。 また、zeitwerkそのものをruby.wasmに持ってくるのは難しいです。 というわけで、オートローダーはオマケぐらいに考えていました。

kakikataは、ブラウザで印刷するためのアプリケーションなので、最初からruby.wasm向けに作っています。 CRuby版は存在しません。 CRubyでの開発体験を気にせずに、ruby.wasmではオートローダーが使えてもいいのかもしれません。

追記

よくると、このオートローダーは、動きがちょっと変です。

https://github.com/ledsun/kakikata/blob/9fefffc518025fb559e4b5d7d5c07592b3f72a12/app/document.rb

Documentはトップレベルのモジュールです。 Appモジュール内で呼び出されたので、App::Documentとして解釈しています。 ディレクトリがappなので解決できています。

リモート呼び出しだと、スカって次の候補を探すのはあまりやりたくありません。 もうちょっとシンプルなモジュール名-パス解決ルールを決めないと、汎用的なオートローダーとするのは難しそうです。