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

vite-plugin-native-rust

v0.4.0

Published

Import Rust directly in Vite SSR server code — compiles napi-rs crates to native addons with caching, types, and zero-config deploys.

Readme

vite-plugin-native-rust

Experimental — 0.3. Still pre-1.0; the API may shift between minor releases, with any change called out in the changelog.

Import Rust directly in Vite SSR server code:

// something.server.ts — server-only (never reachable from the client bundle)
import { hashChain } from "./native/src/lib.rs";

export const digest = await hashChain(6_000_000);

The plugin compiles the enclosing napi-rs crate into a native .node addon for the current platform, content-hash caches it, generates named-export JS that loads the binary at runtime, and mirrors napi's generated types next to your .rs file. The work runs on real threads off the Node event loop, and the compiled addon travels with your vite build output — including onto serverless platforms like Vercel, with zero code changes.

Rust modules are server-only: importing one from code that can reach the client bundle is a build error by design.

Why

For a CPU-bound server loader (a 6,000,000-iteration SHA-256 hash chain), moving the hot path from synchronous JS to an #[napi] async fn measured ~2.9× faster single-request and ~10.7× faster at 5-way concurrency locally, and ~7× faster latency with ~7× lower active-CPU cost on Vercel — same digest either way. Full methodology and the honest serverless caveats are in the benchmarks.

Requirements

  • Node.js >= 20
  • Vite >= 6 (peer dependency)
  • @napi-rs/cli >= 3 (peer dependency — you install it)
  • A Rust toolchain (cargo on PATH; install from https://rustup.rs)
  • macOS or Linux — Windows support is not planned

Supported frameworks: React Router v7 / v8, vanilla Vite SSR, SvelteKit, Astro, TanStack Start, and Qwik City out of the box (Vite 8 / rolldown validated); Nuxt and SolidStart via the vite-plugin-native-rust/nitro helpers. Next.js and Remix v3 don't run Vite — see the repo's examples for the plugin-free pattern with the same crate.

Quickstart

# 1. scaffold a ready-to-build crate (optional but fastest)
npm create native-rust native

# 2. install the plugin and the napi CLI it drives
npm i -D vite-plugin-native-rust @napi-rs/cli
// 3. vite.config.ts — add the plugin before your framework plugin
import { rustPlugin } from "vite-plugin-native-rust";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [rustPlugin(), /* ...your other plugins */],
});
// 4. tsconfig.json compilerOptions — resolve the generated .d.rs.ts types
"allowArbitraryExtensions": true
// 5. import from a server-only module
import { add, sumTo } from "./native/src/lib.rs";

const five = add(2, 3);            // sync, on the main thread
const total = await sumTo(1_000);  // async, off the event loop

A cold cargo build (~30s) starts as soon as the dev server boots for crates the plugin has seen before (see prewarm below); otherwise the first dev request that touches the crate triggers it. Every later request hits the cached addon.

Options

rustPlugin(options?) — every option is optional; defaults reproduce the zero-argument behavior.

| Option | Type | Default | Description | | --- | --- | --- | --- | | cacheDir | string | node_modules/.cache/vite-rust | Where compiled .node addons are cached. Relative paths resolve against the Vite root. | | profile | 'debug' \| 'release' | auto | Force a build profile. Auto = debug in dev/watch, release in build. | | napiArgs | string[] | [] | Extra arguments appended to napi build. | | generateCratePackageJson | boolean | true | Write a package.json with napi.binaryName when the crate lacks one. false errors instead of mutating your crate. | | emitTypes | boolean | true | Mirror napi's generated types to a .d.rs.ts beside the imported .rs. | | logLevel | 'silent' \| 'info' | 'info' | 'silent' suppresses compile-progress and type-write lines; warnings and errors always show. | | prewarm | boolean \| string[] | true | Pre-compile known crates when the dev server starts, so a cold cargo build races your first request instead of blocking inside it (some dev servers — e.g. Nitro — time out module fetches at ~60s and cache the failure until restart). true pre-warms crates remembered from previous sessions (a small manifest in cacheDir, updated on every compile); an array adds explicit anchors — a .rs file or crate dir, resolved against the Vite root — for first-ever runs where no manifest exists yet; false disables. A request arriving mid-pre-warm joins the in-flight compile instead of starting a second one, and a pre-warm failure is only a warning — the crate falls back to compiling on first import. | | spawnBroker | boolean | true | Route cargo/napi spawns through a long-lived helper process started at dev init, making compiles immune to file-descriptor exhaustion in the dev-server process (a bloated watcher fd table breaks all child-process spawning past ~24k fds on macOS/Node — see issue #6). One idle helper (~40MB) per dev server; forked while the fd table is still small, so its own table stays clean. Dev only — production builds always spawn directly. false disables it and spawns directly with the transient-retry + fd diagnosis fallback. If the broker fails to start or dies, the plugin falls back to a direct spawn automatically. |

The cache key folds in the crate's full local dependency closure (path deps, workspace members, the workspace Cargo.toml, and the lockfile) plus the rustc and @napi-rs/cli versions, so a change anywhere in that set recompiles instead of serving a stale binary.

Testing (vitest)

vitest runs its own Vite pipeline, so a .rs import parse-fails at collection unless the plugin is in that config too. Add rustPlugin() to your vitest.config.ts (or each test.projects entry) to test the real compiled crate — it reuses the content-hash cache, so it's cheap after the first run. When you'd rather not compile Rust (CI without a toolchain), rustTestStub({ … }) redirects .rs imports to a JS twin:

import { rustPlugin, rustTestStub } from "vite-plugin-native-rust";

Full recipes — including the viral-failure explanation and a test.projects example — are in testing.md.

Documentation

Full docs live in the GitHub repository:

License

MIT © Kade Angell