Guide for integrating WebAssembly modules into browser extensions using wasm-pack, wasm-bindgen, and cross-browser loading
Install to Claude Code
npx -y skills add aRustyDev/agents --skill wasm-extension-integration --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Wasm Extension Integration?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/arustydev-wasm-extension-integration)More formats (shields.io, HTML) on the badges page.
---
name: wasm-extension-integration
description: Guide for integrating WebAssembly modules into browser extensions using wasm-pack, wasm-bindgen, and cross-browser loading
tags: [browser-extension, webassembly, wasm, wasm-pack, wasm-bindgen, rust]
---
# 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
```bash
# 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
```toml
[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 |
|--------|--------|----------|
| `web` | ES modules | Direct browser loading |
| `bundler` | npm package | Webpack/Vite bundling |
| `nodejs` | CommonJS | Node.js (not for extensions) |
| `no-modules` | Global | Legacy browser support |
## WXT Integration
### Project Structure
```
extension/
├── wasm/
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
├── entrypoints/
│ └── background.ts
└── wxt.config.ts
```
### WXT Configuration
```typescript
// 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)
```typescript
// 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)
```typescript
// 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
```typescript
// 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
```typescript
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:
```typescript
// 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
```
```json
{
"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
```rust
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```json
{
"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 |
|------|-------------|
| `wasm_build` | Build WASM module with wasm-pack |
| `wasm_optimize` | Optimize WASM binary size |
| `wasm_validate` | Validate WASM module |
| `wasm_inspect` | Inspect WASM module exports |
### Usage
```typescript
// 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
```toml
# 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
```bash
# 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)
```toml
[dependencies]
wee_alloc = { version = "0.4", optional = true }
[features]
default = ["wee_alloc"]
```
```rust
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
```
## Common Use Cases
### Cryptography
```rust
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
```rust
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)
```rust
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
```bash
# Build with debug info
wasm-pack build --dev
# Or with custom flags
RUSTFLAGS="-C debuginfo=2" wasm-pack build
```
### Browser DevTools
1. Chrome: DevTools → Sources → shows .wasm files
2. Firefox: Debugger → shows WASM as text
3. Safari: Limited WASM debugging
### Console Logging
```rust
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
Scanned 6/2/2026
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!