wasm-pack なしで Rust コードを NPM パッケージ化する
これまでのあらすじ
先月、rustwasm がアーカイブされることが Rust 公式ブログでアナウンスされていました。rustwasm は、wasm-bindgen や wasm-pack をホストしている GitHub の origanization です。元となっていた Rust and WebAssembly ワーキンググループはもう活動してないので、混乱を避けるため、GitHub 上も実態に合わせよう、という経緯らしいです。
↑のブログの時点では、「wasm-bindgen は別の場所で引き続きメンテされるが、他は未定」という状態でしたが、↓の記事にまとめられているように、wasm-pack などもメンテナが見つかっています。
とはいえ、wasm-pack がどれくらいメンテされるのかはわかりません。元メンテナの人が個人で引き取ったかたちですが、issues が積もっていっているのを見ると、このメンテに割ける余力がそんなにあるわけではなさそうな雰囲気です。
ということで、もしなにかの問題で wasm-pack が動かなくなった日がくることに備えて、いちおう wasm-pack がやってることを知っておいて損はないだろう。と思い立ちました。
wasm-pack なしで Rust コードを NPM パッケージ化する方法の記事
なんのことはない、すでに記事があるのでこれを読んでいきます。ここから先は、この記事に書かれてることを実際にやってみただけの内容です。
大きな流れ
やることは主に 4 つです。
-
wasm32-unknown-unknownをターゲットにしてcargo build - 出来上がったバイナリ(
*.wasm)に対してwasm-bindgenを実行 - その結果に対して
wasm-optを実行 -
package.jsonを追加
なお、実際に試す Rust のコードは MDN の wasm-pack 入門ドキュメントに載ってたやつ(アラートを出す関数)を使いました。
cargo build
まずは、wasm32-unknown-unknown ターゲットをまだインストールしていなければインストールします。
rustup target add wasm32-unknown-unknown
そしてビルドします。ちなみに、--release をつけなくても(デバッグビルド)でも動きます。
cargo build --release --lib --target wasm32-unknown-unknown
こうすると、target/wasm32-unknown-unknown/release(もしくは target/wasm32-unknown-unknown/debug)以下に <クレート名>.wasm というバイナリができているはずです。
wasm-bindgen
まずは wasm-bindgen CLI をインストールします。cargo binstall でもインストールできるみたいです。詳しくは公式ドキュメントを参照してください。
cargo install wasm-bindgen-cli
そして、インストールされたコマンドを実行します。
wasm-bindgen \
--out-dir ディレクトリ名 \
--typescript \
--target bundler \
./target/wasm32-unknown-unknown/release/クレート名.wasm
--target に指定できるオプションは以下です。ここでは、NPM パッケージ化するのを前提にしているのでデフォルトの bundler にしています(デフォルトなので指定しなくてもいい)。
wasm-opt
wasm-opt は、WebAssembly のツールチェーン(Binaryen)に含まれるコマンドで、 WebAssembly バイナリを最適化するためのコマンドです。
上のブログ記事では、cargo install でインストールできる Rust のラッパーを使ってるんですが、公式のものを使うのでよさそうです。macOS なら brew でインストールできます。Windows は特にインストール方法は提供されてなさそうですが、releases にビルド済みバイナリがあります。ダウンロードして展開すればそのまま使えます。
wasm-opt の使い方はこんな感じです。
path/to/binaryen-version_123/bin/wasm-opt \
./ディレクトリ名/クレート名_bg.wasm \
-o ./ディレクトリ名/クレート名_bg.wasm-opt.wasm \
-O
# 最適化されたバイナリで元のを置き換える
mv ./ディレクトリ名/クレート名_bg.wasm-opt.wasm ./ディレクトリ名/クレート名_bg.wasm
最後の -O は、デフォルト[1]の最適化オプションを使う、という意味のオプションです。もっと最適化を利かせたい場合は -Oz とかいろいろあるみたいです。
package.json
ここはブログ記事どおり、こういう適当な package.json を置いておけばいいみたいです[2]。
{
"name": "適当な名前",
"version": "0.1.0",
"files": [
"クレート名_bg.wasm",
"クレート名.js",
"クレート名_bg.js",
"クレート名.d.ts"
],
"module": "クレート名.js",
"types": "クレート名.d.ts",
"sideEffects": [
"./クレート名.js",
"./snippets/*"
]
}
(おまけ)できた NPM パッケージを使うには
vite の場合、この vite-plugin-wasm というの使うのが一般的なようです[3]。
今回は web worker 上で使っているので、plugins だけでなく worker.plugins の設定も必要です(これに気付かなくて1時間くらい苦労した。npm run dev は動くので気にしてなかったら、npm build だとエラーになって困った...)。
import { defineConfig } from "vite";
import wasm from "vite-plugin-wasm";
export default defineConfig({
plugins: [wasm()],
worker: {
format: "es",
plugins: [wasm()],
},
});
webpack の場合は、上記の MDN のドキュメントを参照しましょう。
-
デフォルトなら指定しなくてもいいのでは?と思うのですが、
-Oを付けた時の方がサイズが小さくなるので、何らかの差があるみたいです。よくわかりません。ちゃんとドキュメント読めばなにか説明があるのかも... ↩︎ -
最後の
./snippets/*はどこから来たのかよくわかりません。不要なのかも。 ↩︎ -
使わなくても、ちょっとコードを書けばできる...? https://github.com/WebAssembly/esm-integration/tree/main/proposals/esm-integration#webassemblyes-module-integration ↩︎
Discussion