Rust + WebAssembly + SolidJS なサイトを Cloudflare Pages にデプロイする
はじめに
最近 Rust に再入門して WebAssembly を作って遊んでいます。
スタックとしては、Rust + WebAssembly + SolidJS という感じなのですが、Rust 側と JavaScript 側とそれぞれビルドが必要だったりして、なんかちょっと面倒です。普通に npm run dev
したら、Rust 側も含めてホットリロードされると良いなぁ・・・ということで、今回は以下のコミットを題材に、ローカルでのビルド設定から GitHub Actions を利用したデプロイまで、見ていきたいと思います。
ローカルでの開発とビルド
プロジェクト内には、JavaScript 側のファイルと Rust 側のファイルが共存することになります。大きくなってきたら、階層を整理したり・リポジトリ分けたり・・・という話にもなるかとは思いますが、いったんルート /
直下に JavaScript 側(npm)のプロジェクトを配置しています。Rust 側(Cargo)のプロジェクトは /wasmple
配下に配置しています。
npm run build
まずは、npm scripts を使って、Rust 側と JavaScript 側のビルドが一気に出来る様にしてみましょう。こんな感じ。
"scripts": {
"build:wasm": "cd ./wasmple && cargo build --target wasm32-unknown-unknown --release",
"postbuild:wasm": "wasm-snip -o ./wasmple/target/wasm32-unknown-unknown/wasmple.wasm ./wasmple/target/wasm32-unknown-unknown/release/wasmple.wasm",
"build:js": "vite build",
"build": "run-s build:wasm build:js"
}
Rust 側は、cargo build --release
をした後に、wasm-snip
をかけています。
ビルド直後の .wasm
ファイルは、こんな小さなプロジェクトでも MB 単位になったりしているのですが、snip すると不要なコードが削除されてかなり小さくなります。Web 上でロードするものなので、小さいに越したこと無いです。他にもいくつか似たようなツールがありそうなのですが、これが一番効果がありそうだったので、いったんこれだけかけています。この辺りは、.wasm
のサイズが大きくなってきたら、もう少し研究したいところです。
build phase |
.wasm size |
---|---|
cargo build --release | 1,743,589 bytes |
wasm-snip | 50,543 bytes |
JavaScript 側は、単なる vite build
ですね。Rust 側のビルド完了後に、JavaScript 側のビルドが走る様になっています。JavaScript 内でアセットとして .wasm
ファイルを参照しているので、一緒に dist/
に書き出されますし、assetsInlineLimit
よりも小さい場合は JavaScript 内に base64 で埋め込まれたりもします。良い感じです。
npm run dev
ビルドが出来たので、ホットリロードの方も組んでみましょう。結局こんな感じになりました。
"scripts": {
"dev:wasm": "cd ./wasmple && cargo build --target wasm32-unknown-unknown",
"dev:copy": "cpx ./wasmple/target/wasm32-unknown-unknown/debug/wasmple.wasm ./wasmple/target/wasm32-unknown-unknown/",
"watch:wasm": "cd ./wasmple && cargo watch -x 'build --target wasm32-unknown-unknown'",
"watch:copy": "cpx ./wasmple/target/wasm32-unknown-unknown/debug/wasmple.wasm ./wasmple/target/wasm32-unknown-unknown/ --watch",
"watch:js": "vite --clearScreen false",
"predev": "run-s dev:wasm dev:copy",
"dev": "run-p watch:wasm watch:copy watch:js",
}
開発中は、snip しない .wasm
を利用しています。今のところはデバッガ繋ぐわけでも無いので、あんまり変わらない気もします。watch を始める時に、.wasm
側が整っていないとエラーが出てちょっとびっくりするので、一度 Rust 側のビルドを通してから watch に移行しています。
Rust 側は、cargo watch
を使って変更監視をしており、変更があり次第ビルドが走る様になっています。.wasm
が更新されると、JavaScript から参照しているパスにコピーされた後に、連鎖して vite 側のホットリロードが走ります。これで、どちらのコードを編集してもホットリロードが走るようになりました。まぁまぁ快適。
GitHub Actions でのビルドとデプロイ
ローカルでのビルドはサクッと出来る様になったので、今度は Continuous Delivery の設定をしてみます。最近割と好きな Cloudflare Pages にデプロイしたいと思うのですが、Cloudflare Pages のビルド環境には Rust が入っていない様です。ねぇ。
仕方がない(?)ので、GitHub Actions でビルドすることにします。ワークフローこんな感じです。
実行のたびに依存 crate をダウンロードしてくるところからやるのは時間がかかるので、ある程度キャッシュされるように Swatinem/rust-cache
というアクションを使っています。
また、途中で wasm-snip
のインストールを行っているのですが、(上記キャッシュの影響等で)既にバイナリが存在すると cargo install
が失敗するので、continue-on-error: true
として実行が止まらない様にしています。
Cloudflare Pages へのデプロイについては、公式のアクション cloudflare/pages-action
があるので、こちらを利用します。設定の手順については、@nwtgck さんの記事が詳しいです。感謝。
おわりに
これで、ローカルでの開発からデプロイまで整いました。push する度に Cloudflare Pages にデプロイされて、その都度プレビュー用の url が払い出されるので、記事から参照する際にも実際のサイトでデモすることが出来て良さそうです。なかなか快適なので、引き続き遊んでみることにします。
Discussion