👻

wasm-pack なしで Rust コードを NPM パッケージ化する

に公開

これまでのあらすじ

先月、rustwasm がアーカイブされることが Rust 公式ブログでアナウンスされていました。rustwasm は、wasm-bindgenwasm-pack をホストしている GitHub の origanization です。元となっていた Rust and WebAssembly ワーキンググループはもう活動してないので、混乱を避けるため、GitHub 上も実態に合わせよう、という経緯らしいです。

https://blog.rust-lang.org/inside-rust/2025/07/21/sunsetting-the-rustwasm-github-org/

↑のブログの時点では、「wasm-bindgen は別の場所で引き続きメンテされるが、他は未定」という状態でしたが、↓の記事にまとめられているように、wasm-pack などもメンテナが見つかっています。

https://zenn.dev/lemonadern/articles/bb8fd4715dd33f

とはいえ、wasm-pack がどれくらいメンテされるのかはわかりません。元メンテナの人が個人で引き取ったかたちですが、issues が積もっていっているのを見ると、このメンテに割ける余力がそんなにあるわけではなさそうな雰囲気です。

ということで、もしなにかの問題で wasm-pack が動かなくなった日がくることに備えて、いちおう wasm-pack がやってることを知っておいて損はないだろう。と思い立ちました。

wasm-pack なしで Rust コードを NPM パッケージ化する方法の記事

なんのことはない、すでに記事があるのでこれを読んでいきます。ここから先は、この記事に書かれてることを実際にやってみただけの内容です。

https://fourteenscrews.com/essays/look-ma-no-wasm-pack/

大きな流れ

やることは主に 4 つです。

  1. wasm32-unknown-unknown をターゲットにして cargo build
  2. 出来上がったバイナリ(*.wasm)に対して wasm-bindgen を実行
  3. その結果に対して wasm-opt を実行
  4. package.json を追加

なお、実際に試す Rust のコードは MDN の wasm-pack 入門ドキュメントに載ってたやつ(アラートを出す関数)を使いました。

https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Rust_to_Wasm

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 にしています(デフォルトなので指定しなくてもいい)。

https://wasm-bindgen.github.io/wasm-bindgen/reference/deployment.html

wasm-opt

wasm-opt は、WebAssembly のツールチェーン(Binaryen)に含まれるコマンドで、 WebAssembly バイナリを最適化するためのコマンドです。

https://github.com/WebAssembly/binaryen?tab=readme-ov-file#tools

上のブログ記事では、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]

https://github.com/Menci/vite-plugin-wasm

今回は 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 のドキュメントを参照しましょう。

脚注
  1. デフォルトなら指定しなくてもいいのでは?と思うのですが、-O を付けた時の方がサイズが小さくなるので、何らかの差があるみたいです。よくわかりません。ちゃんとドキュメント読めばなにか説明があるのかも... ↩︎

  2. 最後の ./snippets/* はどこから来たのかよくわかりません。不要なのかも。 ↩︎

  3. 使わなくても、ちょっとコードを書けばできる...? https://github.com/WebAssembly/esm-integration/tree/main/proposals/esm-integration#webassemblyes-module-integration ↩︎

Discussion