Agents wasm-extension-integration
Guide for integrating WebAssembly modules into browser extensions using wasm-pack, wasm-bindgen, and cross-browser loading
install
source · Clone the upstream repo
git clone https://github.com/aRustyDev/agents
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/plugins/web/browser-extension-dev/skills/wasm-extension-integration" ~/.claude/skills/arustydev-agents-wasm-extension-integration && rm -rf "$T"
manifest:
content/plugins/web/browser-extension-dev/skills/wasm-extension-integration/SKILL.mdsource content
WASM Extension Integration
Guide for integrating WebAssembly modules into browser extensions using wasm-pack, wasm-bindgen, and cross-browser loading patterns.
Overview
WASM in browser extensions enables:
- High-performance computations (cryptography, parsing, compression)
- Code reuse from Rust/C++ libraries
- Sandboxed execution environments
Build Pipeline
wasm-pack Workflow
# Install wasm-pack curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh # Build for web target wasm-pack build --target web --out-dir pkg # Build for bundler (if using webpack/vite) wasm-pack build --target bundler --out-dir pkg
Cargo.toml Configuration
[package] name = "extension-wasm" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib", "rlib"] [dependencies] wasm-bindgen = "0.2" js-sys = "0.3" web-sys = { version = "0.3", features = ["console"] } [profile.release] opt-level = "s" # Optimize for size lto = true # Link-time optimization codegen-units = 1 # Single codegen unit for better optimization strip = true # Strip debug symbols
Build Targets
| Target | Output | Use Case |
|---|---|---|
| ES modules | Direct browser loading |
| npm package | Webpack/Vite bundling |
| CommonJS | Node.js (not for extensions) |
| Global | Legacy browser support |
WXT Integration
Project Structure
extension/ ├── wasm/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── entrypoints/ │ └── background.ts └── wxt.config.ts
WXT Configuration
// wxt.config.ts import { defineConfig } from 'wxt'; export default defineConfig({ vite: () => ({ plugins: [ // WASM plugin for Vite { name: 'wasm-pack', buildStart: async () => { const { execSync } = await import('child_process'); execSync('wasm-pack build wasm --target web --out-dir ../public/wasm', { stdio: 'inherit' }); } } ], build: { target: 'esnext', rollupOptions: { output: { // Preserve WASM imports inlineDynamicImports: false } } }, optimizeDeps: { exclude: ['*.wasm'] } }) });
Cross-Browser Loading
Synchronous Instantiation (Small Modules)
// For modules < 4KB async function loadWasmSync(wasmPath: string): Promise<WebAssembly.Instance> { const response = await fetch(chrome.runtime.getURL(wasmPath)); const bytes = await response.arrayBuffer(); const module = new WebAssembly.Module(bytes); return new WebAssembly.Instance(module); }
Asynchronous Instantiation (Large Modules)
// For modules >= 4KB (required by browsers) async function loadWasmAsync(wasmPath: string): Promise<WebAssembly.Instance> { const response = await fetch(chrome.runtime.getURL(wasmPath)); if (WebAssembly.instantiateStreaming) { // Chrome, Firefox, Edge - streaming compilation const { instance } = await WebAssembly.instantiateStreaming(response); return instance; } else { // Safari fallback const bytes = await response.arrayBuffer(); const { instance } = await WebAssembly.instantiate(bytes); return instance; } }
wasm-bindgen Initialization
// Generated by wasm-pack import init, { process_data } from './pkg/extension_wasm.js'; let wasmReady = false; async function initWasm(): Promise<void> { if (wasmReady) return; const wasmUrl = chrome.runtime.getURL('pkg/extension_wasm_bg.wasm'); await init(wasmUrl); wasmReady = true; } // Usage async function processWithWasm(data: Uint8Array): Promise<Uint8Array> { await initWasm(); return process_data(data); }
Browser Compatibility
Feature Detection
function checkWasmSupport(): { basic: boolean; streaming: boolean; threads: boolean; simd: boolean; } { const basic = typeof WebAssembly !== 'undefined'; const streaming = basic && typeof WebAssembly.instantiateStreaming === 'function'; // Check for threads (SharedArrayBuffer) const threads = basic && typeof SharedArrayBuffer !== 'undefined'; // Check for SIMD const simd = basic && (() => { try { // Minimal SIMD validation bytes const bytes = new Uint8Array([ 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11 ]); return WebAssembly.validate(bytes); } catch { return false; } })(); return { basic, streaming, threads, simd }; }
Safari Considerations
Safari has stricter WASM policies:
// Safari requires explicit WASM MIME type // Ensure server sends: Content-Type: application/wasm // Safari doesn't support streaming compilation from cross-origin // Keep WASM files in extension package // Safari may require web_accessible_resources for WASM
{ "web_accessible_resources": [ { "resources": ["pkg/*.wasm", "pkg/*.js"], "matches": ["<all_urls>"] } ] }
Memory Management
Extension Memory Limits
| Context | Chrome | Firefox | Safari |
|---|---|---|---|
| Service worker | 128MB default | 512MB | 128MB |
| Content script | Tab memory | Tab memory | Tab memory |
| Popup | 128MB | 128MB | 128MB |
Memory-Efficient Patterns
// lib.rs - Minimize allocations use wasm_bindgen::prelude::*; // Reuse allocations static mut BUFFER: Vec<u8> = Vec::new(); #[wasm_bindgen] pub fn process_chunk(data: &[u8]) -> Vec<u8> { unsafe { BUFFER.clear(); BUFFER.extend_from_slice(data); // Process in place BUFFER.clone() } } // Free memory explicitly #[wasm_bindgen] pub fn free_buffer() { unsafe { BUFFER = Vec::new(); BUFFER.shrink_to_fit(); } }
Streaming Processing
// Process large data in chunks to avoid memory spikes async function processLargeData( data: ArrayBuffer, chunkSize: number = 1024 * 1024 // 1MB chunks ): Promise<ArrayBuffer> { await initWasm(); const input = new Uint8Array(data); const results: Uint8Array[] = []; for (let i = 0; i < input.length; i += chunkSize) { const chunk = input.slice(i, i + chunkSize); const processed = process_chunk(chunk); results.push(processed); } // Combine results const totalLength = results.reduce((sum, arr) => sum + arr.length, 0); const output = new Uint8Array(totalLength); let offset = 0; for (const result of results) { output.set(result, offset); offset += result.length; } // Free WASM memory free_buffer(); return output.buffer; }
Service Worker Integration
Loading WASM in Service Worker
// background.ts (service worker) let wasmModule: WebAssembly.Module | null = null; // Pre-compile on install chrome.runtime.onInstalled.addListener(async () => { const response = await fetch(chrome.runtime.getURL('pkg/module.wasm')); const bytes = await response.arrayBuffer(); wasmModule = await WebAssembly.compile(bytes); // Store compiled module reference for quick instantiation console.log('WASM module compiled'); }); // Instantiate when needed async function getWasmInstance(): Promise<WebAssembly.Instance> { if (!wasmModule) { const response = await fetch(chrome.runtime.getURL('pkg/module.wasm')); const bytes = await response.arrayBuffer(); wasmModule = await WebAssembly.compile(bytes); } return new WebAssembly.Instance(wasmModule); }
Service Worker Lifecycle
// Handle service worker termination // WASM modules are lost when worker sleeps chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === 'PROCESS_WASM') { // Re-initialize WASM if needed processWithWasm(message.data) .then(sendResponse) .catch(error => sendResponse({ error: error.message })); return true; } });
MCP Server: wasm-bindgen
The plugin includes a wasm-bindgen MCP server for build automation.
Configuration
{ "mcpServers": { "wasm-bindgen": { "command": "cargo", "args": [ "run", "--manifest-path", "${HOME}/.local/share/mcp/wasm-bindgen-mcp/Cargo.toml", "--", "--target-dir", ".output/wasm" ], "env": { "WASM_PACK_PATH": "wasm-pack", "WASM_TARGET": "web", "WASM_EXTENSION_MODE": "true" } } } }
Available Tools
| Tool | Description |
|---|---|
| Build WASM module with wasm-pack |
| Optimize WASM binary size |
| Validate WASM module |
| Inspect WASM module exports |
Usage
// AI can invoke: // wasm_build({ profile: 'release', target: 'web' }) // wasm_optimize({ input: 'module.wasm', level: 's' }) // wasm_validate({ path: 'module.wasm' })
Size Optimization
Build Flags
# Cargo.toml [profile.release] opt-level = "z" # Optimize for size (aggressive) lto = "fat" # Full LTO codegen-units = 1 panic = "abort" # No unwinding strip = true
Post-Build Optimization
# Install wasm-opt (from binaryen) brew install binaryen # Optimize WASM binary wasm-opt -Os -o output.wasm input.wasm # Or with wasm-pack wasm-pack build --release -- --features wee_alloc
wee_alloc (Smaller Allocator)
[dependencies] wee_alloc = { version = "0.4", optional = true } [features] default = ["wee_alloc"]
#[cfg(feature = "wee_alloc")] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
Common Use Cases
Cryptography
use wasm_bindgen::prelude::*; use sha2::{Sha256, Digest}; #[wasm_bindgen] pub fn hash_sha256(data: &[u8]) -> Vec<u8> { let mut hasher = Sha256::new(); hasher.update(data); hasher.finalize().to_vec() }
Data Compression
use wasm_bindgen::prelude::*; use flate2::Compression; use flate2::write::GzEncoder; use std::io::Write; #[wasm_bindgen] pub fn compress_gzip(data: &[u8]) -> Vec<u8> { let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); encoder.write_all(data).unwrap(); encoder.finish().unwrap() }
JSON Parsing (Fast)
use wasm_bindgen::prelude::*; use serde_json::Value; #[wasm_bindgen] pub fn parse_json(input: &str) -> JsValue { match serde_json::from_str::<Value>(input) { Ok(value) => serde_wasm_bindgen::to_value(&value).unwrap(), Err(e) => JsValue::from_str(&format!("Error: {}", e)) } }
Debugging
Source Maps
# Build with debug info wasm-pack build --dev # Or with custom flags RUSTFLAGS="-C debuginfo=2" wasm-pack build
Browser DevTools
- Chrome: DevTools → Sources → shows .wasm files
- Firefox: Debugger → shows WASM as text
- Safari: Limited WASM debugging
Console Logging
use web_sys::console; #[wasm_bindgen] pub fn debug_log(message: &str) { console::log_1(&message.into()); }
Quality Checklist
- WASM module builds with wasm-pack
- Cross-browser loading tested
- Memory usage within limits
- Service worker lifecycle handled
- Size optimized for extension bundle
- Error handling implemented
- Fallback for unsupported browsers