# Webassembly Component Development

> Comprehensive guide to WebAssembly component development covering WASI fundamentals, component composition patterns, language interoperability, runtime compatibility, and troubleshooting. Use when this capability is needed.

- Skill: `tomevault-io/webassembly-component-development` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/webassembly-component-development`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/webassembly-component-development/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/webassembly-component-development

---


# WebAssembly Component Development

This skill provides comprehensive expertise in developing WebAssembly components, including WASI fundamentals, composition patterns, runtime compatibility, and troubleshooting.

---

## Part 1: Core Concepts

### What is the Component Model?

The WebAssembly Component Model enables:
- Combining multiple components into a single application
- Using libraries written in one language from another language
- Building modular, reusable WebAssembly modules
- Creating component graphs with defined dependencies

### Benefits

1. **Language Interoperability:** Use the best language for each task
2. **Code Reuse:** Share components across projects
3. **Modularity:** Clear boundaries and interfaces
4. **Independent Development:** Teams can work on different components
5. **Type Safety:** WIT ensures type-safe composition

### WASI (wasip2)

The current WASI standard targets `wasm32-wasip2` (Rust 1.82+ tier-2 target):

- **Full Component Model support**
- **Native networking** via `wasi:http` and `wasi:sockets`
- **UTF-8 guaranteed** string encoding
- **Async** via `tokio` (1.50+) or `wstd`; WASIp3 (0.3) adds native async (experimental)

---

## Part 2: Component Composition

### Composition Patterns

#### 1. Simple Dependency

One component depends on another component's interface.

```wit
// component-a/wit/world.wit
package example:component-a;

interface math {
    add: func(a: s32, b: s32) -> s32;
}

world component-a {
    export math;
}
```

```wit
// component-b/wit/world.wit
package example:component-b;

world component-b {
    import example:component-a/math;
    export calculate: func(x: s32, y: s32) -> s32;
}
```

#### 2. Adapter Pattern

Adapting one interface to another for compatibility.

```wit
world adapter {
    import old-api/interface;
    export new-api/interface;
}
```

#### 3. Middleware Pattern

Components that intercept and process data between other components.

```wit
world http-logger {
    import wasi:http/incoming-handler;
    export wasi:http/outgoing-handler;
    // Logs all HTTP requests/responses
}
```

#### 4. Facade Pattern

Single component providing simplified access to multiple components.

```wit
world application-facade {
    import database/client;
    import cache/client;
    import auth/service;
    export api/handler;
}
```

### Designing Component Boundaries

**Good Boundaries:**
- Clear, well-defined responsibilities
- Minimal coupling between components
- Coarse-grained interfaces (fewer, larger operations)
- Domain-aligned (matches business/technical domains)

```wit
// Good: Clear, focused interface
interface user-service {
    create-user: func(name: string, email: string) -> result<user-id, error>;
    get-user: func(id: user-id) -> result<user, error>;
    update-user: func(id: user-id, updates: user-updates) -> result<_, error>;
}
```

**Poor Boundaries (Anti-patterns):**
- Too fine-grained (many small operations)
- Tight coupling (components know too much about each other)
- Chatty interfaces (many back-and-forth calls)

```wit
// Bad: Too fine-grained, chatty interface
interface user-service {
    set-user-name: func(id: user-id, name: string);
    get-user-name: func(id: user-id) -> string;
    set-user-email: func(id: user-id, email: string);
    get-user-email: func(id: user-id) -> string;
}
```

### Building Components

If the project has a `.wash/config.yaml`, use `wash` to build and develop:

```bash
wash build              # Build a valid WebAssembly component
wash dev                # Development with hot-reload
wash build --skip-fetch
```

`wash build` automatically:
- Compiles to the correct `wasm32-wasip2` target
- Handles WIT dependency fetching and binding generation
- Respects `.wash/config.yaml` build configuration

For non-wash projects, you can target wasip2

```bash
cargo build --target wasm32-wasip2 --release
```

### Composition Tools


#### wac (WebAssembly Composition)

A modern composition tool using a declarative `.wac` language:

```bash
# Install wac
cargo install wac-cli

