WebAssembly has moved beyond browsers. WASI (WebAssembly System Interface) gives modules portable access to filesystem, networking, and random APIs. The Component Model adds interface types and composition — you can compile a Rust library to WASM and call it from a Go host, or compose components from different languages without FFI overhead. Claude Code generates WASM components and host embeddings correctly — understanding the capability model, WIT interface definitions, and where WASM excels over native code.
When to Use WebAssembly
WASM strengths:
- Sandboxed plugins: execute untrusted code safely (capability-based security)
- Language interoperability: Rust library called from Go, Python library in Node.js
- Edge deployment: Cloudflare Workers, Fastly Compute — start in microseconds
- Portable compute: compile once, run on any architecture
WASM weaknesses:
- Higher startup time than native for long-running processes
- WASI filesystem is a capability model — not all Linux APIs available
- Debugging is harder than native code
CLAUDE.md for WASM Projects
## WebAssembly Configuration
- Rust target: wasm32-wasi (WASI) or wasm32-unknown-unknown (browser)
- Component Model: cargo-component (builds WASM + WIT interface)
- WIT definitions: wit/ directory
- Wasmtime 19+ for embedding WASM in host applications
- wasm-pack for browser/Node.js targets
- Component composition: wasm-tools compose
## Build Commands
- cargo component build --release (creates component .wasm)
- wasm-tools component wit component.wasm (inspect interface)
- wasmtime run --dir . component.wasm (test locally)
WIT Interface Definition
Define a WIT interface for an image processing component —
resize, crop, and convert format operations.
The component needs to be callable from any host language.
// wit/image-processor.wit
package company:image-processor@1.0.0;
interface types {
/// Supported image formats
enum format {
jpeg,
png,
webp,
avif,
}
/// Dimensions in pixels
record dimensions {
width: u32,
height: u32,
}
/// Crop region
record crop-region {
x: u32,
y: u32,
width: u32,
height: u32,
}
/// Processing error
variant error {
invalid-input(string),
unsupported-format(string),
encoding-failed(string),
out-of-memory,
}
}
interface processor {
use types.{format, dimensions, crop-region, error};
/// Resize an image, maintaining aspect ratio
resize: func(
input: list<u8>,
input-format: format,
target: dimensions,
output-format: format,
) -> result<list<u8>, error>;
/// Crop to specific region
crop: func(
input: list<u8>,
input-format: format,
region: crop-region,
output-format: format,
) -> result<list<u8>, error>;
/// Get image metadata without full decode
get-info: func(input: list<u8>) -> result<tuple<format, dimensions>, error>;
}
world image-component {
export processor;
}
Rust Component Implementation
Implement the image processor component in Rust.
Compile to the Component Model target.
# Cargo.toml
[package]
name = "image-processor"
version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["cdylib"] # Required for WASM component
[dependencies]
wit-bindgen = "0.24"
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp"] }
[profile.release]
opt-level = "s" # Optimize for size, not speed
lto = true # Link-time optimization reduces WASM size
// src/lib.rs
wit_bindgen::generate!({
world: "image-component",
path: "wit/image-processor.wit",
});
use exports::company::image_processor::processor::{Guest, Error, Format, Dimensions, CropRegion};
use image::{DynamicImage, ImageFormat, imageops::FilterType};
use std::io::Cursor;
struct Component;
impl Guest for Component {
fn resize(
input: Vec<u8>,
input_format: Format,
target: Dimensions,
output_format: Format,
) -> Result<Vec<u8>, Error> {
let fmt = to_image_format(input_format)?;
let img = image::load_from_memory_with_format(&input, fmt)
.map_err(|e| Error::InvalidInput(e.to_string()))?;
// Maintain aspect ratio — fit within target dimensions
let resized = img.resize(target.width, target.height, FilterType::Lanczos3);
encode_image(resized, output_format)
}
fn crop(
input: Vec<u8>,
input_format: Format,
region: CropRegion,
output_format: Format,
) -> Result<Vec<u8>, Error> {
let fmt = to_image_format(input_format)?;
let img = image::load_from_memory_with_format(&input, fmt)
.map_err(|e| Error::InvalidInput(e.to_string()))?;
let cropped = img.crop_imm(region.x, region.y, region.width, region.height);
encode_image(cropped, output_format)
}
fn get_info(input: Vec<u8>) -> Result<(Format, Dimensions), Error> {
let reader = image::io::Reader::new(Cursor::new(&input))
.with_guessed_format()
.map_err(|e| Error::InvalidInput(e.to_string()))?;
let native_fmt = reader.format().ok_or_else(|| Error::InvalidInput("unknown format".into()))?;
let (width, height) = reader.into_dimensions()
.map_err(|e| Error::InvalidInput(e.to_string()))?;
let format = from_image_format(native_fmt)
.ok_or_else(|| Error::UnsupportedFormat("format not supported".into()))?;
Ok((format, Dimensions { width, height }))
}
}
fn encode_image(img: DynamicImage, format: Format) -> Result<Vec<u8>, Error> {
let mut buf = Vec::new();
let output_fmt = to_image_format(format)?;
img.write_to(&mut Cursor::new(&mut buf), output_fmt)
.map_err(|e| Error::EncodingFailed(e.to_string()))?;
Ok(buf)
}
fn to_image_format(f: Format) -> Result<ImageFormat, Error> {
match f {
Format::Jpeg => Ok(ImageFormat::Jpeg),
Format::Png => Ok(ImageFormat::Png),
Format::Webp => Ok(ImageFormat::WebP),
Format::Avif => Err(Error::UnsupportedFormat("AVIF encode not yet supported".into())),
}
}
fn from_image_format(f: ImageFormat) -> Option<Format> {
match f {
ImageFormat::Jpeg => Some(Format::Jpeg),
ImageFormat::Png => Some(Format::Png),
ImageFormat::WebP => Some(Format::Webp),
_ => None,
}
}
export!(Component);
# Build the component
cargo component build --release
# Output: target/wasm32-wasi/release/image_processor.wasm
# Inspect the generated interface
wasm-tools component wit target/wasm32-wasi/release/image_processor.wasm
Embedding in a Wasmtime Host (Rust)
Embed the image processor component in a Rust web server.
Load the WASM once, call it per request.
// host/src/main.rs — Wasmtime host embedding WASM component
use wasmtime::{Engine, Store, Config};
use wasmtime::component::{Component, Linker, bindgen};
// Generate Rust host bindings from WIT
bindgen!({
world: "image-component",
path: "../wit/image-processor.wit",
});
struct HostState {}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Configure Wasmtime engine — shared across all requests
let mut config = Config::new();
config.wasm_component_model(true);
config.async_support(true);
let engine = Engine::new(&config)?;
// Load component once at startup (expensive)
let component = Component::from_file(&engine, "image_processor.wasm")?;
// Create linker with WASI imports
let mut linker = Linker::<HostState>::new(&engine);
wasmtime_wasi::add_to_linker_async(&mut linker)?;
// Serve HTTP requests
let app = axum::Router::new()
.route("/resize", axum::routing::post(move |body: axum::body::Bytes| async move {
let mut store = Store::new(&engine, HostState {});
// Instantiate for this request (fast — reuses compiled component)
let (processor, _) = ImageComponent::instantiate_async(&mut store, &component, &linker).await?;
let result = processor.company_image_processor_processor()
.call_resize(
&mut store,
&body,
Format::Jpeg,
&Dimensions { width: 800, height: 600 },
Format::Webp,
)
.await??;
Ok::<_, anyhow::Error>(result)
}));
axum::serve(tokio::net::TcpListener::bind("0.0.0.0:3000").await?, app).await?;
Ok(())
}
Cloudflare Workers WASM
Deploy a WASM module to Cloudflare Workers.
It needs to run at the edge — minimal startup time is critical.
// worker/src/index.ts — Cloudflare Worker with WASM
import wasm from './image_processor.wasm';
// WASM module is compiled once per Worker isolate startup
let wasmInstance: WebAssembly.Instance | null = null;
async function getWasmInstance() {
if (!wasmInstance) {
wasmInstance = await WebAssembly.instantiate(wasm, {
// Provide imports the WASM module needs
wasi_snapshot_preview1: wasiSnapshot,
});
}
return wasmInstance;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== 'POST') {
return new Response('POST only', { status: 405 });
}
const imageBuffer = await request.arrayBuffer();
const instance = await getWasmInstance();
// Call the exported resize function
const result = callResize(instance, new Uint8Array(imageBuffer), 800, 600);
return new Response(result, {
headers: { 'Content-Type': 'image/webp' },
});
}
};
For the Rust async patterns used in Wasmtime host applications, see the Rust async guide. For deploying WASM to Cloudflare Workers with Durable Objects for stateful edge compute, the edge computing guide covers those patterns. The Claude Skills 360 bundle includes WebAssembly skill sets covering Component Model, WASI, and multi-language interoperability. Start with the free tier to try WASM component generation.