wasm

WebAssembly integration — Rust to WASM with wasm-pack/wasm-bindgen, WASI, browser usage, server-side WASM, and performance considerations

39 stars

Best use case

wasm is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

WebAssembly integration — Rust to WASM with wasm-pack/wasm-bindgen, WASI, browser usage, server-side WASM, and performance considerations

Teams using wasm should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/wasm/SKILL.md --create-dirs "https://raw.githubusercontent.com/InugamiDev/ultrathink-oss/main/.claude/skills/wasm/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/wasm/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How wasm Compares

Feature / AgentwasmStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

WebAssembly integration — Rust to WASM with wasm-pack/wasm-bindgen, WASI, browser usage, server-side WASM, and performance considerations

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# WebAssembly Integration Specialist

## Purpose

Integrate WebAssembly modules into web and server applications for near-native performance. This skill covers compiling Rust to WASM with `wasm-pack` and `wasm-bindgen`, loading WASM in browsers and Node.js, WASI for server-side use, and understanding when WASM is (and is not) the right tool.

## When to Use WASM

| Use Case | WASM Benefit | Alternative |
|----------|-------------|-------------|
| Image/video processing | 5x-20x faster than JS | Canvas API (limited) |
| Cryptography | Constant-time, fast | SubtleCrypto API (limited algorithms) |
| Compression (zstd, brotli) | Near-native speed | Node.js native modules |
| Physics / simulation | Predictable performance | JS with typed arrays |
| Parsing (markdown, code) | Fast, portable | JS parsers (slower) |
| PDF generation | Complex layout engine | JS libs (slower, larger) |
| **Simple DOM manipulation** | No benefit, adds overhead | Use JavaScript |
| **I/O-heavy operations** | No benefit (I/O is async JS) | Use JavaScript |

## Key Patterns

### 1. Rust to WASM with wasm-pack

```bash
# Install tooling
cargo install wasm-pack
rustup target add wasm32-unknown-unknown

# Create a new WASM library
cargo init --lib my-wasm-lib
```

```toml
# Cargo.toml
[package]
name = "my-wasm-lib"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1", features = ["derive"] }
serde-wasm-bindgen = "0.6"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["console", "Performance", "Window"] }

# Optimize for size
[profile.release]
opt-level = "z"       # Optimize for size (or "s" for balanced)
lto = true            # Link-time optimization
codegen-units = 1     # Single codegen unit for better optimization
strip = true          # Strip debug symbols
```

```rust
// src/lib.rs
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};

// Exported function -- callable from JavaScript
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
    match n {
        0 => 0,
        1 => 1,
        _ => {
            let mut a: u64 = 0;
            let mut b: u64 = 1;
            for _ in 2..=n {
                let temp = b;
                b = a + b;
                a = temp;
            }
            b
        }
    }
}

// Working with strings
#[wasm_bindgen]
pub fn process_text(input: &str) -> String {
    input
        .lines()
        .filter(|line| !line.trim().is_empty())
        .map(|line| line.trim().to_uppercase())
        .collect::<Vec<_>>()
        .join("\n")
}

// Passing complex types with serde
#[derive(Serialize, Deserialize)]
pub struct ImageData {
    width: u32,
    height: u32,
    pixels: Vec<u8>,
}

#[derive(Serialize, Deserialize)]
pub struct ProcessResult {
    processed_pixels: Vec<u8>,
    elapsed_ms: f64,
}

#[wasm_bindgen]
pub fn process_image(val: JsValue) -> Result<JsValue, JsValue> {
    let data: ImageData = serde_wasm_bindgen::from_value(val)
        .map_err(|e| JsValue::from_str(&e.to_string()))?;

    let start = js_sys::Date::now();

    // Apply grayscale transformation
    let mut output = Vec::with_capacity(data.pixels.len());
    for chunk in data.pixels.chunks(4) {
        let gray = (0.299 * chunk[0] as f64
            + 0.587 * chunk[1] as f64
            + 0.114 * chunk[2] as f64) as u8;
        output.extend_from_slice(&[gray, gray, gray, chunk[3]]);
    }

    let result = ProcessResult {
        processed_pixels: output,
        elapsed_ms: js_sys::Date::now() - start,
    };

    serde_wasm_bindgen::to_value(&result)
        .map_err(|e| JsValue::from_str(&e.to_string()))
}

// Structs exported to JS
#[wasm_bindgen]
pub struct Counter {
    count: u32,
}

#[wasm_bindgen]
impl Counter {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Counter {
        Counter { count: 0 }
    }

    pub fn increment(&mut self) {
        self.count += 1;
    }

    pub fn get_count(&self) -> u32 {
        self.count
    }
}
```

