Skip to main content

The Rust Wave Under Your node_modules πŸ¦€

Β· 11 min read
Gergely Sipos
Frontend Architect

In January 2020, esbuild 0.1 dropped and gave the JavaScript ecosystem its first taste of what esbuild's own FAQ described as 10–100Γ— faster tooling. The shock was real, but the lasting shift wasn't actually Go. By 2021 the new tools coming out β€” SWC, Turbopack, Rspack, Biome, oxc, Rolldown, Lightning CSS, plus runtimes like Deno and desktop shells like Tauri β€” were almost uniformly Rust. Six years later, on a modern frontend project, a non-trivial fraction of what lands in node_modules after npm install is a Rust binary in a thin JavaScript wrapper. This post is a curiosity tour of that landscape: who built what, in what order, and how the Rust pieces actually get into the install. It is not a recommendation post; there is no switch-to-X conclusion at the end.

How We Got Here: A Native-Tooling Detour​

The pre-2020 baseline was almost entirely JavaScript or TypeScript running on V8: Babel, webpack, ESLint, Prettier, Rollup, the npm CLI. Evan Wallace's esbuild β€” written in Go β€” was the proof point that compilers and bundlers could be much faster if you stepped off V8. The community absorbed the lesson but did not, by and large, follow Wallace into Go. The next wave was Rust.

The reasons cited by the maintainers of the Rust tools tend to look more pragmatic than ideological:

  • No GC pauses and no JIT warmup, which matters for short-lived CLI invocations where startup is a meaningful fraction of total time.
  • The rayon crate makes data-parallel work over many files almost trivial, and bundling/linting/formatting are exactly that shape of workload.
  • Memory safety without a garbage collector.
  • A single static binary, plus first-class WASM as a compilation target β€” useful when plugins need to be portable and sandboxed.
  • Compiler-author ergonomics: enums, exhaustive pattern matching, and serde map naturally onto AST work.

The esbuild FAQ entry Why is esbuild fast? is still the canonical essay on why native tooling pulls ahead of V8-based tooling for these workloads. Most of its arguments transfer directly to the Rust cohort.

⚠️ Wait β€” esbuild Is Go, Not Rust. And Bun Is Zig.​

Before going further, a clarification, because it is the single most common confusion in this space:

  • esbuild is written in Go by Evan Wallace (Figma co-founder). It is not Rust.
  • Bun is written in Zig by Jarred Sumner. The hot paths β€” bundler, transpiler, package manager, runtime glue β€” are Zig, not Rust. Bun does pull in some Rust dependencies, but calling Bun "a Rust runtime" is wrong.
  • Astro's compiler is written in Go, which most readers also miss.
  • Deno is a Rust project, but the JavaScript engine inside it is V8, which is C++. It is correct to call Deno a Rust project; it is incorrect to call its JS engine Rust.
  • Boa is a JavaScript engine actually written in Rust, but it lives in the research and embedding niche. It is not what powers Deno or Bun.

A language-of-origin map for the tools in this post:

ToolLanguageNotes
esbuildGoThe 2020 catalyst
BunZigRuntime + bundler + package manager
Astro compilerGo
DenoRustV8 (C++) for JS execution
SWC, Turbopack, Rspack, Rolldown, Biome, oxc, Lightning CSS, TauriRustThe wave
BoaRustJS engine, research/embedding

With that out of the way, the rest of this post is about the Rust column.

The Cast: A Categorized Tour​

Before listing tools, three centers of gravity are worth naming. Vercel ships SWC, Turbopack, and Turborepo. ByteDance Web Infra ships Rspack and the broader Rs* family β€” Rsbuild, Rspress, Rsdoctor, Rslib. VoidZero, Evan You's company founded in 2024, stewards Vite, Rolldown, oxc, and Vitest. A surprising amount of the cast list maps onto those three orgs.

Bundlers and Compilers​

  • SWC β€” created by DongYoon Kang (@kdy1), now Vercel-funded. Next.js 12 (October 2021) replaced Babel with SWC for its compile pipeline. SWC's documentation reports roughly 20Γ— single-thread and 70Γ— multi-thread speedups over Babel.
  • Turbopack β€” Vercel, led by Tobias Koppers (@sokra), the original author of webpack. next dev powered by Turbopack reached stable in Next.js 15 (October 2024). The status of next build on Turbopack has been moving quickly; verify against current Next.js release notes.
  • Rspack β€” ByteDance Web Infra. 0.1 in March 2023, 1.0 in August 2024. Rspack's launch post claims 5–10Γ— over webpack on real projects. The Rs* family β€” Rsbuild, Rspress, Rsdoctor, Rslib β€” share Rspack's core.
  • Rolldown β€” VoidZero. Pre-1.0 as of writing, with a stated goal of becoming Vite's default bundler. Whether the migration has fully landed depends on which Vite release you are reading about; see Vite for the current state in our docs.
  • Farm β€” community-maintained Rust bundler, smaller install base but the same architectural pattern.
  • Parcel β€” historically JavaScript, now mixed; several hot paths including Lightning CSS are Rust.
