The Rust Rewrite Wave
The JavaScript ecosystem has historically been self-hosted: Node.js runs V8 (C++), but nearly everything above it—bundlers, linters, formatters, test runners—was written in JavaScript itself. Starting around 2022–2023 this began to change. Tools written in Rust (and in Bun’s case, Zig) started replacing JS-based incumbents, not by offering new abstractions but by doing the same work orders of magnitude faster. The key insight is that parsing, transforming, and bundling are CPU-bound workloads where JavaScript’s single-threaded, GC-paused execution model is the bottleneck—not the algorithm. Rewriting in a systems language doesn’t change what these tools do; it removes the overhead of doing it in a managed runtime.
This note covers the tools that define this new generation: Bun (runtime + toolkit), Turborepo (monorepo task orchestration), Vite / Rolldown (dev server + bundler), Biome (linter + formatter), and Oxc (compiler infrastructure). They are independent projects, but increasingly interlocking—Vite 8 uses Rolldown which uses Oxc’s parser, for example.
Bun
Bun positions itself as a drop-in Node.js replacement that bundles a runtime, package manager, bundler, and test runner into a single binary. Where Node.js is a composition of separate tools (npm, npx, jest, webpack, …), Bun folds all of them into one process.
Engine and Language Choices
Node.js embeds V8 (Google’s engine). Bun instead embeds JavaScriptCore (JSC), Apple’s engine that powers Safari and WebKit. JSC has a tiered JIT compiler (LLInt → Baseline → DFG → FTL) and tends to have faster startup than V8 because its lower tiers begin executing sooner, whereas V8’s Maglev/TurboFan pipeline optimizes for peak throughput at the cost of warmup time. For CLI tools and short-lived scripts—which is what a package manager or test runner often is—startup latency matters more than steady-state throughput, so JSC is a good fit.
The native layer is written in Zig rather than C++ or Rust. Zig gives Bun manual memory management without a GC, comptime generics, and seamless C ABI interoperability—important because JSC itself exposes a C API. Bun’s bindgen layer creates zero-overhead bridges between Zig structs and JSC heap objects, avoiding the napi marshalling cost that Node.js addons pay.
Co-Scheduled Event Loop
A subtle but important architectural choice: Bun co-schedules its I/O polling and JSC’s garbage collector in the same loop. In Node.js, V8’s GC runs on its own schedule, and libuv polls for I/O independently; the two can step on each other’s toes (a GC pause that delays an I/O callback, or an I/O burst that defers GC until memory pressure spikes). Bun’s event loop interleaves GC safepoints with I/O readiness checks, which reduces both tail latency on I/O callbacks and peak RSS.
Package Manager
bun install is a package manager compatible with npm’s registry and lockfile semantics. Its speed advantage comes from two things: it resolves and fetches packages in parallel using Zig’s async I/O (not JS promises), and it installs by hardlinking from a global cache rather than copying into each project’s node_modules. A clean install of a mid-size project that takes npm ~15 seconds and pnpm ~8 seconds typically finishes in under 2 seconds with bun install.
Bundler
Bun ships a built-in bundler invoked via bun build. It can bundle 10,000 React components in roughly 270 ms—competitive with esbuild and considerably faster than Webpack or Rollup. As of Bun 1.3, the bundler can produce standalone executables that embed both frontend and backend code into a single binary, useful for shipping self-contained CLI tools or microservices.
Built-In Database Clients
Rather than relying on npm packages like pg or mysql2, Bun ships native database drivers behind the unified Bun.SQL API. PostgreSQL, MySQL/MariaDB, and SQLite are supported. The MySQL driver benchmarks at roughly 9× the throughput of mysql2 on Node.js because the protocol parsing happens in Zig, not in JavaScript, and avoids the Buffer allocation overhead.
import { SQL } from "bun";
const db = new SQL("postgres://localhost/mydb");
const users = await db.query("SELECT * FROM users WHERE active = $1", [true]);Other Notable Built-Ins
Bun.secrets integrates with the OS keychain (Keychain on macOS, libsecret on Linux, Credential Manager on Windows) to store and retrieve secrets for local development without .env files leaking to disk. Bun also includes a native YAML parser, a test runner (bun test, Jest-compatible), and as of 1.3, a zero-config frontend dev server with HMR.
Turborepo
Turborepo is a build system for JavaScript/TypeScript monorepos. It does not replace your bundler or test runner—it orchestrates them. If you have 30 packages in a monorepo and run turbo build, Turborepo figures out which packages depend on which, runs their build scripts in topological order, parallelizes independent branches, and caches every task’s output so that unchanged packages are never rebuilt.
Task Graph and Hashing
When you run turbo run build, Turborepo constructs a directed acyclic graph of tasks from the dependency relationships declared in each package’s package.json and the pipeline definition in turbo.json. It then computes a content hash for each task node. The hash incorporates:
- The source files that the task reads (determined by the
inputsglob inturbo.json, defaulting to all files in the package). - The task configuration from
turbo.json(both root-level and package-level). - The environment variables listed in
envorglobalEnv. - The hashes of upstream tasks that this task depends on (transitively).
If the resulting hash matches a previously stored entry, Turborepo replays the cached outputs (files written to outputs globs) and the logged stdout/stderr, skipping execution entirely. This is a content-addressed cache—it doesn’t care about timestamps, only about whether the inputs have actually changed.
Local and Remote Caching
Locally, cached artifacts live in .turbo/cache. The more powerful feature is remote caching: artifacts are uploaded to a shared store (Vercel’s hosted cache or a self-hosted API-compatible server) so that a CI run or a teammate’s machine can hit the cache for a task that someone else already computed. Setup is two commands:
npx turbo login # authenticate with Vercel (or custom remote)
npx turbo link # associate this repo with the remote cacheAfter this, every turbo run transparently pushes and pulls cache artifacts. In practice this means that if CI already built packages/ui, a developer pulling the branch and running turbo build will restore packages/ui from the remote cache in milliseconds instead of rebuilding it.
Configuration: turbo.json
A typical turbo.json defines the task pipeline and caching behavior:
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"description": "Compile TypeScript and bundle"
},
"test": {
"dependsOn": ["build"],
"outputs": [],
"description": "Run unit tests"
},
"lint": {
"outputs": []
}
}
}The "^build" syntax means “the build task of every package I depend on must complete first.” The description field (added in Turborepo 2.8) serves both human documentation and AI agent consumption.
Watch Mode and Git Worktrees
turbo watch runs a global, dependency-aware file watcher across the monorepo. When a file in packages/utils changes, Turborepo re-executes that package’s relevant task and any downstream tasks that depend on it, respecting the full task graph. As of Turborepo 2.4, watch mode supports caching—if a file change results in a hash that was previously computed, the task is replayed from cache rather than re-executed.
Turborepo 2.8 also introduced Git worktree support, sharing the local cache across worktrees so that switching branches via git worktree add doesn’t invalidate previously computed results.
Vite and Rolldown
Vite’s Architecture
Vite is a frontend dev server and build tool built around two ideas: during development, serve source files over native ES modules (ESM) so the browser does the bundling, and for production, bundle with a traditional bundler. The dev server intercepts import requests, transforms individual files on the fly (TypeScript → JS, JSX → JS, CSS modules → JS), and serves them with aggressive HTTP caching. Because it never bundles during development, startup is near-instant regardless of project size—the cost is paid per-module on demand rather than upfront.
Hot Module Replacement works by maintaining an in-memory module graph. When a file changes, Vite walks the graph to find the minimal set of modules that need updating, sends a WebSocket message to the client, and the client re-imports only those modules. Framework plugins (React, Vue, Svelte) hook into this to preserve component state across edits.
The Rolldown Transition
Historically Vite used esbuild for dependency pre-bundling (converting CommonJS packages into ESM at dev startup) and Rollup for production builds. This dual-tool approach caused subtle behavioral differences between dev and prod. Rolldown is the Rust-based bundler developed by the Vite team (under VoidZero, Evan You’s company) to replace both. It is API-compatible with Rollup’s plugin interface but reimplemented in Rust, using Oxc’s parser and resolver internally.
Vite 8 (released in beta, late 2025) integrates Rolldown as its bundler for both dev pre-bundling and production builds, unifying the pipeline. Rolldown handles tens of thousands of modules efficiently and includes a built-in minifier (still alpha-stage as of early 2026, with Terser/esbuild minification as fallbacks).
Biome
Biome is a Rust-based linter and formatter that replaces the ESLint + Prettier combination with a single binary. It processes 10,000 files in approximately 0.8 seconds, where ESLint + Prettier together take around 45 seconds on the same corpus.
Why a Unified Tool Matters
ESLint and Prettier have a well-known conflict surface: Prettier reformats code, and ESLint’s stylistic rules may disagree with Prettier’s output, requiring eslint-config-prettier to disable the overlapping rules. This layered configuration is fragile and confusing. Biome eliminates the problem by owning both formatting and linting in a single pass—there is no conflict because there is only one opinion engine.
Type-Aware Linting Without tsc
Biome 2.0 introduced type-aware linting: rules that understand TypeScript types (e.g., “this expression is always string | undefined, so the ! assertion is unnecessary”) without invoking the TypeScript compiler. Biome achieves this by running its own lightweight type inference on the AST produced by its parser, which is fast enough to run on every keystroke in an editor without perceptible delay. This is in contrast to typescript-eslint, which shells out to tsc to build a full program and can take seconds on large projects.
Plugin System: GritQL
For custom rules, Biome uses GritQL, a declarative pattern-matching language for ASTs. Instead of writing a visitor in JavaScript (as with ESLint), you write a query that describes the AST shape you want to match and the replacement:
`console.log($msg)` => `logger.info($msg)`The GritQL engine compiles these patterns into efficient matchers that run at Biome’s native speed.
Oxc
Oxc (short for Oxidation Compiler) is not a user-facing tool like the others—it is a compiler infrastructure project that provides reusable components: a parser, a linter, a transformer, a resolver, and a sourcemap library, all in Rust. Other tools build on top of it.
Components
- oxc-parser: Parses
.js(x)and.ts(x)into an AST. It passes all ECMAScript Test262 stage-4 tests. Benchmarks show it is roughly 3× faster than SWC and 5× faster than Biome’s parser. - oxlint: A standalone linter with 500+ built-in rules, 50–100× faster than ESLint depending on core count. It is designed to be used alongside or as a replacement for ESLint.
- oxc-transform: Transpiles TypeScript and JSX down to ES2015. It is 4× faster than SWC’s transformer and uses 20% less memory. It supports isolated declaration emit (generating
.d.tsfiles without runningtsc). - oxc-resolver: A module resolution implementation compatible with Node.js and TypeScript’s resolution algorithms, used by Rolldown.
Relationship to Rolldown and Vite
Rolldown uses oxc-parser for parsing, oxc-resolver for module resolution, and Oxc’s sourcemap library for map composition. This means that when you run vite build on Vite 8, the chain is: Vite → Rolldown → Oxc. The entire hot path from source file to bundled output stays in Rust, with no JS ↔ native marshalling until the final result is handed back to the Vite plugin pipeline.
Putting It All Together
A realistic modern project might combine these tools as follows. Turborepo sits at the top, orchestrating build, test, and lint tasks across a monorepo. Each package’s build task invokes Vite 8, which uses Rolldown (backed by Oxc) to bundle. Linting and formatting are handled by Biome (or by Oxlint for linting paired with Biome for formatting). If the runtime is Bun rather than Node.js, bun install replaces npm/pnpm, bun test replaces Jest, and bun build can be used for backend bundling or standalone binaries.
The package manager layer is the one place where you must pick a side. In a Turborepo monorepo you choose one of:
- npm — the default, widest compatibility, slowest
- pnpm — content-addressable store with symlinked
node_modules, good monorepo support via workspaces - Yarn (Berry) — PnP mode eliminates
node_modulesentirely, but some packages still don’t support it - Bun — fastest installs, but occasionally breaks on packages with native addons that assume Node.js-specific build tooling
Turborepo works with all four; configure via "packageManager" in the root package.json.
Tip
If you’re starting a new monorepo today and don’t need Bun’s runtime features, pnpm + Turborepo + Vite 8 + Biome is the lowest-friction combination. pnpm’s workspace protocol (
workspace:*) and Turborepo’s task graph integrate cleanly, Vite 8 handles both dev and prod with Rolldown, and Biome replaces both ESLint and Prettier with zero config conflicts.
Warning
The Rolldown minifier is still in alpha as of early 2026. For production builds where minification correctness is critical, keep
build.minify: 'esbuild'(or'terser') in your Vite config until Rolldown’s minifier stabilizes.