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

unplugin-rust-wasm-pack

v0.1.0

Published

Unplugin that runs wasm-pack on virtual Cargo.toml imports for Vite, Rollup, Rolldown, and Webpack

Readme

unplugin-rust-wasm-pack

Import Rust/WASM crates in Vite, Rollup, Rolldown, and Webpack via virtual Cargo.toml imports. The plugin runs wasm-pack automatically, watches Rust sources in dev, generates TypeScript shims, and warns when required Vite peer plugins are missing.

Table of contents

How it works

  1. You import a crate like any other module: import { greet } from './rust/my-crate/Cargo.toml'.
  2. The plugin intercepts Cargo.toml specifiers, resolves the crate root, and runs wasm-pack build when sources or options change.
  3. The virtual import is redirected to wasm-pack's generated JS glue in pkg/ (client) or pkg-node/ (SSR).
  4. A Cargo.toml.d.ts shim is written next to each crate so TypeScript sees wasm-bindgen's exports.
  5. In dev, Rust file changes trigger a rebuild and Vite HMR invalidation (when enabled).
app.ts
  └── import './rust/my-crate/Cargo.toml'
        └── wasm-pack → rust/my-crate/pkg/my_crate.js + my_crate_bg.wasm

Prerequisites

  • Rust
  • rustup target add wasm32-unknown-unknown
  • wasm-pack on your PATH

Installation

pnpm add -D unplugin-rust-wasm-pack

For Vite with the default bundler target, also install the peer plugins:

pnpm add -D vite-plugin-wasm vite-plugin-top-level-await

Quick start (Vite)

// vite.config.ts
import { defineConfig } from 'vite'
import RustWasmPack from 'unplugin-rust-wasm-pack/vite'
import wasm from 'vite-plugin-wasm'
import topLevelAwait from 'vite-plugin-top-level-await'

export default defineConfig({
  plugins: [
    RustWasmPack(),
    wasm(),
    topLevelAwait(),
  ],
})
// app.ts
import { greet } from './rust/my-crate/Cargo.toml'

console.log(greet('world'))

Rust crate setup

Each WASM crate needs a [lib] section with crate-type = ["cdylib"] and typically wasm-bindgen:

[package]
name = "my-crate"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"
// src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {name}!")
}

Place the crate anywhere your bundler can resolve — e.g. rust/my-crate/ next to src/.

Import patterns

Static import — simplest; wasm is built on first resolve:

import { greet } from './rust/my-crate/Cargo.toml'

Dynamic import — code-split or defer loading (recommended for client-only crates in SSR apps):

if (!import.meta.env.SSR) {
  const { double } = await import('./rust/client-fx/Cargo.toml')
}

Web Workers — import Cargo.toml inside the worker entry; add wasm + top-level-await to config.worker.plugins in Vite.

Multiple crates — each Cargo.toml path is a separate virtual module. See examples/vite-multi-crate.

TypeScript

On first import, the plugin writes Cargo.toml.d.ts next to each crate. That shim re-exports wasm-pack's generated types from pkg/, so imports like import { greet } from './rust/my-crate/Cargo.toml' are fully typed.

Disable shim generation with typesShim: false if you maintain your own declarations or import wasm-pack output directly.

Add a tsconfig.json that includes your source and generated shims:

{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "strict": true,
    "noEmit": true
  },
  "include": ["src", "rust/**/*.d.ts"]
}

The shim is regenerated when wasm-pack rebuilds. You can gitignore **/Cargo.toml.d.ts if you prefer — it is recreated on the next dev/build.

Import option types from the package:

import type { Options, CrateConfig, CrateRuntime } from 'unplugin-rust-wasm-pack'

Required peer plugins (Vite + bundler target)

When target is bundler (default), Vite needs these plugins to load .wasm and top-level await:

| Package | Purpose | |---------|---------| | vite-plugin-wasm | Import .wasm files | | vite-plugin-top-level-await | Top-level await in glue code |

The plugin warns at build time if either is missing. Disable with checkPeers: false.

Workers: If you use config.worker.plugins, add wasm + top-level-await there too.

Configuration

import RustWasmPack from 'unplugin-rust-wasm-pack/vite'

