Claude Code for WebAssembly Components: WASI, Component Model, and Cross-Language Modules — Claude Skills 360 Blog
Blog / Systems / Claude Code for WebAssembly Components: WASI, Component Model, and Cross-Language Modules
Systems

Claude Code for WebAssembly Components: WASI, Component Model, and Cross-Language Modules

Published: September 19, 2026
Read time: 9 min read
By: Claude Skills 360

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.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free