WebAssembly と Rust を Vite プロジェクトで動かす|wasm-pack・SharedArrayBuffer・CORS ヘッダー設定
wasm-pack build --target web → vite-plugin-wasm でインポートの2ステップで動作します。SharedArrayBuffer には COOP + COEP の 2 ヘッダーが必須です。
設定手順
cargo install wasm-packで wasm-pack をインストールし、Rust の wasm ライブラリプロジェクトを作成するwasm-pack build --target webでビルドしてpkg/に TypeScript 型定義付きパッケージを生成するvite-plugin-wasmとvite-plugin-top-level-awaitを追加してvite.config.tsに設定するSharedArrayBufferが必要な場合は COOP・COEP ヘッダーを設定する
Rust プロジェクトの準備
# wasm-pack のインストール
cargo install wasm-pack
# 新規 Rust ライブラリプロジェクト作成
cargo new --lib my-wasm-lib
cd my-wasm-lib
# Cargo.toml
[package]
name = "my-wasm-lib"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn heavy_computation(n: u32) -> u32 {
// CPU 集中処理の例:n 番目のフィボナッチ数を計算
fibonacci(n)
}
fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
# WebAssembly にビルド(pkg/ ディレクトリに出力)
wasm-pack build --target web --out-dir pkg
pkg/ には .wasm ファイルと TypeScript の型定義ファイル(.d.ts)が生成されます。
Vite の設定
pnpm add -D vite-plugin-wasm vite-plugin-top-level-await
// vite.config.ts
import { defineConfig } from 'vite';
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';
export default defineConfig({
plugins: [
wasm(),
topLevelAwait(), // wasm の初期化に top-level await が必要
],
});
TypeScript からの呼び出し
// src/main.ts
import init, { heavy_computation } from '../my-wasm-lib/pkg/my_wasm_lib.js';
async function main() {
// wasm を初期化(初回のみ必要)
await init();
// Rust の関数を TypeScript から呼び出す
const result = heavy_computation(30);
console.log(`Fibonacci(30) = ${result}`); // 832040
}
main();
SharedArrayBuffer と Cross-Origin Isolation
マルチスレッド処理(Web Workers + SharedArrayBuffer によるメモリ共有)を使う場合、Cross-Origin Isolated 環境が必要です。
なぜ SharedArrayBuffer が undefined になるか
// ❌ Cross-Origin Isolated でない場合
console.log(window.crossOriginIsolated); // false
console.log(typeof SharedArrayBuffer); // "undefined"
これは Spectre 脆弱性の対策として 2021 年に仕様変更されたものです。SharedArrayBuffer は window.crossOriginIsolated === true の場合のみ利用できます。
Cloudflare Pages での COOP/COEP ヘッダー設定
# public/_headers
/*
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
2 つのヘッダーを両方設定しないと crossOriginIsolated は true になりません。
COEP のサードパーティリソースへの影響
Cross-Origin-Embedder-Policy: require-corp を有効にすると、クロスオリジンのリソース(Google Fonts・CDN の画像等)は Cross-Origin-Resource-Policy: cross-origin ヘッダーを返さない限りブロックされます。
<!-- ❌ COEP 有効後にブロックされる例 -->
<img src="https://external-cdn.example.com/image.png">
<!-- Cross-Origin-Resource-Policy ヘッダーがない場合ブロック -->
<!-- ✅ crossorigin 属性を追加することで回避できる場合もある -->
<img src="https://external-cdn.example.com/image.png" crossorigin="anonymous">
Google Fonts や Google Analytics は Cross-Origin-Resource-Policy ヘッダーを返さないため、require-corp と共存させるには credentialless モードを使うか、関連リソースをセルフホストする必要があります。
Failure Boundary:wasm の GPU メモリとメモリ管理
wasm-bindgen で生成された Rust のオブジェクトは JavaScript の GC 管理外です。free() メソッドを明示的に呼ばないとメモリリークします。
// wasm-bindgen が生成したクラスの場合
const myWasmObj = new MyWasmStruct();
try {
myWasmObj.process();
} finally {
myWasmObj.free(); // 必ず解放する
}
大量データを処理する場合は free() の漏れがないか定期的に確認してください。Chrome DevTools の Memory タブでスナップショットを取ると wasm の heap 使用量を確認できます。