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

Using WebAssembly in VitePress

This page summarizes the general approach to using WebAssembly (WASM) inside VitePress content. It covers the topics that are not specific to Ray Tracing: placement, loading, and constraints.

Placement

A WASM build typically produces two files: a glue script (.js / .mjs) and a binary (.wasm).

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

In this project they are placed under docs/public/wasm/. Files under public/ are copied as-is to dist/ at build time, which keeps URLs stable and allows the binaries to live outside srcDir.

Vite Dev Server Constraint

JS files under public/ cannot be import()-ed while the dev server is running.

js
// Fails on the dev server
const wasm = await import('/wasm/my_module/my_module.js')

The Vite dev server intercepts ES module import() requests for files inside the public/ directory and responds with an error:

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.

This is by design in Vite. Adding /* @vite-ignore */ does not work around this restriction. After the site is built as a static site the restriction no longer applies and fetch() access works fine. This is a dev-server-only issue.

Solution: fetch + Blob URL

Fetch the glue script as text with fetch(), create a Blob URL from it, and import() the Blob URL.

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)
}

The import.meta.url Problem and How to Fix It

A module loaded via a Blob URL has import.meta.url set to a blob: URL. Because wasm-bindgen / Emscripten use import.meta.url internally to resolve the path to the WASM binary, you must supply that URL explicitly.

WASM Loading Patterns for Vue Components

wasm-bindgen (Rust)

The JS generated by wasm-bindgen accepts the WASM binary URL as an argument to its default-export initialization function. By convention wasm-bindgen places the binary at _bg.wasm in the same directory as the .js file.

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

  // fetch + Blob URL import (works around the Vite dev server import restriction)
  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)
  }

  // Fix the import.meta.url problem: pass the WASM binary URL explicitly
  const wasmBinaryUrl = new URL(
    jsPath.replace(/\.js$/, '_bg.wasm'),
    window.location.origin
  )
  await wasm.default(wasmBinaryUrl)

  // Now call the module's exports
  const result = wasm.my_function()
})

Emscripten (C/C++)

The JS generated by Emscripten lets you override the WASM binary path via a locateFile callback on the object passed to the initialization function.

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

  // fetch + Blob URL import (works around the Vite dev server import restriction)
  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)
  }

  // Fix the import.meta.url problem: specify the WASM binary URL via locateFile
  const module = await wasmModule.default({
    locateFile: (path) => withBase('/wasm/another/' + path)
  })

  // Now call the module's exports
  const result = module._my_function()
})

CSP

In environments where Content Security Policy (CSP) is explicitly set, script-src blob: must be permitted.

Project-Specific Topics

For the raytracing_demos structure (common, r1XX-*), PPM string generation, and rendering in PPMRenderer, see en/raytracing/0.