caution

All speed multipliers in this post are vendor-reported. Independent benchmarks vary by workload, hardware, and configuration. Treat them as order-of-magnitude indicators, not guarantees.

CSS and Styling​

  • Lightning CSS β€” by Devon Govett, the original author of Parcel. Used by Vite, Bun, and others as a CSS parser/transformer/minifier.
  • Tailwind v4 "Oxide" engine (January 2025) β€” partly Rust. The Tailwind team reports substantial build-time wins; the public numbers should again be read as maintainer-reported.

Linters and Formatters​

  • Biome β€” the community fork of Rome after Rome Tools (the company) shut down in September 2023. Biome 1.0 shipped in August 2023; Biome 2.0, in 2025, brought type-aware rules and a plugin API. The Biome team reports formatter performance around 25Γ— over Prettier.
  • oxc / oxlint β€” by Boshen Chen (@Boshen), now under VoidZero. The oxc README reports 50–100Γ— over ESLint. Type-aware rules are in active rollout; the project's status is worth checking against the current release notes before depending on them.
  • dprint β€” Rust core with WASM plugins.
  • deno_lint β€” Rust, ships inside Deno.
  • For the historical record: Rome (rewritten from TypeScript to Rust in 2022, then continued as Biome) and RSLint (abandoned).
note

Rome Tools (the company) shut down in September 2023. Biome is the community fork of its codebase. They are not separate tools that competed β€” Biome is what Rome's codebase became.

Runtimes, Package Managers, Version Managers​

  • Deno β€” Ryan Dahl's runtime, Rust on top of V8. Deno 2 shipped in October 2024.
  • Bun β€” written in Zig, not Rust (mentioned again here because the confusion is persistent).
  • Version managers: Volta, fnm, and mise are all Rust.
  • Orogene β€” Kat MarchΓ‘n's experimental Rust package manager.
  • npm, pnpm, and Yarn are not Rust.

Monorepos and Test​

  • Turborepo β€” Vercel; originally Go, undergoing a phased rewrite to Rust since 2023.
  • moon / moonrepo β€” Rust from day one.
  • Nx β€” TypeScript shell, with Rust internals introduced in Nx 16+.
  • Vitest is not Rust; it sits in the VoidZero stewardship orbit but is a JavaScript/TypeScript test runner.

AST and Codemod Layer​

  • swc_ecma_parser and oxc_parser β€” both available as standalone parser crates, decoupled from their parent toolchains.
  • ast-grep β€” Rust plus Tree-sitter, structural search and codemod.
  • Tree-sitter β€” C core with Rust bindings; the substrate for many editors and several linters.

Desktop​

  • Tauri β€” Rust shell wrapping the OS-native webview. Same pattern as the rest of the cast: hot loop in Rust, UI in JavaScript or TypeScript.

A Compressed Timeline​

  • 2018-05 β€” Deno unveiled at Ryan Dahl's "10 things I regret about Node" talk.
  • 2019 β€” SWC project started.
  • 2020-01 β€” esbuild 0.1 β€” the catalyst.
  • 2020-08 β€” Rome Tools founded by Sebastian McKenzie (also creator of Babel and Yarn).
  • 2021-10 β€” Next.js 12 swaps Babel for SWC.
  • 2022 β€” Rome rewrites from TypeScript to Rust.
  • 2022-10 β€” Turbopack alpha.
  • 2023-03 β€” Rspack 0.1.
  • 2023-08 β€” Biome 1.0.
  • 2023-09 β€” Rome Tools shuts down; Biome forks.
  • 2023-11 β€” Rolldown announced.
  • 2024-08 β€” Rspack 1.0.
  • 2024-10 β€” Deno 2; Next.js 15 ships Turbopack next dev as stable.
  • 2025-01 β€” Tailwind v4 Oxide.
  • 2025 β€” Biome 2.0 with type-aware rules.
  • 2025–26 β€” Rolldown approaching stable; Vite migration in flight.