RustWasmPack({
  crates: [{ root: './rust/my-crate', outDir: 'pkg', runtime: 'both' }],
  target: 'bundler',
  browser: false,
  devProfile: 'dev',
  buildProfile: 'release',
  profile: undefined,
  noOpt: false,
  weakRefs: false,
  referenceTypes: false,
  features: [],
  noDefaultFeatures: false,
  outDir: 'pkg',
  ssrOutDir: 'pkg-node',
  defaultRuntime: 'client',
  wasmPackArgs: [],
  cacheDir: 'node_modules/.cache/unplugin-rust-wasm-pack',
  hmr: true,
  checkPeers: true,
  typesShim: true,
})

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | crates | CrateConfig[] | — | Allowlist of crate roots with per-crate overrides. Omit to accept any resolved Cargo.toml. | | target | 'bundler' \| 'web' \| 'nodejs' | 'bundler' | wasm-pack --target for client builds. SSR always uses nodejs. | | browser | boolean | false | Pass --browser to wasm-bindgen (wasm-pack build -- --browser). | | devProfile | 'dev' \| 'profiling' | 'dev' | Dev build profile: --dev or --profiling. | | buildProfile | 'release' \| 'dev' | 'release' | Production build profile: --release or --dev. | | profile | string | — | Custom Cargo profile (wasm-pack build --profile <name>). Overrides devProfile / buildProfile. | | noOpt | boolean | false | Skip wasm-opt (wasm-pack build --no-opt). Faster builds; larger output. | | weakRefs | boolean | false | Enable JS weak references (--weak-refs). | | referenceTypes | boolean | false | Enable WebAssembly reference types (--reference-types). | | features | string \| string[] | — | Cargo features (wasm-pack build -- --features <list>). | | noDefaultFeatures | boolean | false | Disable default Cargo features (-- --no-default-features). | | outDir | string | 'pkg' | Default client output dir, relative to each crate root. | | ssrOutDir | string | 'pkg-node' | Default SSR (nodejs) output dir, relative to each crate root. | | defaultRuntime | 'client' \| 'server' \| 'both' | 'client' | Default runtime for crates without a crates entry. | | wasmPackArgs | string[] | [] | Extra args appended after plugin-managed flags (e.g. ['--out-name', 'custom']). For uncommon cargo flags, prefer features / profile options first. | | cacheDir | string | node_modules/.cache/unplugin-rust-wasm-pack | Build-cache metadata directory. | | hmr | boolean | true | Invalidate Vite modules when Rust rebuilds (Vite only). | | checkPeers | boolean | true | Warn when Vite peer plugins are missing. | | typesShim | boolean | true | Write Cargo.toml.d.ts type shims next to each crate. |

Per-crate config (CrateConfig)

| Field | Type | Default | Description | |-------|------|---------|-------------| | root | string | — | Path to the crate directory (folder containing Cargo.toml). | | outDir | string | plugin outDir | Client wasm-pack output, relative to root. | | ssrOutDir | string | plugin ssrOutDir | SSR wasm-pack output, relative to root. | | runtime | 'client' \| 'server' \| 'both' | plugin defaultRuntime | Where this crate's WASM may run. See SSR. | | devProfile | 'dev' \| 'profiling' | plugin devProfile | Per-crate dev profile override. | | buildProfile | 'release' \| 'dev' | plugin buildProfile | Per-crate production profile override. | | profile | string | plugin profile | Per-crate custom Cargo profile. | | noOpt | boolean | plugin noOpt | Per-crate wasm-opt skip. | | weakRefs | boolean | plugin weakRefs | Per-crate weak references. | | referenceTypes | boolean | plugin referenceTypes | Per-crate reference types. | | features | string \| string[] | plugin features | Per-crate Cargo features. | | noDefaultFeatures | boolean | plugin noDefaultFeatures | Per-crate no-default-features. | | wasmPackArgs | string[] | [] | Extra args appended after global wasmPackArgs. |

Dev builds use the resolved dev profile (--dev or --profiling); production uses --release or --dev per buildProfile, unless profile is set. Builds write to a staging directory first, then atomically replace pkg/ to avoid partial output.

Examples:

RustWasmPack({
  noOpt: true,
  devProfile: 'profiling',
  features: 'console_error_panic_hook',
  crates: [
    { root: './rust/heavy', features: 'simd', profile: 'release-with-debug' },
  ],
})

SSR

Each crate can run on the client, server, or both via the runtime option:

| runtime | Client bundle | SSR server | Use case | |-----------|---------------|------------|----------| | 'client' (default) | wasm-pack bundler target | empty stub | Browser-only work (canvas, WebGL, lazy UI) | | 'server' | empty stub | wasm-pack nodejs target | SEO, auth, data prep — never shipped to browser | | 'both' | bundler target | nodejs target | Shared logic (formatting, parsing) on server and client |

RustWasmPack({
  crates: [
    { root: './rust/shared', runtime: 'both' },
    { root: './rust/client-fx', runtime: 'client' },
    { root: './rust/server-meta', runtime: 'server' },
  ],
})

Server builds emit nodejs glue to pkg-node/ (override with ssrOutDir). During vite build --ssr, nodejs glue and .wasm files are copied to dist/server/wasm/<crate-name>/.