```bash
# Build for browser (generates pkg/ directory with JS + TS + WASM)
wasm-pack build --target web --release

# Build for bundler (webpack, vite, etc.)
wasm-pack build --target bundler --release

# Build for Node.js
wasm-pack build --target nodejs --release
```

### 2. Browser Integration (ES Modules)

```typescript
// Using wasm-pack "web" target
async function initWasm() {
  // Dynamic import of the generated JS glue code
  const wasm = await import("./pkg/my_wasm_lib.js");

  // Initialize the WASM module (required for "web" target)
  await wasm.default();

  // Use exported functions
  const result = wasm.fibonacci(40);
  console.log(`Fibonacci(40) = ${result}`);

  // Use exported struct
  const counter = new wasm.Counter();
  counter.increment();
  console.log(`Count: ${counter.get_count()}`);

  // Free memory when done (WASM objects are not GC'd automatically)
  counter.free();
}
```

```typescript
// React component with WASM
"use client";

import { useEffect, useRef, useState } from "react";

export function WasmProcessor() {
  const wasmRef = useRef<typeof import("./pkg/my_wasm_lib.js") | null>(null);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    async function loadWasm() {
      const wasm = await import("./pkg/my_wasm_lib.js");
      await wasm.default();
      wasmRef.current = wasm;
      setReady(true);
    }
    loadWasm();
  }, []);

  const processImage = async (imageData: ImageData) => {
    if (!wasmRef.current) return;

    const result = wasmRef.current.process_image({
      width: imageData.width,
      height: imageData.height,
      pixels: Array.from(imageData.data),
    });

    return result;
  };

  return ready ? <div>WASM Ready</div> : <div>Loading WASM...</div>;
}
```

### 3. Vite / Next.js Configuration

```typescript
// vite.config.ts
import { defineConfig } from "vite";
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";

export default defineConfig({
  plugins: [wasm(), topLevelAwait()],
  optimizeDeps: {
    exclude: ["my-wasm-lib"],
  },
});
```

```typescript
// next.config.ts -- enable WASM in Next.js
const nextConfig: NextConfig = {
  webpack(config) {
    config.experiments = {
      ...config.experiments,
      asyncWebAssembly: true,
    };

    // Handle .wasm files
    config.module.rules.push({
      test: /\.wasm$/,
      type: "webassembly/async",
    });

    return config;
  },
};
```

### 4. Web Workers + WASM (Off Main Thread)

```typescript
// worker.ts -- run WASM in a Web Worker to avoid blocking UI
import init, { process_image } from "./pkg/my_wasm_lib.js";

let initialized = false;

self.onmessage = async (event: MessageEvent) => {
  if (!initialized) {
    await init();
    initialized = true;
  }

  const { type, payload } = event.data;

  switch (type) {
    case "process": {
      const result = process_image(payload);
      // Transfer ownership of ArrayBuffer for zero-copy
      self.postMessage(
        { type: "result", payload: result },
        { transfer: [result.processed_pixels.buffer] }
      );
      break;
    }
  }
};
```

```typescript
// main.ts -- use the worker
const worker = new Worker(new URL("./worker.ts", import.meta.url), {
  type: "module",
});

function processImageOffThread(imageData: ImageData): Promise<ProcessResult> {
  return new Promise((resolve) => {
    worker.onmessage = (event) => {
      if (event.data.type === "result") {
        resolve(event.data.payload);
      }
    };
    worker.postMessage({
      type: "process",
      payload: {
        width: imageData.width,
        height: imageData.height,
        pixels: Array.from(imageData.data),
      },
    });
  });
}
```

### 5. WASI (Server-Side WASM)

