WebAssembly と Rust を Vite プロジェクトで動かす|wasm-pack・SharedArrayBuffer・CORS ヘッダー設定

WebAssembly Rust Vite パフォーマンス フロントエンド
結論

wasm-pack build --target webvite-plugin-wasm でインポートの2ステップで動作します。SharedArrayBuffer には COOP + COEP の 2 ヘッダーが必須です。

設定手順

  1. cargo install wasm-pack で wasm-pack をインストールし、Rust の wasm ライブラリプロジェクトを作成する
  2. wasm-pack build --target web でビルドして pkg/ に TypeScript 型定義付きパッケージを生成する
  3. vite-plugin-wasmvite-plugin-top-level-await を追加して vite.config.ts に設定する
  4. 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 年に仕様変更されたものです。SharedArrayBufferwindow.crossOriginIsolated === true の場合のみ利用できます。

Cloudflare Pages での COOP/COEP ヘッダー設定

# public/_headers
/*
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Embedder-Policy: require-corp

2 つのヘッダーを両方設定しないと crossOriginIsolatedtrue になりません。

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 使用量を確認できます。