その昔JavaScriptがあまり好きではなくて、「ブラウザでもRubyでスクリプト書きてぇ〜!」などと思っていた。 その後JavaScriptにもだいぶ慣れたのでそういうこともなくなったが、最近ではwasm化されて動かせるとのことなので触ってみた。
簡単な使い方
ruby.wasmのQuick Start (for Browser)に簡単な説明が書かれている:
<html> |
vm.eval(Rubyコード文字列)
でRubyコードを実行する。
Ruby側では require "js"
して JS
経由でアクセスできる。
- ruby.wasmの解説は ruby.wasm/packages/npm-packages/ruby-head-wasm-wasi at main · ruby/ruby.wasm に書かれている。
JS
モジュールに関しては module JS - ruby.wasm 0.3.0 Documentation に書かれている。
RubyからJavaScriptの関数を呼び出す
RubyからJavaScriptにアクセスするには、 JS::global
でJavaScriptのグローバルを参照できる。
例えばJavaScriptで関数を定義しておいて:
function jsFunc(...args) { |
Rubyから呼び出す:
JS::global.call(:jsFunc, 1, 2, 3) |
- 呼び出す関数名はシンボルでも文字列でもよい
受け渡せる型
RubyからJavaScriptに受け渡せる値の型は、
- シンプルな値(数値、文字列)は問題なし
- 配列はダメ
- ラムダ関数は可能(JSから普通に
f()
として呼び出せる):コールバックで使える
Uint8Array
RubyとJavaScript間で小規模以上のデータをやり取りしたいが配列はダメだった。
Uint8Array
はどうかと試したら可能だった。
Rubyから
u8a = JS::eval('return new Uint8Array(30)') |
で生成して受け取れる。
ブラケットを使って u8a[0] = 123
とか普通にアクセスできる。
JavaScriptの関数に渡せばJavaScript側からも見れた。
- 長さの取得方法はわからなかった(
.length
はエラー)
Rubyから値を返して、JavaScriptから呼び出す
vm.eval
だとJavaScriptの値を渡すことができない(リテラルとして埋め込んでならできるけど)のでなんとかしたい。
Rubyから関数を返してそれを呼び出してやればできるかと思ったがやり方がわからなかった。
これを参考に、クラスのインスタンスを返して、それに対して call("メソッド名", 引数, ...)
で呼び出せる:
// JavaScript |
vm.wrap(jsValue)
でRubyの値に変換(数値とかでも自動には変換してくれない)
ファイルシステム
ファイルの扱いがどうなっているか。
カレントディレクトリ
ルートにいる、ファイルは空:
p Dir.pwd # => "/" ルートディレクトリ |
ファイルの読み書き
自分の用途としてはブラウザ上で動かすことなので実際のローカルファイルに直接アクセスはできなくていいんだけど、メモリ上でファイルを扱えれば幅が広がると思って試してみたが、Errno::ENOTCAPABLE
が出る:
File.open('foo.txt', 'w') do |f| |
書き込みができないので、読み込みも対象ファイルがないためできず…。
ライブラリ読み込み
require "JS"
はできるが、他のライブラリは使えないっぽい。
require "date" |
CDNから読み込むwasmバイナリをruby.wasm
からruby+stdlib.wasm
に変更すればライブラリは使える。
ruby.wasmでは内部で wasi-vfs が使われてる(Ruby 3.2.0 リリース)とのこと。
+stdlib
はライブラリを事前にパックしてるもので、後からの書き込みはできないっぽい。
ファイル操作をできるようにするには
wasi-vfsでは実行時にファイルの書き込みなどはできなそう。
どうしたらいいかと漁ってたら、Ruby on Browserのソースが参考になりそうだった。
出来合いのruby.wasmを使うのでなく、@wasmer/wasi
や@wasmer/wasmfs
と組み合わせることで実現している。
ちょっと自分でも試してみたがWebpack関係?でうまく動かなかったので、また後日確認したい。