課題: Trunkを採用する場合にRustから[wasm_bindgen]
でexportしたfnをどうやってES側から呼んだらよいか?
- ※この記事は Exported wasm functions are not exposed to js code #298 に Workaround を書いたついでに書いたものです。
- ※執筆時点では 2022-09-01JSTのGitHub版(crates.io の 0.16.0 に幾つかの不具合修正などが merge されている状態)での"課題"として記述しています。
Trunk を採用する場合、以下のようにRust側からES側へexportした関数を呼び出したりしたい場合に困った事になります。
// wasm-bindgen プロジェクトで rust 側から ES 側へ関数を export する例
[wasm_bindgen]
pub fn my_function_from_rust(){}
この関数をES側のユーザーコードから呼び出すためには .wasm をHTMLページへ読み込む際にどこかしらに束縛しておく必用があります。しかし、 Trunk を採用するプロジェクトの場合は <link data-trunk rel="rust" href="../Cargo.toml" />
などの記述から自動的に生成される .wasm の読み込みの実装詳細は次のようになります。
<script type="module">import init from '/hoge-5c64fa4ec64c02b8.js';init('/hoge-5c64fa4ec64c02b8_bg.wasm')</script>
.wasmを読み込む.jsモジュールが読み込んだWASMをどこにも束縛しない実装詳細が生成されます。このためTrunkを採用する場合には、現状の表向きのドキュメントの範囲内の方法では .dist に生成後の .html の該当部分を次のように書き換える必用があります。
<script type="module">import init from '/hoge-5c64fa4ec64c02b8.js';init('/hoge-5c64fa4ec64c02b8_bg.wasm').then(wasm=>window.wasm=wasm)</script>
このようにTrunkが生成するHTMLの実装を"わざわざ"書き換えればES側から window.wasm.my_function_from_rust()
のように呼べます。
しかし、せっかく Trunk を採用するのに Trunk の build が走る都度、事後処理で書き換えをしなければならないのは面倒です。自動化するにしても trunk serve
に build の事後処理をフックするのにどうしようか、書き換えは何でやろうか、やや面倒そうです。
回避策: pattern_script
を使う
Trunk には表向きのドキュメントには記述がありませんが、 Trunk.toml
の [build]
セクションで使える pattern_script
があります。
- https://github.com/thedodd/trunk/blob/16ba0353822bed5e84d174cadbe02d4659e5a630/src/config/models.rs#L56
- https://github.com/thedodd/trunk/blob/16ba0353822bed5e84d174cadbe02d4659e5a630/src/config/models.rs#L296
/// Optional pattern for the app loader script [default: None]
///
/// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to
/// properly load the application. Other sequences may be included corresponding
/// to key/value pairs provided in `pattern_params`.
///
/// These values can only be provided via config file.
#[clap(skip)]
#[serde(default)]
pub pattern_script: Option<String>,
と、いうわけで Trunk は .wasm の読み込みを行う .js モジュールのHTMLに生成される記述について、 {base}
, {wasm}
, {js}
を変数として展開しつつ任意の文字列で設定できます。この機能を使い、 Trunk.toml
の [build]
セクションを次のように記述することで今回の課題の回避策とできます。
# Trunk.toml
[build]
pattern_script = "<script type=\"module\">import init from '{base}{js}';init('{base}{wasm}').then(wasm=>window.wasm=wasm);</script>"
これで trunk build
や trunk serve
で生成される .html が、 .wasm を読み込む .js モジュールのHTML部分の実装を細工しRust側から[wasm_bindgen]
でexportした関数をES側から呼び出すなどできる状態で出力されるようになります。
執筆時点での examples にもあるように yew や seed を採用して Rust 実装だけで HTML の生成など行いイベントハンドルも組み込めればよいプロジェクトでは今回の例のように window.wasm に束縛しておいて云々のような実装は必用ありませんが、HTML側の実装はほぼpureなHTMLで行いたい場合や、部分的に npm を併用して開発したい場合などに困る方の一助となれば幸いです。
バインディング付きで欲しい場合
先の例では生の .wasm モジュールを適当な場所にマウントしていますが一部のコード研究用でなければ通常は同時に生成されている .js バインディングモジュールをまるごとマウントしたい場合が多いと思います。
[build]
pattern_script = "<script type=\"module\">import init, * as with_bindings from '{base}{js}';init('{base}{wasm}').then(()=>window.wasm_with_bindings=with_bindings);</script>"
# もちろん、変数名やマウントポイントはお好みで構いません。 (`wasm_with_bindigs`, `window.wasm_with_bindings` )
参考: