Skip to main content

Introduction

Bomboni provides comprehensive WebAssembly (WASM) support through three crates:
  • bomboni_wasm - Main utilities for JavaScript interoperability and console logging
  • bomboni_wasm_derive - Derive macros for generating TypeScript bindings
  • bomboni_wasm_core - Core utilities for TypeScript type generation
These crates work together to seamlessly integrate Rust types with JavaScript/TypeScript environments through wasm-bindgen.

When to Use WASM Features

Use Bomboni’s WASM features when you need to:
  • Expose Rust APIs to JavaScript/TypeScript - Share complex data structures between Rust and JavaScript
  • Generate TypeScript declarations automatically - Keep your TypeScript types in sync with Rust implementations
  • Build browser-based applications - Leverage Rust’s performance and safety in web applications
  • Create npm packages - Package Rust functionality for the JavaScript ecosystem

Key Features

Automatic TypeScript Generation

The Wasm derive macro automatically generates TypeScript type declarations that match your Rust types:
use serde::{Deserialize, Serialize};
use bomboni_wasm::Wasm;

#[derive(Serialize, Deserialize, Wasm)]
pub struct User {
    pub name: String,
    pub age: i32,
    #[serde(default)]
    pub email: Option<String>,
}
This generates the TypeScript declaration:
export interface User {
  name: string;
  age: number;
  email?: string | null;
}

Type Conversions

Bomboni automatically maps Rust types to their TypeScript equivalents:
Rust TypeTypeScript Type
i8, i16, i32, i64, u8, u16, u32, u64, f32, f64number
i128, u128bigint (with js feature)
String, &str, charstring
boolboolean
Option<T>T | null | undefined
Vec<T>, HashSet<T>T[]
HashMap<K, V>Map<K, V> or Record<K, V>

Wasm Trait

The Wasm trait provides conversion methods between Rust and JavaScript:
pub trait Wasm {
    type JsType: JsCast;

    fn to_js(&self) -> Result<Self::JsType, serde_wasm_bindgen::Error>
    where
        Self: serde::Serialize;

    fn from_js<T: Into<JsValue>>(js: T) -> Result<Self, serde_wasm_bindgen::Error>
    where
        Self: serde::de::DeserializeOwned;
}
Usage example:
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn process_user(js_user: JsValue) -> Result<JsValue, JsValue> {
    let user = User::from_js(js_user)?;
    // Process the user...
    Ok(user.to_js()?)
}

Browser Integration

Console Logging

Bomboni provides macros for logging to the browser console:
use bomboni_wasm::{console_log, console_error};

#[wasm_bindgen]
pub fn run_computation() {
    console_log!("Starting computation...");
    
    match perform_calculation() {
        Ok(result) => console_log!("Result: {}", result),
        Err(e) => console_error!("Error: {}", e),
    }
}
The macros expand to direct JavaScript console calls:
// Low-level console functions
use bomboni_wasm::macros::{log, error};

log("This goes to console.log");
error("This goes to console.error");

Building for the Browser

To build your WASM module for browser use:
# Add wasm32 target
rustup target add wasm32-unknown-unknown

# Install wasm-bindgen-cli
cargo install wasm-bindgen-cli

# Build your project
cargo build --target wasm32-unknown-unknown --release

# Generate JavaScript bindings
wasm-bindgen target/wasm32-unknown-unknown/release/your_crate.wasm \
  --out-dir ./pkg \
  --target web

TypeScript Declaration Generation

Accessing Generated Types

Each type with the Wasm derive macro exposes its TypeScript declaration:
#[derive(Serialize, Deserialize, Wasm)]
pub struct Point {
    x: f64,
    y: f64,
}

// Access the TypeScript declaration
const DECL: &'static str = Point::DECL;
// "export interface Point { x: number; y: number; }"

Generating .d.ts Files

When you build with wasm-bindgen, TypeScript declarations are automatically included in the generated package:
wasm-bindgen target/wasm32-unknown-unknown/release/your_crate.wasm \
  --out-dir ./pkg \
  --target bundler \
  --typescript
This creates:
  • pkg/your_crate.js - JavaScript bindings
  • pkg/your_crate.d.ts - TypeScript declarations
  • pkg/your_crate_bg.wasm - WebAssembly binary

Custom TypeScript Sections

Bomboni uses wasm-bindgen’s typescript_custom_section to inject type declarations:
#[wasm_bindgen(typescript_custom_section)]
const TS_APPEND_CONTENT: &'static str = r#"
export interface CustomType {
  field: string;
}
"#;

Complete Example

Here’s a complete example showing browser integration:
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use bomboni_wasm::{Wasm, console_log, console_error};

#[derive(Serialize, Deserialize, Wasm)]
#[wasm(into_wasm_abi, from_wasm_abi)]
pub struct Task {
    pub id: u32,
    pub title: String,
    pub completed: bool,
}

#[wasm_bindgen]
pub fn create_task(title: String) -> Result<Task, JsValue> {
    console_log!("Creating task: {}", title);
    
    Ok(Task {
        id: generate_id(),
        title,
        completed: false,
    })
}

#[wasm_bindgen]
pub fn process_tasks(tasks: Vec<Task>) -> Result<JsValue, JsValue> {
    console_log!("Processing {} tasks", tasks.len());
    
    let completed: Vec<_> = tasks
        .into_iter()
        .filter(|t| t.completed)
        .collect();
    
    completed.to_js().map_err(|e| e.into())
}
TypeScript usage:
import init, { create_task, process_tasks, Task } from './pkg/your_crate';

await init();

const task: Task = create_task("Learn WASM");
console.log(task.title); // "Learn WASM"

const tasks: Task[] = [
  { id: 1, title: "Task 1", completed: true },
  { id: 2, title: "Task 2", completed: false },
];

const completed = await process_tasks(tasks);

Next Steps

Build docs developers (and LLMs) love