```typescript
// Run WASM modules with WASI system interface (Node.js 20+)
import { readFile } from "node:fs/promises";
import { WASI } from "node:wasi";

async function runWasi() {
  const wasi = new WASI({
    version: "preview1",
    args: ["my-program", "--flag"],
    env: { NODE_ENV: "production" },
    preopens: {
      "/sandbox": "./data", // Map /sandbox in WASM to ./data on host
    },
  });

  const wasmBuffer = await readFile("./my_module.wasm");
  const wasmModule = await WebAssembly.compile(wasmBuffer);
  const instance = await WebAssembly.instantiate(wasmModule, {
    wasi_snapshot_preview1: wasi.wasiImport,
  });

  wasi.start(instance);
}
```

```rust
// Rust WASI target
// Build with: cargo build --target wasm32-wasip1 --release

use std::fs;
use std::io::Read;

fn main() {
    // WASI provides filesystem, env vars, clock access
    let mut file = fs::File::open("/sandbox/input.txt").unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();

    let processed = contents.to_uppercase();
    fs::write("/sandbox/output.txt", processed).unwrap();

    println!("Processed {} bytes", contents.len());
}
```

### 6. Shared Memory with WASM

```typescript
// Direct memory access for maximum performance
async function processWithSharedMemory(data: Uint8Array) {
  const wasm = await import("./pkg/my_wasm_lib.js");
  await wasm.default();

  // Access WASM linear memory directly
  const memory = wasm.__wbg_get_memory();
  const inputPtr = wasm.allocate(data.length);

  // Copy data into WASM memory
  const inputArray = new Uint8Array(memory.buffer, inputPtr, data.length);
  inputArray.set(data);

  // Process in-place (no copy)
  const outputPtr = wasm.process_buffer(inputPtr, data.length);

  // Read result from WASM memory
  const outputArray = new Uint8Array(memory.buffer, outputPtr, data.length);
  const result = new Uint8Array(outputArray); // copy out

  // Free WASM memory
  wasm.deallocate(inputPtr, data.length);
  wasm.deallocate(outputPtr, data.length);

  return result;
}
```

### 7. Performance Benchmarking

```typescript
// Compare JS vs WASM performance
async function benchmark() {
  const wasm = await import("./pkg/my_wasm_lib.js");
  await wasm.default();

  const iterations = 1000;
  const input = 40;

  // JS implementation
  function fibonacciJS(n: number): number {
    let a = 0, b = 1;
    for (let i = 2; i <= n; i++) {
      [a, b] = [b, a + b];
    }
    return n === 0 ? 0 : b;
  }

  // Benchmark JS
  const jsStart = performance.now();
  for (let i = 0; i < iterations; i++) fibonacciJS(input);
  const jsTime = performance.now() - jsStart;

  // Benchmark WASM
  const wasmStart = performance.now();
  for (let i = 0; i < iterations; i++) wasm.fibonacci(input);
  const wasmTime = performance.now() - wasmStart;

  console.log(`JS:   ${jsTime.toFixed(2)}ms (${iterations} iterations)`);
  console.log(`WASM: ${wasmTime.toFixed(2)}ms (${iterations} iterations)`);
  console.log(`WASM is ${(jsTime / wasmTime).toFixed(1)}x faster`);
}
```

## Best Practices

1. **Profile before reaching for WASM** -- V8 is fast; WASM wins on compute-heavy, tight-loop workloads, not simple logic
2. **Minimize JS-WASM boundary crossings** -- Each call has overhead; batch work into single calls
3. **Use `SharedArrayBuffer` or typed arrays** for large data transfers instead of serializing to JSON
4. **Run WASM in a Web Worker** for heavy computation to keep the main thread responsive
5. **Optimize for size** with `opt-level = "z"`, LTO, and `wasm-opt` (from binaryen) for smaller downloads
6. **Use `wasm-bindgen`** for ergonomic Rust-JS interop rather than raw `extern "C"` bindings
7. **Lazy-load WASM modules** -- Do not include in the critical path; load on first use
8. **Cache compiled modules** -- Use `WebAssembly.compileStreaming()` for browser caching of compiled WASM
9. **Free resources explicitly** -- WASM-exported structs are not garbage-collected; call `.free()` when done
10. **Use `wasm-opt -Oz`** as a post-processing step to further reduce binary size

## Common Pitfalls