# Quick plug: wire one component's exports into another's imports
wac plug component-a.wasm --plug component-b.wasm -o composed.wasm

# Or use a .wac composition file for complex graphs
wac compose composition.wac -o composed.wasm
```

#### wasmCloud Composition

wasmCloud handles runtime linking automatically via wRPC over NATS. Components are wired together using link definitions and deployed via `wash`.

### Build Configuration with .wash/config.yaml

For projects using `wash`, the `.wash/config.yaml` file in the project root configures custom build commands:

```yaml
# .wash/config.yaml
dev:
  command: make build

build:
  command: make bindgen build
```

This is useful for non-Rust projects (Go, TinyGo, etc.) or projects with custom build steps, controlling how `wash build` and `wash dev` invoke the build.

---

## Part 3: Language Interoperability

### Cross-Language Composition

**Example:** Python library used from Rust

```wit
// python-ml-lib/wit/world.wit
package ml:inference;

interface model {
    predict: func(features: list<float32>) -> list<float32>;
}

world ml-component {
    export model;
}
```

```rust
// Rust component using Python ML library
wit_bindgen::generate!({
    world: "app",
    path: "wit",
});

impl Guest for MyApp {
    fn process(data: Vec<f32>) -> Vec<f32> {
        ml::inference::model::predict(&data)
    }
}
```

### Supported Languages

- **Rust** - First-class support via `wash build`
- **Python** - Via `componentize-py`
- **JavaScript/TypeScript** - Via `componentize-js` (experimental, uses SpiderMonkey)
- **Go** - Via TinyGo 0.33+ with native WASI P2 support, plus Go Component SDK
- **C/C++** - Via WASI SDK
- **C#** - Experimental support

### Language Compatibility Considerations

**Data Types:**
- WIT provides common types that map to each language
- Complex types (records, variants, lists) are automatically converted
- Strings are always UTF-8

**Performance:**
- Cross-language calls have overhead (serialization/deserialization)
- Keep interfaces coarse-grained to minimize calls
- Pass handles/resources instead of large data when possible

**Error Handling:**
- Use WIT `result` types for cross-language error propagation
- Each language maps `result<T, E>` to its native error handling

---

## Part 4: WASI Gotchas

### 1. No Threading Support

**Problem:** Thread pool operations are unsupported on wasm32-wasip2.

**Symptoms:**
- Panic with "operation not supported on this platform"
- Libraries like `rayon` fail at runtime
- Any code using `std::thread::spawn` fails

**Solution:**
- Avoid libraries that require threading (check dependency trees)
- Use single-threaded algorithms and data structures
- Consider async/await patterns instead of threads

### 2. String/Encoding Issues

**Problem:** Unsafe assumptions about string encoding across platforms.

**Key Facts:**
- `OsStr` from wasip2 is guaranteed to be valid UTF-8
- Avoid `as_encoded_bytes()` - uses unspecified encoding
- Conversion to `str` should almost never fail on wasip2

```rust
// Good: Safe UTF-8 handling
let path_str = path.to_str().expect("wasip2 guarantees UTF-8");

