kakikataというペン習字練習用紙を印刷するruby.wasmアプリケーションがあります。
複数ページ印刷する機能を追加するついでに、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アプリケーションの作り方は、
- CRubyで大まかな動きを実装する
- ruby.wasmに移植する
でした。 Wordle SearchとGitHub - ledsun/tetris: Rubyでテトリスを実装するは、このパターンです。 このパターンでは、オートローダーは要らないと考えていました。
Rails以外のCRubyアプリケーションを作る時にzeitwerk
のようなオートローダーを使うのはあまり流行っていなさそうです。
Railsをruby.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なので解決できています。
リモート呼び出しだと、スカって次の候補を探すのはあまりやりたくありません。 もうちょっとシンプルなモジュール名-パス解決ルールを決めないと、汎用的なオートローダーとするのは難しそうです。