useWasm
Load and instantiate WebAssembly modules easily.
Installation
npx shadcn@latest add https://r.fiberui.com/r/hooks/use-wasm.jsonA React hook designed to easily load, instantiate, and manage WebAssembly (.wasm) modules. Provides reactive state for loading and error handling, abstracting the complexity of fetching and compiling WASM byte code.
Features
- Streaming Compilation - Uses
instantiateStreamingwhen available for optimal performance - Reactive State - Returns
loading,error, and the loadedmoduleinterface - Type Safe - Accepts generic types for strongly typed WebAssembly exports
- Memory Safe - Cleans up references safely
Basic Usage
WASM Loader State
WASM Loader State
Fetching & Compiling...
useWasm automatically handles fetching, WebAssembly compile streaming, and provides typescript safety for exports.
"use client";
import { useWasm } from "@repo/hooks/performance/use-wasm";
import { Card } from "@repo/ui/components/card";
import { Loader2, CheckCircle2, AlertCircle } from "lucide-react";
// Define the interface for the WASM module exports
interface SimpleWasmModule {
add: (a: number, b: number) => number;
}
export function Example1() {
// Note: Provide a valid URL to your .wasm file in a real project
// This example simulates the loading state handler
const { module, loading, error } =
useWasm<SimpleWasmModule>("/example-math.wasm");
return (
<Card className="mx-auto flex max-w-sm flex-col gap-4 p-6 text-center">
<h3 className="text-lg font-bold">WASM Loader State</h3>
<div className="bg-muted/30 flex min-h-[140px] flex-col items-center justify-center rounded-lg p-6 transition-all">
{loading && (
<div className="text-primary flex flex-col items-center gap-2">
<Loader2 className="h-8 w-8 animate-spin" />
<span className="text-sm font-medium">
Fetching & Compiling...
</span>
</div>
)}
{error && (
<div className="text-destructive flex flex-col items-center gap-2 text-center">
<AlertCircle className="h-8 w-8" />
<span className="text-sm font-medium">
Failed to load WebAssembly
</span>
<span className="mt-1 max-w-[200px] text-xs opacity-80">
{error.message ||
'Check network tab for details. Verify that "example-math.wasm" is served correctly.'}
</span>
</div>
)}
{!loading && !error && module && (
<div className="flex flex-col items-center gap-2 text-green-600 dark:text-green-400">
<CheckCircle2 className="h-8 w-8" />
<span className="text-sm font-bold">
WASM Module Ready
</span>
<span className="mt-1 pb-1 text-xs opacity-80">
module.add() is available
</span>
</div>
)}
</div>
<p className="text-muted-foreground mt-2 text-xs">
<code>useWasm</code> automatically handles fetching, WebAssembly
compile streaming, and provides typescript safety for exports.
</p>
</Card>
);
}
Imports Memory Binding
Imports Memory Binding
Pass JS functions into the WASM instance via `importObject`.
WASM Status: Compiling
Browser Logs
Waiting for WebAssembly calls...
"use client";
import { useWasm } from "@repo/hooks/performance/use-wasm";
import { useState, useMemo } from "react";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
import { Loader2 } from "lucide-react";
interface CounterWasm {
increment: () => void;
getCount: () => number;
}
export function Example2() {
const [jsLogs, setJsLogs] = useState<string[]>([]);
// Provide an import object to pass JavaScript functions into the WebAssembly instance
const importObject = useMemo(() => {
return {
env: {
// This function will be called FROM the WASM code
logFromWasm: (val: number) => {
setJsLogs((prev) => [
...prev.slice(-4),
`WASM logged value: ${val}`,
]);
},
},
};
}, []);
const { module, loading, error } = useWasm<CounterWasm>(
"/example-imports.wasm",
importObject,
);
return (
<Card className="mx-auto flex max-w-sm flex-col gap-6 p-6">
<div className="text-center">
<h3 className="text-lg font-bold">Imports Memory Binding</h3>
<p className="text-muted-foreground mt-1 text-xs">
Pass JS functions into the WASM instance via `importObject`.
</p>
</div>
<div className="flex flex-col gap-3">
<div className="bg-muted flex items-center justify-between rounded-md border px-3 py-2 text-sm">
<span>WASM Status:</span>
<span className="font-medium">
{loading ? (
<span className="text-primary flex items-center gap-1">
<Loader2 className="h-3 w-3 animate-spin" />{" "}
Compiling
</span>
) : error ? (
<span className="text-destructive">Error</span>
) : (
<span className="text-green-500">Ready</span>
)}
</span>
</div>
<Button
onClick={() => {
if (module) {
module.increment();
// In a real scenario, increment might call the logFromWasm import.
// For this demo mock if module doesn't exist.
} else {
// Simulation fallback since we don't have a real WASM loaded right now
importObject.env.logFromWasm(
Math.floor(Math.random() * 100),
);
}
}}
className="w-full"
isDisabled={loading}
>
Trigger Action in WASM
</Button>
</div>
<div className="flex flex-col">
<span className="text-muted-foreground mb-2 pl-1 text-xs font-bold uppercase tracking-widest">
Browser Logs
</span>
<div className="flex min-h-[120px] flex-col justify-end gap-1 overflow-hidden rounded-lg border bg-zinc-950 p-4 font-mono text-xs text-green-400 shadow-inner">
{jsLogs.length === 0 ? (
<span className="text-zinc-500">
Waiting for WebAssembly calls...
</span>
) : (
jsLogs.map((log, i) => (
<div
key={i}
className="animate-in slide-in-from-bottom-2 fade-in"
>{`> ${log}`}</div>
))
)}
</div>
</div>
</Card>
);
}
Compilation Error Handling
Compilation Error Handling
The hook guarantees a safe error state if the `.wasm` file cannot be reached, the MIME type is rejected, or the binaries fail to compile correctly.
Hook State Output
loading: true,
module: null,
error: null
"use client";
import { useWasm } from "@repo/hooks/performance/use-wasm";
import { Card } from "@repo/ui/components/card";
import { AlertTriangle, FileCode2 } from "lucide-react";
export function Example3() {
// Pointing to an invalid URL to demonstrate the error boundary logic
const { loading, error } = useWasm("/404-not-found-missing.wasm");
return (
<Card className="mx-auto flex max-w-md flex-col gap-4 border-red-500/20 bg-red-500/5 p-6">
<div className="flex items-start gap-4">
<div className="mt-1 rounded-xl bg-red-500/20 p-3 text-red-600 dark:text-red-400">
<AlertTriangle className="h-6 w-6" />
</div>
<div>
<h3 className="text-lg font-bold text-red-600 dark:text-red-400">
Compilation Error Handling
</h3>
<p className="text-foreground/80 mt-1 text-sm">
The hook guarantees a safe error state if the `.wasm`
file cannot be reached, the MIME type is rejected, or
the binaries fail to compile correctly.
</p>
</div>
</div>
<div className="bg-background mt-4 overflow-hidden rounded-md border">
<div className="bg-muted/40 flex items-center justify-between border-b px-3 py-2">
<span className="text-muted-foreground text-xs font-semibold uppercase">
Hook State Output
</span>
<FileCode2 className="text-muted-foreground h-3 w-3" />
</div>
<div className="bg-zinc-950 p-4 font-mono text-xs text-zinc-300">
<div className="flex">
<span className="mr-2 text-blue-400">loading:</span>{" "}
<span className="text-orange-300">
{loading ? "true" : "false"},
</span>
</div>
<div className="flex">
<span className="mr-2 text-blue-400">module:</span>{" "}
<span className="text-purple-400">null,</span>
</div>
<div className="flex">
<span className="mr-2 text-blue-400">error:</span>{" "}
<span className="text-zinc-100">
{error ? `Error: "${error.message}"` : "null"}
</span>
</div>
</div>
</div>
</Card>
);
}
Advanced: Memory & Strings
Advanced: Memory & Strings
Passing complex data types via `memory` exports.
"use client";
import { useWasm } from "@repo/hooks/performance/use-wasm";
import { useState } from "react";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
import { Input } from "@repo/ui/components/input";
import { Cpu, ArrowRight } from "lucide-react";
interface StringWasm {
// WASM modules often use memory pointers for strings
// This example simulates calling a module that allocates and returns memory
processString: (ptr: number, len: number) => number;
memory: WebAssembly.Memory;
}
export function Example4() {
// In a real application, you would load a compiled WASM that exports memory and string processing functions
const { module, loading } = useWasm<StringWasm>("/example-string.wasm");
const [input, setInput] = useState("Hello WebAssembly");
const [output, setOutput] = useState("");
const [isProcessing, setIsProcessing] = useState(false);
const handleProcess = () => {
setIsProcessing(true);
// Simulating the complexity of passing strings to WASM
setTimeout(() => {
if (module && module.memory) {
try {
// 1. Encode string to UTF-8
const encoder = new TextEncoder();
const bytes = encoder.encode(input);
// 2. Allocate memory in WASM (pseudo-code, depends on WASM implementation)
// const ptr = module.alloc(bytes.length);
// 3. Write to memory
// new Uint8Array(module.memory.buffer, ptr, bytes.length).set(bytes);
// 4. Call WASM function
// const resultPtr = module.processString(ptr, bytes.length);
// 5. Read result from memory
// const decoder = new TextDecoder();
// const resultBytes = new Uint8Array(module.memory.buffer, resultPtr, someLen);
// setOutput(decoder.decode(resultBytes));
setOutput(
`[WASM Processed]: ${input.toUpperCase()} (Simulated)`,
);
} catch (e) {
setOutput("WASM memory interacting failed.");
}
} else {
// Fallback simulation for the docs UI since we don't have a real .wasm file here
setOutput(`[Processed]: ${input.split("").reverse().join("")}`);
}
setIsProcessing(false);
}, 400);
};
return (
<Card className="mx-auto flex w-full max-w-md flex-col gap-6 p-6">
<div className="flex items-center gap-3 border-b pb-4">
<div className="bg-primary/10 text-primary rounded-lg p-2">
<Cpu className="h-5 w-5" />
</div>
<div>
<h3 className="font-bold">Advanced: Memory & Strings</h3>
<p className="text-muted-foreground mt-0.5 text-xs">
Passing complex data types via `memory` exports.
</p>
</div>
</div>
<div className="flex flex-col gap-3">
<Input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Enter string to process..."
/>
<Button
onClick={handleProcess}
isDisabled={loading || isProcessing}
className="w-full"
>
{isProcessing ? "Processing in WASM..." : "Process Data"}
</Button>
</div>
{output && (
<div className="animate-in fade-in zoom-in-95 mt-2 flex flex-col gap-2">
<div className="text-muted-foreground flex items-center text-xs font-bold uppercase tracking-wider">
<ArrowRight className="mr-1 h-3 w-3" />
Output Result
</div>
<div className="break-all rounded-md border bg-zinc-100 p-3 font-mono text-sm dark:bg-zinc-900">
{output}
</div>
</div>
)}
</Card>
);
}
API Reference
Hook Signature
function useWasm<T>(
url: string,
importObject?: WebAssembly.Imports,
): WasmState<T>;Parameters
| Parameter | Type | Description |
|---|---|---|
url | string | URL to the .wasm file |
importObject | WebAssembly.Imports | (Optional) Functions or values to pass into the module |
Return Value
| Property | Type | Description |
|---|---|---|
module | T | null | The instantiated WebAssembly module exports |
loading | boolean | true while the module is being fetched/compiled |
error | Error | null | Standard error object if instantiation fails |
Hook Source Code
import { useState, useEffect } from "react";
interface WasmState<T> {
module: T | null;
loading: boolean;
error: Error | null;
}
/**
* Load and instantiate a WebAssembly module.
*
* @param url The URL of the `.wasm` file to load.
* @param importObject Optional import object for the WebAssembly instance.
*/
export function useWasm<T = any>(
url: string,
importObject?: WebAssembly.Imports,
): WasmState<T> {
const [state, setState] = useState<WasmState<T>>({
module: null,
loading: true,
error: null,
});
useEffect(() => {
let isMounted = true;
const loadWasm = async () => {
setState((prev) => ({ ...prev, loading: true, error: null }));
try {
// Determine if WebAssembly.instantiateStreaming is supported
if ("instantiateStreaming" in WebAssembly) {
const response = await fetch(url);
const { instance } = await WebAssembly.instantiateStreaming(
response,
importObject,
);
if (isMounted) {
setState({
module: instance.exports as unknown as T,
loading: false,
error: null,
});
}
} else {
// Fallback to WebAssembly.instantiate
const response = await fetch(url);
const buffer = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(
buffer,
importObject,
);
if (isMounted) {
setState({
module: instance.exports as unknown as T,
loading: false,
error: null,
});
}
}
} catch (error) {
if (isMounted) {
setState({
module: null,
loading: false,
error:
error instanceof Error
? error
: new Error(String(error)),
});
}
}
};
loadWasm();
return () => {
isMounted = false;
};
}, [url, importObject]);
return state;
}