// Bad: Platform-specific encoding
let bytes = path.as_os_str().as_encoded_bytes(); // Don't do this!
```

### 3. Network Capabilities

Available WASI networking interfaces:
- `wasi:http` - HTTP client and server APIs
- `wasi:sockets` - Low-level socket APIs

---

## Part 5: Async in WebAssembly Components

### Tokio on wasip2

As of tokio 1.50+, tokio supports `wasm32-wasip2` networking. This means you can use tokio's async runtime and networking APIs in WebAssembly components.

**Requirements:**
- tokio 1.50+
- Build with `RUSTFLAGS="--cfg tokio_unstable"` (required until stabilized)

```toml
# Cargo.toml
[dependencies]
tokio = { version = "1.50", features = ["rt", "net", "macros", "time", "io-util", "sync"] }
```

```bash
# Build with wash (tokio_unstable flag required)
RUSTFLAGS="--cfg tokio_unstable" wash build
```

**What works on wasip2:**
- `tokio::net::TcpListener`, `TcpStream` — full async TCP networking
- `tokio::time` — timers and delays
- `tokio::sync` — channels, mutexes, etc.
- `tokio::io` — async I/O utilities
- Single-threaded runtime (`rt` feature)

**What does NOT work yet:**
- `tokio::net::lookup_host` — DNS lookups require multithreading workaround
- `tokio::fs` — async file I/O (may work with future WASIp3 threading)
- Multi-threaded runtime (`rt-multi-thread`) — WASI has no threading support yet
- `tokio::process` — no process spawning in WASI

```rust
#[tokio::main(flavor = "current_thread")]
async fn main() {
    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
    loop {
        let (stream, _addr) = listener.accept().await.unwrap();
        tokio::spawn(async move {
            handle_connection(stream).await;
        });
    }
}
```

### wstd

[wstd](https://github.com/bytecodealliance/wstd) is a lightweight, WebAssembly-native async standard library built specifically for WASI 0.2. It provides a minimal async runtime, HTTP, networking, and other APIs purpose-built for the Wasm component model.

```toml
# Cargo.toml
[dependencies]
wstd = "0.6"
```

**When to use wstd over tokio:**
- You want a minimal, Wasm-native dependency with no `tokio_unstable` flag required
- You need `wstd::http` client/server APIs (higher-level than raw TCP)
- You want `wstd::rand` for random number generation in Wasm
- You prefer a purpose-built Wasm library over a general-purpose runtime

| Module | Purpose | Example Types |
|--------|---------|---------------|
| `wstd::http` | HTTP client and server | `Client`, `Server` |
| `wstd::io` | Async I/O abstractions | Read, Write traits |
| `wstd::net` | Async TCP networking | `TcpListener` |
| `wstd::time` | Async time interfaces | `Duration`, `Instant` |
| `wstd::rand` | Random number generation | `thread_rng()` |
| `wstd::runtime` | Async event loop | Task spawning |

```rust
#[wstd::main]
async fn main() {
    let client = wstd::http::Client::new();
    let response = client.get("https://example.com").await.unwrap();
    println!("Status: {}", response.status());
}
```

```rust
#[wstd::http_server]
async fn handle(request: wstd::http::IncomingRequest) -> wstd::http::Response {
    wstd::http::Response::new(200, "Hello, World!")
}
```

**Note:** wstd defines its own I/O traits separate from tokio — do not mix wstd and tokio in the same component.

---

## Part 6: Runtime Compatibility

### Unknown Import Errors

**The Most Common Error:**

```
Error: instantiation failed: unknown import
component name: wasi:cli/environment@0.2.0
```

**Root Causes:**
1. Runtime doesn't support WASI Preview 2
2. Runtime doesn't implement that specific interface
3. Version mismatch between component and runtime

**Diagnosis:**

```bash
# Check what your component imports
wasm-tools component wit your-component.wasm

# Try running and see what fails
wasmtime run component.wasm 2>&1 | grep "unknown import"
```

**Solutions:**

```bash
# Solution 1: Update runtime
cargo install wasmtime-cli --version 18.0.0

# Solution 2: Re-fetch WIT dependencies
wkg wit fetch

