Claude Skills Guide

Claude Code for wasm-bindgen Workflow Tutorial

WebAssembly (WASM) has become essential for bringing high-performance compute to web applications. When combined with Rust’s memory safety and performance, wasm-bindgen provides a powerful toolkit for building fast, interoperable modules. This guide shows you how to use Claude Code to automate and accelerate your wasm-bindgen development workflow.

Why Use Claude Code with wasm-bindgen?

The wasm-bindgen ecosystem involves multiple moving parts: Rust code, Cargo configuration, npm packages, and JavaScript/TypeScript integration. Claude Code excels at coordinating across these boundaries because it can:

The key is providing Claude Code with clear context about your project structure and build requirements.

Setting Up Your Project Structure

Before diving into code, establish a clean project structure that separates Rust and JavaScript concerns. Here’s a recommended layout:

my-wasm-project/
├── Cargo.toml
├── src/
│   └── lib.rs
├── pkg/                  # Generated by wasm-pack
├── www/                  # Frontend application
│   ├── package.json
│   ├── index.html
│   └── index.ts
└── README.md

Ask Claude Code to scaffold this structure:

“Create a new wasm-bindgen project with a Cargo.toml configured for wasm32-unknown-unknown target, a basic lib.rs with a greeting function, and a www/ directory with package.json and TypeScript setup.”

Claude Code will generate appropriate configuration files and show you how to install dependencies.

Writing Rust Functions for WASM

When writing Rust functions intended for WASM, there are specific patterns that work best. Here’s an example Rust library with functions callable from JavaScript:

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

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

#[wasm_bindgen]
pub fn process_array(arr: &[i32]) -> Vec<i32> {
    arr.iter()
        .map(|x| x * 2)
        .collect()
}

The #[wasm_bindgen] attribute is the key—it generates the JavaScript glue code. Ask Claude Code to explain specific attributes or help you convert existing Rust code to WASM-compatible versions.

Building and Publishing Your WASM Module

The build process typically uses wasm-pack. Add this to your package.json scripts:

{
  "scripts": {
    "build": "wasm-pack build --target web --out-dir pkg",
    "dev": "wasm-pack build --target web --out-dir pkg --watch",
    "test": "wasm-pack test --node"
  }
}

When building fails—and it will—Claude Code can parse the compilation errors and suggest fixes. Common issues include:

Run the build and paste errors to Claude Code for guided debugging.

Consuming WASM in JavaScript/TypeScript

Once built, your WASM module is available in the pkg/ directory. Here’s how to use it in TypeScript:

import init, { greet, fibonacci, process_array } from './pkg/my_wasm_project';

async function run() {
  await init();
  
  // Call the greet function
  const message = greet("World");
  console.log(message); // "Hello, World!"
  
  // Calculate fibonacci
  const fib = fibonacci(20);
  console.log(`Fibonacci(20) = ${fib}`); // 6765
  
  // Process arrays
  const input = new Int32Array([1, 2, 3, 4, 5]);
  const result = process_array(input);
  console.log(result); // Int32Array [2, 4, 6, 8, 10]
}

run();

Claude Code can help you write TypeScript type definitions and handle async initialization properly.

Optimizing WASM for Production

Production WASM requires attention to file size and load time. Claude Code can help you:

  1. Enable link-time optimization by adding to Cargo.toml:
[profile.release]
lto = true
opt-level = 3
  1. Use wasm-opt for further size reduction:
wasm-opt -Oz -o output.wasm input.wasm
  1. Lazy loading strategies for large modules:
async function loadComputationHeavyModule() {
  const wasm = await import('./pkg/heavy_module');
  await wasm.default.init();
  return wasm;
}

Testing Your WASM Module

Testing is critical because WASM bugs can be harder to diagnose. Create tests in Rust:

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }
}

Run tests with wasm-pack test. For integration testing from JavaScript:

import { add } from './pkg/my_project';

describe('WASM functions', () => {
  it('adds numbers correctly', () => {
    expect(add(2, 3)).toBe(5);
  });
});

Common Pitfalls and How to Avoid Them

Working with wasm-bindgen has a learning curve. Here are frequent issues:

1. Ownership and Borrowing Rust’s ownership model doesn’t map directly to JavaScript. Avoid returning borrowed references:

// Won't work - returns a borrowed reference
#[wasm_bindgen]
pub fn bad_example(s: &str) -> &str {
    s
}

// Works - returns owned data
#[wasm_bindgen]
pub fn good_example(s: &str) -> String {
    s.to_string()
}

2. Panic Handling WASM panics are hard to debug. Set up custom panic hooks:

#[wasm_bindgen]
pub fn set_panic_hook() {
    console_error_panic_hook::set_once();
}

3. JavaScript Value Conversions Some types require explicit conversion. Use the JsValue API for complex objects:

use wasm_bindgen::JsValue;

#[wasm_bindgen]
pub fn process_object(obj: &JsValue) -> Result<JsValue, JsValue> {
    // Convert JS object to serde_json::Value
    let json: serde_json::Value = obj.into_serde()?;
    // Process and convert back
    Ok(JsValue::from_serde(&json)?)
}

Integrating with Frameworks

Modern web frameworks have specific integration patterns. For React:

import { useEffect, useState } from 'react';
import init, { computeHeavyResult } from './pkg/heavy_compute';

export function useWasmCompute(input: number) {
  const [result, setResult] = useState<number | null>(null);
  
  useEffect(() => {
    init().then(() => {
      setResult(computeHeavyResult(input));
    });
  }, [input]);
  
  return result;
}

For Vue, Svelte, or other frameworks, Claude Code can generate appropriate component wrappers.

Conclusion

Claude Code dramatically accelerates wasm-bindgen development by handling the boilerplate, debugging cryptic errors, and generating integration code. The workflow becomes:

  1. Define your Rust functions with #[wasm_bindgen]
  2. Ask Claude Code to build and explain any errors
  3. Use the generated JavaScript bindings in your frontend
  4. Iterate based on performance testing

Start with small functions and progressively add complexity as you become comfortable with the Rust-to-JavaScript boundary.

Built by theluckystrike — More at zovo.one