SvelteKit + Cloudflare Workers で WASM の制約を回避する:ビルド時生成パターン

背景

Rust で定義した enum をフロントエンドの SvelteKit アプリケーションで共有したいというユースケースがあった。Rust 側で enum のバリアントやラベル文字列を一元管理し、wasm-pack でビルドした WASM パッケージを SvelteKit から利用する構成。

ローカル開発(Vite dev server / Node.js)では vite-plugin-wasm を使えば問題なく動作する。しかし、デプロイ先が Cloudflare Workers になると話が変わる。

課題:vite-plugin-wasm と SSR ビルドの問題

vite-plugin-wasm を使って SSR ビルドを行うと、出力されるサーバーコードに以下のような行が生成される。

var URL = globalThis.URL;

Cloudflare Workers ランタイムでは globalThis.URL が期待通りに解決されず、ReferenceError が発生した。

解決策:ビルド時生成パターン

ランタイムでは WASM を使わないアーキテクチャにする。

ビルド時に Node.js 上で WASM を実行し、結果を JSON ファイルとして出力する。SvelteKit アプリはその JSON を import するだけになる。

アーキテクチャ

Rust enum
  ↓ wasm-pack build
WASM + JS バインディング (_bg.js)
  ↓ ビルドスクリプト (Node.js)
JSON ファイル
  ↓ import
SvelteKit アプリ ($lib/wasm/index.ts)

ビルドスクリプト: scripts/generate-labels.js

以下のスクリプトを scripts/generate-labels.js に配置する。wasm-bindgen が生成する JS バインディング(*_bg.js)を直接 import し、__wbg_set_wasm 関数で WASM インスタンスを手動で接続する。

import { readFile, writeFile } from "node:fs/promises";
import { __wbg_set_wasm, get_all_labels } from "../pkg/my_crate_bg.js";

async function main() {
  const wasmBuffer = await readFile(
    new URL("../pkg/my_crate_bg.wasm", import.meta.url)
  );
  const wasmModule = await WebAssembly.instantiate(wasmBuffer, {
    // wasm-bindgen が要求する import を空で渡す
    // Rust 側から JS の関数を呼び出していない場合は空オブジェクトで十分
    "./my_crate_bg.js": {},
  });

  __wbg_set_wasm(wasmModule.instance.exports);

  const labels = get_all_labels();

  await writeFile(
    new URL("../src/lib/generated/labels.json", import.meta.url),
    JSON.stringify(labels, null, 2)
  );

  console.log("labels.json generated");
}

main();

__wbg_set_wasm は wasm-bindgen が _bg.js 内に生成する内部関数で、通常は init() 関数の中で自動的に呼ばれる。これを直接使うことで、Node.js 環境での手動インスタンス化が可能になる。

ランタイムラッパー: $lib/wasm/index.ts

ランタイム側は JSON を読み込むだけのシンプルなラッパーになる。

// src/lib/wasm/index.ts
import labels from "$lib/generated/labels.json";

export type Label = {
  value: string;
  label: string;
};

export function getAllLabels(): Label[] {
  return labels;
}

WASM への依存がビルド時に解決されるため、Workers ランタイムでの制約を一切受けない。

ビルドの統合

package.jsonbuild スクリプトにラベル生成を組み込む。

{
  "scripts": {
    "generate": "node scripts/generate-labels.js",
    "build": "npm run generate && vite build"
  }
}

まとめ

Cloudflare Workers 上で WASM を直接実行するのではなく、ビルド時に Node.js で WASM を実行して結果を JSON として出力するパターンを用意した。ランタイムからは WASM への依存がなくなるため、Workers 固有の制約を回避できる。Rust 側でデータを一元管理しつつフロントエンドと共有したい場合に有効。