npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

wasm-bindgen-lite

v0.3.5

Published

CLI tool to build Rust crates into minimal, SIMD-optimized WASM packages with JS loaders

Downloads

857

Readme

wasm-bindgen-lite

An ultra-minimal, high-performance CLI tool and runtime for building Rust WebAssembly packages.

wasm-bindgen-lite is designed for developers who want the performance of Rust WASM without the overhead and complexity of wasm-bindgen. It provides a thin, manual-memory-management layer that is perfect for performance-critical utilities like encoders, decoders, crypto, and data processing.

Why "Lite"?

Traditional wasm-bindgen generates a lot of glue code and often hides the underlying memory management. While convenient, it can introduce overhead and make it difficult to optimize data transfers.

wasm-bindgen-lite gives you:

  • Zero-cost ABI: Uses standard extern "C" functions.
  • Manual Control: Explicit alloc and free for maximum efficiency.
  • SIMD by Default: Built-in support for SIMD detection and fallback.
  • Tiny Runtime: A minimal JS wrapper (usually < 2KB) that works in Node.js and browsers.
  • Modern ESM: Pure ESM output for modern build tools and runtimes.

Key Features

  • 🚀 SIMD Fallback: Automatically compiles two versions of your WASM (baseline and SIMD). The runtime detects support and loads the fastest one.
  • 📦 Dual Loaders: Supports standard .wasm files (best for caching) or inlined base64 (best for single-file distribution).
  • 🔄 Streaming Support: Built-in integration with TransformStream for processing data chunks on the fly.
  • 🛠️ Configurable: Simple wasm-bindgen-lite.config.json to define your exports and behavior.
  • Optimized: Integrated with wasm-opt for smallest possible binaries.

Quick Start

1. Install

npm install -D wasm-bindgen-lite

2. Prepare your Rust code

Expose alloc_bytes and free_bytes along with your functions. No wasm-bindgen dependency required!

use std::alloc::{alloc, dealloc, Layout};
use std::mem;

#[no_mangle]
pub unsafe extern "C" fn alloc_bytes(len: usize) -> *mut u8 {
    let layout = Layout::from_size_align(len, mem::align_of::<u8>()).unwrap();
    alloc(layout)
}

#[no_mangle]
pub unsafe extern "C" fn free_bytes(ptr: *mut u8, len: usize) {
    let layout = Layout::from_size_align(len, mem::align_of::<u8>()).unwrap();
    dealloc(ptr, layout);
}

#[no_mangle]
pub unsafe extern "C" fn my_transform(
    in_ptr: *const u8, in_len: usize,
    out_ptr: *mut u8, _out_len: usize,
) -> isize {
    let input = std::slice::from_raw_parts(in_ptr, in_len);
    let output = std::slice::from_raw_parts_mut(out_ptr, in_len);

    for i in 0..in_len {
        output[i] = input[i].wrapping_add(1);
    }

    in_len as isize
}

3. Build

Run the CLI to compile and generate JS loaders:

rustup target add wasm32-unknown-unknown
npx wasm-bindgen-lite build --crate . --out ./wasm-dist

4. Use in JavaScript

If you've published your output as an npm package (e.g., my-wasm-pkg), consumers can simply import it. Modern runtimes and bundlers will automatically pick the correct version (Node vs Browser) via conditional exports.

import { init, my_transform } from 'my-wasm-pkg'

await init()

const input = new Uint8Array([1, 2, 3])
const output = my_transform(input)
console.log(output) // Uint8Array([2, 3, 4])

Publishing & Automatic Export Configuration

When you build your project, wasm-bindgen-lite automatically detects your package.json and adds or updates the exports field to point to the generated artifacts.

This ensures that modern runtimes and bundlers automatically pick the correct version (Node vs Browser) via conditional exports.

Generated Exports Configuration

{
  "exports": {
    ".": {
      "browser": "./wasm-dist/browser.js",
      "node": "./wasm-dist/node.js",
      "default": "./wasm-dist/node.js"
    },
    "./inline": {
      "browser": "./wasm-dist/browser-inline.js",
      "node": "./wasm-dist/node-inline.js",
      "default": "./wasm-dist/node-inline.js"
    }
  }
}

Import Examples

Standard (Automatic Environment Detection)

Used by Vite, Webpack, and Node.js.

import { init, my_transform } from 'my-wasm-pkg'
await init()

Inline (Zero External Files)

Perfect for serverless or single-file distribution.

import { init, my_transform } from 'my-wasm-pkg/inline'
await init()

CDN (jsDelivr, unpkg, etc.)

Because the browser loader uses modern import.meta.url resolution, it works out-of-the-box on CDNs. No extra configuration is needed.

import {
  init,
  my_transform,
} from 'https://cdn.jsdelivr.net/npm/my-wasm-pkg/wasm-dist/browser.js'
await init()

Note: For advanced users, there is a wasmDelivery: { type: "jsdelivr" } config option if you want to bundle the JS locally but fetch WASM binaries from a CDN (offloading).

Initialization Modes (autoInit)

The autoInit setting controls how and when the WASM module is instantiated.

1. off (Default)

You must manually call init() and wait for it before calling any WASM functions.

import { init, process } from 'my-wasm-pkg'
await init()
const result = process(data) // Sync call

2. lazy (Automatic & Async)

The generated wrapper functions are async. They will automatically call init() on the first invocation.

import { process } from 'my-wasm-pkg'
const result = await process(data) // First call inits automatically
const result2 = await process(data2) // Subsequent calls use existing init

3. eager (Immediate)