| Pitfall | Impact | Fix |
|---------|--------|-----|
| Expecting WASM to speed up I/O | No improvement (I/O is JS-bound) | Only use WASM for CPU-bound computation |
| Loading WASM on the main thread synchronously | Blocks rendering | Use `WebAssembly.compileStreaming()` or Web Workers |
| Not freeing WASM-exported objects | Memory leak | Call `.free()` on every `wasm_bindgen` struct |
| Serializing large data as JSON across boundary | Slower than JS-only | Use shared memory / typed arrays |
| Missing CORS headers for `.wasm` files | Module fails to load | Serve with `application/wasm` MIME type and proper CORS |
| Using `opt-level = 3` for WASM | Large binary | Use `opt-level = "z"` or `"s"` for smaller output |
| Calling small WASM functions in a tight JS loop | Overhead dominates | Move the loop into WASM |
| Forgetting `asyncWebAssembly` in webpack/Next.js | Build errors | Enable the experiment in webpack config |

## Examples

### Example 1: Image Processing Pipeline

```
1. User uploads image in browser
2. Decode to ImageData via Canvas API (JS)
3. Transfer pixel buffer to Web Worker (zero-copy with transfer)
4. Worker calls WASM for resize + filter (Rust image crate)
5. Worker posts result back (zero-copy transfer)
6. Render processed image to canvas (JS)

Performance: 2048x2048 grayscale — JS: 180ms, WASM: 12ms
```

### Example 2: Markdown Parser on Edge

```
1. Compile pulldown-cmark (Rust) to WASM with wasm-pack
2. Deploy to Cloudflare Workers (WASM supported natively)
3. Parse markdown to HTML at the edge — 10x faster than JS parsers
4. Cache rendered output at CDN layer

Binary size: 45KB gzipped (after wasm-opt -Oz)
```

Related Skills

ultrathink

39
from InugamiDev/ultrathink-oss

UltraThink Workflow OS — 4-layer skill mesh with persistent memory and privacy hooks for complex engineering tasks. Routes prompts through intent detection to activate the right domain skills automatically.

ultrathink_review

39
from InugamiDev/ultrathink-oss

Multi-pass code review powered by UltraThink's quality gate — checks correctness, security (OWASP), performance, readability, and project conventions in a single structured pass.

ultrathink_memory

39
from InugamiDev/ultrathink-oss

Persistent memory system for UltraThink — search, save, and recall project context, decisions, and patterns across sessions using Postgres-backed fuzzy search with synonym expansion.

ui-design

39
from InugamiDev/ultrathink-oss

Comprehensive UI design system: 230+ font pairings, 48 themes, 65 design systems, 23 design languages, 30 UX laws, 14 color systems, Swiss grid, Gestalt principles, Pencil.dev workflow. Inherits ui-ux-pro-max (99 UX rules) + impeccable-frontend-design (anti-AI-slop). Triggers on any design, UI, layout, typography, color, theme, or styling task.

Zod

39
from InugamiDev/ultrathink-oss

> TypeScript-first schema validation with static type inference.

webinar-registration-page

39
from InugamiDev/ultrathink-oss

Build a webinar or live event registration page as a self-contained HTML file with countdown timer, speaker bio, agenda, and registration form. Triggers on: "build a webinar registration page", "create a webinar sign-up page", "event registration landing page", "live training registration page", "workshop sign-up page", "create a webinar page", "build an event page", "free webinar landing page", "live demo registration page", "online event page", "create a registration page for my webinar", "build a training event page".

webhooks

39
from InugamiDev/ultrathink-oss

Webhook design patterns — delivery, retry with exponential backoff, HMAC signature verification, payload validation, idempotency keys

web-workers

39
from InugamiDev/ultrathink-oss

Offload heavy computation from the main thread using Web Workers, SharedWorkers, and Comlink — structured messaging, transferable objects, and off-main-thread architecture patterns

web-vitals

39
from InugamiDev/ultrathink-oss

Core Web Vitals monitoring (LCP, FID, CLS, INP, TTFB), measurement with web-vitals library, reporting to analytics, and optimization strategies for Next.js

web-components

39
from InugamiDev/ultrathink-oss

Native Web Components, custom elements API, Shadow DOM, HTML templates, slots, lifecycle callbacks, and framework-agnostic design patterns

vue

39
from InugamiDev/ultrathink-oss

Vue 3 Composition API, Nuxt patterns, reactivity system, component architecture, and production development practices

Vitest

39
from InugamiDev/ultrathink-oss

> Blazing fast unit testing powered by Vite — Jest-compatible API, native ESM, TypeScript.