# Solution 3: Remove dependency on unsupported interface
```

### Runtime-Specific Compatibility

#### wasmtime

- Full WASI 0.2 support (stable)
- WASI 0.3 (WASIp3) support is experimental — adds native async

```bash
wasmtime run component.wasm
wasmtime serve component.wasm                          # HTTP components
wasmtime serve -S cli component.wasm                   # Enable wasi:cli (env vars, filesystem, sockets, clocks)
wasmtime serve -S cli,inherit-network component.wasm   # Full host network access within the guest
wasmtime serve -S cli,allow-ip-name-lookup component.wasm # Enable wasi:sockets/ip-name-lookup
wasmtime serve -S p3 component.wasm                    # Enable WASIp3 (experimental)
```

#### wasmCloud

- Full WASI 0.2 support in recent versions
- Custom wasmCloud interfaces (wasmcloud:*)
- `wasmcloud:wash` interface not published (use `wash build --skip-fetch` for plugins)

```bash
wash dev      # Development testing with hot-reload
```

### Version Alignment Strategies

**Strategy 1: Match Runtime Version**

```toml
[dependencies]
wasi = "=0.14.0"  # Exact version matching runtime
```

**Strategy 2: Use Conservative Interfaces**

- ✅ `wasi:cli/environment`
- ✅ `wasi:cli/stdin/stdout/stderr`
- ✅ `wasi:filesystem` (basic operations)
- ⚠️ `wasi:http` (check runtime support)
- ⚠️ `wasi:sockets` (not universally available)

**Strategy 3: Feature Detection**

```rust
#[cfg(feature = "wasi-http")]
mod http_impl { /* HTTP-specific code */ }

#[cfg(not(feature = "wasi-http"))]
mod http_impl { /* Fallback implementation */ }
```

---

## Part 7: Testing Strategies

### 1. Lightweight Host Harness

```rust
use wasmtime::{Engine, Store, component::Component};

#[cfg(test)]
mod tests {
    #[test]
    fn test_component() -> Result<()> {
        let engine = Engine::default();
        let mut store = Store::new(&engine, ());
        let component = Component::from_file(&engine, "component.wasm")?;
        // Instantiate and test...
        Ok(())
    }
}
```

### 2. Mock Components

```rust
wit_bindgen::generate!({
    world: "mock-database",
    exports: {
        "database:client/query": MockDatabase,
    }
});

struct MockDatabase;

impl database::client::Query for MockDatabase {
    fn execute(query: String) -> Result<Vec<Row>, Error> {
        Ok(vec![/* test rows */])
    }
}
```

### 3. Integration Tests with wash

```bash
wash dev  # Start development environment
curl http://localhost:8080/api/test  # Test the component's HTTP interface
```

### 4. Feature Flags for Host-Specific Code

```rust
#[cfg(target_family = "wasm")]
mod wasm_impl {
    pub fn get_data() -> Vec<u8> {
        wasi::filesystem::read("/data/file.bin")
    }
}

#[cfg(not(target_family = "wasm"))]
mod native_impl {
    pub fn get_data() -> Vec<u8> {
        vec![1, 2, 3, 4] // Test data
    }
}
```

### 5. Environment Setup

```rust
use wasmtime_wasi::{WasiCtxBuilder, Dir};

#[test]
fn test_with_filesystem() -> Result<()> {
    let test_dir = tempfile::tempdir()?;
    std::fs::write(test_dir.path().join("test.txt"), "test data")?;

    let wasi = WasiCtxBuilder::new()
        .env("TEST_MODE", "true")?
        .preopened_dir(
            Dir::open_ambient_dir(test_dir.path(), ambient_authority())?,
            "/data",
        )?
        .build();

    // Test component...
    Ok(())
}
```

---

## Part 8: Debugging

### Debugging Workflow

1. **Check target compatibility:**
   ```bash
   rustc --print target-list | grep wasi
   ```

2. **Fetch WIT dependencies:**
   ```bash
   # WIT deps are fetched automatically by wash build
   # For manual management, use the wkg CLI
   wkg wit fetch
   ```

3. **Inspect component imports:**
   ```bash
   wasm-tools component wit your-component.wasm
   ```

4. **Check for threading issues:**
   - Review dependencies for `rayon`, `tokio` (threaded), `std::thread`

### Common Error Patterns

| Error | Cause | Solution |
|-------|-------|----------|
| "operation not supported on this platform" | Threading issue | Remove threaded dependencies |
| "unknown import: wasi:cli/..." | Runtime doesn't support interface | Update runtime or remove dependency |
| WIT version mismatch | Version conflict | Re-fetch deps with `wkg wit fetch` or rebuild with `wash build` |
| Component instantiation failed | Adapter/runtime incompatibility | Check adapter version |

### Tools

```bash
# Inspect component structure
wasm-tools print component.wasm