init() is called immediately when the module is imported.

import { init, process } from 'my-wasm-pkg'
// init() is already running in the background
await init() // Ensure it's finished
const result = process(data)

Custom JS Wrapper

You can provide a js.custom file to add high-level APIs. It has access to the internal core.js utilities.

Example src/wrapper.js:

import { createTransformStream } from './core.js'
export * from './core.js' // Re-export everything from core

const decoder = new TextDecoder()

export function createLineStream() {
  // createTransformStream handles alloc/free for you
  const wasmSplit = createTransformStream('splitLines')

  return wasmSplit.readable.pipeThrough(
    new TransformStream({
      transform(chunk, controller) {
        controller.enqueue(decoder.decode(chunk))
      },
    })
  )
}

Specify it in your config:

{
  "js": { "custom": "src/wrapper.js" }
}

Configuration (wasm-bindgen-lite.config.json)

{
  "outDir": "wasm-dist",
  "artifactBaseName": "mod",
  "inline": true,
  "targets": { "baseline": true, "simd": true },
  "wasmOpt": { "mode": "auto", "args": ["-Oz"] },
  "js": {
    "emit": ["node", "browser", "inline"],
    "custom": "src/wrapper.js"
  },
  "exports": [
    {
      "abi": "my_transform",
      "name": "process",
      "return": "bytes",
      "reuseBuffer": true
    }
  ],
  "autoInit": "lazy",
  "stream": {
    "enable": true,
    "export": "process",
    "delimiter": 10
  },
  "wasmDelivery": { "type": "relative" }
}

Configuration Options

| Option | Description | Default | | ----------------------- | ------------------------------------------------------------ | ------------- | | outDir | Directory for generated files | "dist" | | artifactBaseName | Base name for .wasm files | "mod" | | inline | Whether to generate inline JS modules | false | | autoInit | "off", "lazy", "eager" | "off" | | exports | List of WASM functions to wrap | [] | | exports[].abi | Name of the extern "C" function in Rust | required | | exports[].name | Name of the exported JS function | same as abi | | exports[].return | Return type: bytes, f32, i32, u32, etc. | "bytes" | | exports[].reuseBuffer | If true, reuses the same memory buffer to reduce allocations | false | | stream.enable | Generates a createTransformStream() helper | false | | js.custom | Path to a custom JS file to include in the runtime | null |

Advanced Usage

SIMD Acceleration

wasm-bindgen-lite automatically compiles your Rust code with SIMD features enabled for the .simd.wasm target. The runtime detects support and picks the optimal binary.

SIMD-First Lazy Loading

The runtime uses a SIMD-first, lazy-baseline loading strategy:

  1. Only the SIMD artifact is fetched initially (saves bandwidth)
  2. If SIMD instantiation fails (e.g., on older browsers), the baseline is fetched as a fallback
  3. On SIMD-capable environments, the baseline is never downloaded

This means most users only download one WASM file instead of both.

Forcing a Specific Backend (for Benchmarking)

For consistent performance testing, you can force the runtime to use a specific backend:

import { init, my_transform } from 'my-wasm-pkg'

// Force baseline (non-SIMD) for comparison
await init({}, { backend: 'base' })

// Force SIMD (will throw if not supported)
await init({}, { backend: 'simd' })

// Auto-detect (default behavior)
await init({}, { backend: 'auto' })

This is useful for A/B benchmarking SIMD vs baseline performance in the same environment.

Streaming Processing

Use createTransformStream() for high-performance data pipelines:

import { init, createTransformStream } from 'my-wasm-pkg'
await init()

const response = await fetch('data.bin')
const processed = response.body.pipeThrough(createTransformStream())

CLI Reference

wasm-bindgen-lite build [options]

Options:
  --crate <path>      Path to Rust crate (default: ".")
  --out <path>        Output directory
  --release           Build in release mode (default)
  --debug             Build in debug mode
  --inline            Generate inline JS loaders
  --no-simd           Disable SIMD build
  --wasm-opt          Enable wasm-opt (default)
  --wasm-opt-args     Custom args for wasm-opt

SIMD Variant Analysis

Build a matrix of WASM variants and analyze SIMD usage:

wasm-bindgen-lite bench [options]

Options:
  --crate <path>      Path to Rust crate (default: ".")
  --clean             Clean output directory before building

The bench command builds variants based on your SIMD feature configuration and produces:

  • Variant matrix: scalar, autovec, explicit-* (per feature), explicit-all
  • SIMD analysis: Counts SIMD opcodes in each variant
  • Provenance detection: Identifies compiler-added vs explicit SIMD instructions
  • Reports: JSON and HTML reports in bench_out/

SIMD Configuration

Add this to your wasm-bindgen-lite.config.json:

{
  "simd": {
    "features": {
      "explicit-simd-encode": { "name": "encode" },
      "explicit-simd-decode": { "name": "decode" }
    },
    "allFeature": "explicit-simd"
  },
  "bench": {
    "outputDir": "bench_out"
  }
}

This generates 5 variants:

  • scalar: No SIMD (opt-level=3 only)
  • autovec: LLVM autovectorization (+simd128 but no explicit features)
  • explicit-encode: explicit-simd-encode feature enabled
  • explicit-decode: explicit-simd-decode feature enabled
  • explicit-all: explicit-simd meta-feature (all SIMD)

The provenance analysis shows how many SIMD instructions were added by:

  • Compiler: autovec.simd_ops - scalar.simd_ops
  • Explicit code: explicit.simd_ops - autovec.simd_ops

Examples

License

MIT