The cadence works out to roughly one major Rust-tool release per quarter for five years.

How Rust Actually Ships Inside node_modules​

An end user runs npm install and never sees a Rust toolchain. How does the Rust binary get there?

The dominant mechanism is napi-rs by LongYinan (@Brooooooklyn), which wraps Node-API β€” Node.js's C API for native addons, designed to provide ABI stability across Node.js versions so a compiled binary keeps working as Node itself upgrades. SWC, Rspack, Lightning CSS, the SWC plugin host inside Next.js, and the oxc bindings all use napi-rs. The older alternative is Neon, still active but less common in the recent wave.

The distribution pattern, popularized by esbuild and standardized by napi-rs, looks like this:

  • A thin main JavaScript package on npm.
  • Many per-platform native packages, each declaring os, cpu, and (where relevant) libc fields.
  • All of those native packages listed in optionalDependencies of the main package.
  • At install time, the package manager skips entries whose platform constraints don't match. At runtime, the main package require()s whichever sibling matched.
package.json (excerpt β€” version pinned for illustration)
{
"name": "@swc/core",
"version": "1.10.1",
"optionalDependencies": {
"@swc/core-linux-x64-gnu": "1.10.1",
"@swc/core-linux-x64-musl": "1.10.1",
"@swc/core-darwin-arm64": "1.10.1",
"@swc/core-darwin-x64": "1.10.1",
"@swc/core-win32-x64-msvc": "1.10.1",
"@swc/core-win32-arm64-msvc": "1.10.1"
}
}

In real @swc/core releases the platform packages are pinned to the exact same version as the main package β€” the snippet above uses an abbreviated platform list and a single illustrative version (1.10.1) for readability.

The other delivery channel is WebAssembly. wasm-bindgen, wasm-pack, and WASI cover the toolchain side. SWC plugins ship as WASM modules executed inside a wasmer/wasmtime sandbox. dprint plugins are WASM. Biome 2's plugin layer leans on GritQL with a WASM-style sandbox boundary.

The pattern is the same across the cast: keep the host fast and native, keep plugins portable and sandboxed.

Trade-offs Nobody Puts on the Landing Page​

This is descriptive, not prescriptive.

  • Install size and platform-matrix lag β€” every supported triple is another tarball on npm. musl builds, Windows-arm64, and Linux-arm64 sometimes ship later than the main targets, which has caused install failures during platform transitions.
  • Supply-chain audit difficulty β€” cargo audit and npm audit cover different dependency graphs. A purely npm-side audit doesn't see the Rust crate graph baked into the prebuilt binary.
  • Plugin friction β€” Rust plugins require either a toolchain on the user's machine or a stable WASM ABI. Rspack's webpack-loader compatibility layer is the prime example: the existing JavaScript plugin ecosystem is a moat to bridge, not replace.
  • Cross-language debugging β€” stack traces that cross the JS/Rust boundary are awkward, and source maps stop being any single tool's responsibility.
  • ABI churn β€” Node-API itself is stable, but bindings evolve, and native modules occasionally need rebuilds when Node has a major release.

A recurring shape in these trade-offs: end users get the speed and the single-binary install, while plugin authors pay in toolchain, ABI, and sandbox complexity.

Where Things Stand in 2026​

Statuses below are as of writing and will drift; treat the table as a snapshot, not a spec.

ToolStatus (as of writing)
Vite + RolldownRolldown approaching default in Vite; opt-in via rolldown-vite
Turbopacknext dev stable since Next 15; next build reaching GA
Biome2.x with type-aware rules and plugin API
oxlintStable; type-aware rules in active rollout
Rspack1.x stable, webpack-loader-compatible
Deno2.x
Bun1.x (Zig, not Rust β€” final reminder)

The 2026 picture isn't "Rust replaced JavaScript tooling." It is closer to "every hot loop got rewritten; the JavaScript and TypeScript shells stayed."

Closing: Hot Loops in Rust, Ergonomic Shells in JS/TS​

The pattern is mechanical, not ideological. Parsers, bundlers, linters, formatters, and version resolvers are CPU-bound batch jobs over many files; that is a workload Rust is good at. Configuration surfaces, plugin APIs, and developer-facing CLIs need to be ergonomic and dynamic; that is a workload JavaScript and TypeScript are good at. The split falls along that line consistently across the cast list.

Tauri versus Electron is the same shape one layer up β€” native shell, web UI β€” and shows up because the underlying constraint is the same.

This post is a snapshot. The cast list will be different in two years. The architectural pattern probably won't be.