# Extract WIT interfaces
wasm-tools component wit component.wasm

# Validate component
wasm-tools validate component.wasm

# Check component info (wasmCloud)
wash inspect component.wasm
```

---

## Part 9: Performance Optimization

### Minimize Cross-Component Calls

```rust
// Bad: Multiple fine-grained calls
let name = user_service.get_name(id)?;
let email = user_service.get_email(id)?;

// Good: Single coarse-grained call
let user = user_service.get_user(id)?;
```

### Use Streaming for Large Data

```wit
interface data-processor {
    resource stream {
        read-chunk: func() -> option<list<u8>>;
    }
    process-stream: func(input: stream) -> result<_, error>;
}
```

### Batch Operations

```rust
// Bad: Many individual calls
for item in items {
    database.insert(item)?;
}

// Good: Single batch operation
database.insert_batch(items)?;
```

---

## Part 10: Best Practices

### Documentation References

- **wasmCloud docs:** Always use https://wasmcloud.com/docs/ — do NOT use v1 docs (`/docs/v1/`), which are outdated
- **wstd docs:** https://docs.rs/wstd/latest/wstd/
- **Component Model:** https://component-model.bytecodealliance.org/

### Development

1. **Use `wash build`** when the project has `.wash/config.yaml`; otherwise use `wasm-tools component new` to produce a component
2. **Avoid Threading:** Design for single-threaded execution
3. **Use UTF-8:** Don't rely on platform-specific encodings
4. **Minimal Dependencies:** Fewer dependencies = fewer WASI compatibility issues
5. **Test Early:** Run components in target runtime during development
6. **Async options:** Use `tokio` (1.50+, with `--cfg tokio_unstable`) or `wstd` for async in Wasm components

### Component Design

1. **Single Responsibility:** Each component should have one clear purpose
2. **Coarse-Grained Interfaces:** Design for fewer, larger operations
3. **Version Your Interfaces:** Use WIT package versioning
4. **Document Dependencies:** Note which WASI interfaces your component needs

### Runtime Compatibility

1. **Check Runtime Support:** Verify WASI interfaces before using them
2. **Keep Tools Updated:** Regularly update `wash`, `wasmtime`, and Rust
3. **Pin Versions in Production:** Use exact versions for reproducibility
4. **Maintain Compatibility Matrix:** Document supported runtimes

### CI/CD

```yaml
# .github/workflows/test.yml
- name: Test with wasmtime
  run: wasmtime run component.wasm

- name: Test with wasmCloud
  run: |
    wash build
    wash dev &
    # Run integration tests
```

---

## Common Architecture Patterns

### Microservices

```wit
world api-gateway {
    import user:service/handler;
    import order:service/handler;
    import payment:service/handler;
    export http:handler/incoming;
}
```

### Plugin Architecture

```wit
world app-core {
    export plugin:host/register;
    export plugin:host/execute;
}

world plugin {
    import plugin:host/core-api;
    export plugin:interface/handler;
}
```

### Data Pipeline

```wit
world data-pipeline {
    import source:reader/stream;
    import transform:processor/apply;
    import sink:writer/write;
    export pipeline:orchestrator/run;
}
```

---

## Summary

- **Component Model** enables language-agnostic, composable WebAssembly applications
- **WASI 0.2 (wasip2)** is the current standard with full networking and UTF-8 guarantees
- **Design coarse-grained interfaces** for better performance
- **Test in target runtime** early and often
- **Use `wash build`** for wasmCloud projects - handles compatibility automatically
- **Document runtime requirements** and maintain compatibility matrices

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/cosmonic-labs) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-11 -->

