Skip to content
Copied!
published on 2026-04-05

VitePressでWebAssemblyを使う

VitePressのコンテンツ内でWebAssembly(WASM)を利用する際の,汎用的な設計をまとめます。ここではRay Tracingに依存しない共通論点(配置,読み込み,制約)を扱います。

配置方針

WASMの生成物は,通常,グルーコード(.js / .mjs)とバイナリ(.wasm)の2ファイルで構成されます。

  • wasm-bindgen(Rust):<name>.js<name>_bg.wasm
  • Emscripten(C/C++):<name>.mjs<name>.wasm

このプロジェクトでは docs/public/wasm/ 配下に置きます。public 配下のファイルはビルド時にそのまま dist へコピーされるため,URLを安定させやすく,バイナリを srcDir 外に分離できます。

Vite開発サーバーの制約

public 配下のJSファイルは,開発サーバー上で import() できません。

js
// dev serverでは失敗する
const wasm = await import('/wasm/my_module/my_module.js')

Vite開発サーバーは,publicディレクトリ内のJSファイルへのES module import()リクエストをインターセプトして以下のエラーを返します。

Failed to load url /wasm/my_module/my_module.js ...
This file is in ../public and will be copied as-is during build without going through
the plugin transforms, and therefore should not be imported from source code.
It can only be referenced via HTML tags.

これはViteの仕様です。/* @vite-ignore */ を付けても,この制約は回避できません。なお,静的サイトとして生成後はこの制約はなく,fetch()でアクセスできます。開発サーバー固有の問題です。

解決策:fetch + Blob URL

fetch() でグルーコードのテキストを取得し,Blob URLにして import() します。

js
const response = await fetch(jsPath)
if (!response.ok) throw new Error(`HTTP ${response.status} fetching ${jsPath}`)
const jsText = await response.text()
const blob = new Blob([jsText], { type: 'text/javascript' })
const blobUrl = URL.createObjectURL(blob)
let wasmModule
try {
  wasmModule = await import(/* @vite-ignore */ blobUrl)
} finally {
  URL.revokeObjectURL(blobUrl)
}

import.meta.url問題と対処

Blob URL経由で読み込んだモジュールでは,import.meta.urlblob: URLになります。wasm-bindgen/Emscriptenは内部でWASMバイナリの相対パス解決に import.meta.url を使うため,バイナリURLを明示する必要があります。

VueコンポーネントのWASM読み込みパターン

wasm-bindgen(Rust)

wasm-bindgenが生成するJSは,デフォルトエクスポートの初期化関数にWASMバイナリのURLを引数として渡せます。wasm-bindgenの慣例では .js と同じディレクトリに _bg.wasm という名前でバイナリが置かれます。

js
onMounted(async () => {
  const jsPath = withBase('/wasm/my_module/my_module.js')

  // fetch + Blob URLでimport(Vite開発サーバーのimport制限を回避)
  const response = await fetch(jsPath)
  if (!response.ok) throw new Error(`HTTP ${response.status} fetching ${jsPath}`)
  const jsText = await response.text()
  const blob = new Blob([jsText], { type: 'text/javascript' })
  const blobUrl = URL.createObjectURL(blob)
  let wasm
  try {
    wasm = await import(/* @vite-ignore */ blobUrl)
  } finally {
    URL.revokeObjectURL(blobUrl)
  }

  // import.meta.url問題の回避:WASMバイナリURLを明示的に渡す
  const wasmBinaryUrl = new URL(
    jsPath.replace(/\.js$/, '_bg.wasm'),
    window.location.origin
  )
  await wasm.default(wasmBinaryUrl)

  // 以降はモジュールのexportを呼ぶ
  const result = wasm.my_function()
})

Emscripten(C/C++)

Emscriptenが生成するJSは,初期化関数に渡すオブジェクトのlocateFileコールバックでWASMバイナリのパスを上書きできます。

js
onMounted(async () => {
  const mjsPath = withBase('/wasm/another/another.mjs')

  // fetch + Blob URLでimport(Vite開発サーバーのimport制限を回避)
  const response = await fetch(mjsPath)
  if (!response.ok) throw new Error(`HTTP ${response.status} fetching ${mjsPath}`)
  const jsText = await response.text()
  const blob = new Blob([jsText], { type: 'text/javascript' })
  const blobUrl = URL.createObjectURL(blob)
  let wasmModule
  try {
    wasmModule = await import(/* @vite-ignore */ blobUrl)
  } finally {
    URL.revokeObjectURL(blobUrl)
  }

  // import.meta.url問題の回避:locateFileでWASMバイナリのURLを明示的に指定
  const module = await wasmModule.default({
    locateFile: (path) => withBase('/wasm/another/' + path)
  })

  // 以降はmoduleのexportを呼ぶ
  const result = module._my_function()
})

CSP

Content Security Policy(CSP)を明示する環境では,script-src blob: の許可が必要です。

プロジェクト固有の話題

raytracing_demos の構成(commonr1XX-*),PPM文字列の生成,PPMRenderer での描画など,Ray Tracing固有の実装は ja/raytracing/0 を参照してください。