wasm-pack & Bundling
Build and ship npm-consumable WASM packages with wasm-pack and frontend bundlers.
Recipe
cargo install wasm-pack
wasm-pack build --target bundler --releasepkg/
mylib.js # JS glue
mylib_bg.wasm # WASM binary
mylib.d.ts # TypeScript types
package.json # npm metadata
When to reach for this: Publishing Rust WASM to npm for React, Vite, or webpack consumers.
Working Example
// package.json (root frontend project)
{
"dependencies": {
"mylib": "file:../mylib/pkg"
}
}// src/worker.ts
import init, { process_data } from "mylib";
export async function run(input: Uint8Array) {
await init();
return process_data(input);
}# Rust crate Cargo.toml
[package]
name = "mylib"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"What this demonstrates:
wasm-packemits a complete npm package inpkg/--target bundlersuits webpack/vite tree-shakinginit()must run before calling exported functions- Local
file:path links during development
Deep Dive
wasm-pack Targets
| Target | Consumer |
|---|---|
web | Browser <script type="module"> |
bundler | webpack, vite, parcel |
nodejs | Node.js with fs access |
deno | Deno imports |
Vite Configuration
// vite.config.ts
export default {
optimizeDeps: {
exclude: ["mylib"],
},
};Exclude WASM packages from pre-bundling so init() loads correctly.
CI Build
- run: wasm-pack build --target bundler --release
- run: cd pkg && npm publish --access publicGotchas
- Forgot
await init()- Functions fail with opaque errors. Fix: Always await init at app startup. - Wrong target -
webvsbundlerimport paths differ. Fix: Match target to your bundler docs. - Huge pkg folder - Debug builds bloat npm tarballs. Fix:
--releaseand addpkg/to.gitignoreif rebuilding in CI. - MIME type on static servers -
.wasmserved asapplication/octet-streamfails. Fix: Configureapplication/wasm. - Top-level await - Older bundlers choke on WASM init. Fix: Dynamic
import()inside async functions.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
cargo component build | Component Model packages | Classic browser wasm-bindgen apps |
Manual wasm-bindgen-cli | Custom build pipelines | You want npm metadata generated |
| CDN script tags | Quick prototypes | npm-based frontend apps |
FAQs
Monorepo layout?
Rust crate in crates/mylib, wasm-pack build outputs to crates/mylib/pkg, frontend depends via workspace protocol.
TypeScript types?
wasm-pack generates .d.ts automatically. Commit or regenerate in CI.
Workspaces and hoisting?
Hoisting may break relative .wasm paths. Pin WASM pkg as direct dependency.
SSR frameworks?
WASM runs client-side only. Gate imports with typeof window !== "undefined".
Webpack async WASM?
Enable experiments.asyncWebAssembly in webpack 5 config.
Version alignment?
Keep wasm-bindgen crate and CLI versions matched per wasm-pack release notes.
Private npm registry?
Publish pkg/ to your registry; consumers install like any npm package.
Feature flags?
Pass -- --features foo to forward Cargo features through wasm-pack.
Optimize with wasm-opt?
wasm-pack can run binaryen optimizations. Enable in wasm-pack config for smaller output.
Testing the pkg?
Use wasm-pack test --headless with chromedriver or playwright.
Related
- wasm-bindgen - JS/Rust bridge
- WebAssembly Basics - build targets
- Performance & Size - binary optimization
- Frontend Frameworks (Leptos / Yew) - framework integration
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.