Client-only crates resolve to export {} on the server — use dynamic import if you want to defer loading:

if (!import.meta.env.SSR) {
  const { double } = await import('./rust/client-fx/Cargo.toml')
}

See examples/vite-ssr for all three modes in one app.

Rollup / Rolldown

import RustWasmPack from 'unplugin-rust-wasm-pack/rollup'
// or
import RustWasmPack from 'unplugin-rust-wasm-pack/rolldown'

No Vite peer plugins are required. Rollup 4+ has native wasm support; otherwise ensure your setup handles .wasm imports.

When target is 'bundler', the plugin still warns if wasm-loading plugins are missing from the Rollup plugin list.

Webpack

import RustWasmPack from 'unplugin-rust-wasm-pack/webpack'

export default {
  mode: 'production',
  experiments: { asyncWebAssembly: true },
  module: {
    rules: [{ test: /\.wasm$/, type: 'webassembly/async' }],
  },
  plugins: [RustWasmPack()],
}

Webpack 5 requires experiments.asyncWebAssembly: true for wasm-pack bundler target glue. The plugin warns at compile time if it is missing (checkPeers: true).

No Vite peer plugins are required. Rust file changes trigger rebuilds via webpack watch / dev server.

See examples/webpack-basic.

Monorepo / crates outside project root

Crates outside the Vite root are supported. The plugin uses fs.watch on the crate directory so changes are picked up even when Vite's watcher does not cover them.

Use crates to pin roots explicitly:

RustWasmPack({
  crates: [{ root: '../crates/compute' }],
})

See examples/vite-monorepo.

Caching and rebuilds

  • Build metadata is stored under cacheDir (default node_modules/.cache/unplugin-rust-wasm-pack).
  • wasm-pack runs only when crate sources, Cargo.toml, or relevant plugin options change.
  • Concurrent imports for the same crate share a single in-flight build.
  • Delete cacheDir and pkg/ if you suspect stale output.

API reference

Package exports

| Import path | Default export | Named exports | |-------------|----------------|---------------| | unplugin-rust-wasm-pack | unplugin instance | rustWasmPack, vite, rollup, rolldown, webpack, types | | unplugin-rust-wasm-pack/vite | Vite plugin | same as main | | unplugin-rust-wasm-pack/rollup | Rollup plugin | same as main | | unplugin-rust-wasm-pack/rolldown | Rolldown plugin | same as main | | unplugin-rust-wasm-pack/webpack | Webpack plugin | same as main |

Public types

All types are exported from every entry path. Full JSDoc is available in your editor and in dist/*.d.ts.

| Type | Description | |------|-------------| | Options | Plugin configuration | | CrateConfig | Per-crate overrides inside Options.crates | | CrateRuntime | 'client' \| 'server' \| 'both' | | WasmPackTarget | 'bundler' \| 'web' \| 'nodejs' | | DevProfile | 'dev' \| 'profiling' | | BuildProfile | 'release' \| 'dev' |

Troubleshooting

| Issue | Fix | |-------|-----| | cdylib error | Add [lib] crate-type = ["cdylib"] to Cargo.toml | | Stale output | Delete pkg/, pkg-node/, and node_modules/.cache/unplugin-rust-wasm-pack | | .wasm import fails in Vite | Add vite-plugin-wasm and vite-plugin-top-level-await | | wasm-pack not found | cargo install wasm-pack | | HMR not updating | ABI-breaking Rust changes may need a full page reload | | Types missing in IDE | Ensure rust/**/*.d.ts is in tsconfig include; trigger a build so shims are generated | | Crate not resolved | Check the import path ends with Cargo.toml; add the root to crates if using an allowlist |

Examples

| Example | What it tests | Run | |---------|---------------|-----| | vite-basic | Minimal Vite + Cargo.toml import | cd examples/vite-basic && pnpm dev | | vite-worker | WASM in a dedicated Web Worker | cd examples/vite-worker && pnpm dev | | vite-shared-worker | WASM in a SharedWorker | cd examples/vite-shared-worker && pnpm dev | | vite-lazy | Dynamic import() of Cargo.toml | cd examples/vite-lazy && pnpm dev | | vite-ssr | Client / server / both WASM runtimes | cd examples/vite-ssr && pnpm dev | | vite-monorepo | Crate outside Vite root | cd examples/vite-monorepo && pnpm dev | | vite-multi-crate | Two Cargo.toml imports in one app | cd examples/vite-multi-crate && pnpm dev | | rollup-basic | Rollup (no Vite peers) | cd examples/rollup-basic && pnpm build | | webpack-basic | Webpack 5 + asyncWebAssembly | cd examples/webpack-basic && pnpm build |

Build all examples (requires Rust + wasm-pack):

pnpm examples:build

License

MIT