Skill
Community

WASM Extension Integration

Install
1
Install the plugin
$
npx claudepluginhub arustydev/agents --plugin browser-extension-dev

Want just this skill?

Then install: npx claudepluginhub u/[userId]/[slug]

Description

Guide for integrating WebAssembly modules into browser extensions using wasm-pack, wasm-bindgen, and cross-browser loading patterns.

Tool Access

This skill uses the workspace's default tool permissions.

Skill 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

TargetOutputUse Case
webES modulesDirect browser loading
bundlernpm packageWebpack/Vite bundling
nodejsCommonJSNode.js (not for extensions)
no-modulesGlobalLegacy 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

ContextChromeFirefoxSafari
Service worker128MB default512MB128MB
Content scriptTab memoryTab memoryTab memory
Popup128MB128MB128MB

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

ToolDescription
wasm_buildBuild WASM module with wasm-pack
wasm_optimizeOptimize WASM binary size
wasm_validateValidate WASM module
wasm_inspectInspect 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

  1. Chrome: DevTools → Sources → shows .wasm files
  2. Firefox: Debugger → shows WASM as text
  3. 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
Stats
Stars6
Forks2
Last CommitMar 18, 2026

Similar Skills