Guides integration of Rust WebAssembly modules into browser extensions using wasm-pack, wasm-bindgen, WXT, Vite/Webpack bundling, and cross-browser loading patterns.
npx claudepluginhub arustydev/agents --plugin browser-extension-devThis skill uses the workspace's default tool permissions.
Guide for integrating WebAssembly modules into browser extensions using wasm-pack, wasm-bindgen, and cross-browser loading patterns.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Guide for integrating WebAssembly modules into browser extensions using wasm-pack, wasm-bindgen, and cross-browser loading patterns.
WASM in browser extensions enables:
# 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
[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
| 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 |
extension/
├── wasm/
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
├── entrypoints/
│ └── background.ts
└── wxt.config.ts
// 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']
}
})
});
// 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);
}
// 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;
}
}
// 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);
}
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 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>"]
}
]
}
| Context | Chrome | Firefox | Safari |
|---|---|---|---|
| Service worker | 128MB default | 512MB | 128MB |
| Content script | Tab memory | Tab memory | Tab memory |
| Popup | 128MB | 128MB | 128MB |
// 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();
}
}
// 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;
}
// 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);
}
// 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;
}
});
The plugin includes a wasm-bindgen MCP server for build automation.
{
"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"
}
}
}
}
| 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 |
// AI can invoke:
// wasm_build({ profile: 'release', target: 'web' })
// wasm_optimize({ input: 'module.wasm', level: 's' })
// wasm_validate({ path: 'module.wasm' })
# Cargo.toml
[profile.release]
opt-level = "z" # Optimize for size (aggressive)
lto = "fat" # Full LTO
codegen-units = 1
panic = "abort" # No unwinding
strip = true
# 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
[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;
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()
}
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()
}
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))
}
}
# Build with debug info
wasm-pack build --dev
# Or with custom flags
RUSTFLAGS="-C debuginfo=2" wasm-pack build
use web_sys::console;
#[wasm_bindgen]
pub fn debug_log(message: &str) {
console::log_1(&message.into());
}