Every eighteen months or so, the JavaScript ecosystem gets a new bundler, the conversation restarts, and somebody on Hacker News explains that the previous one is now dead. Webpack was dead in 2020 when esbuild landed. Vite killed Webpack in 2022. Rspack and Turbopack are killing Vite in 2024. Soon something else will kill those two.

The reality is messier and more interesting. Webpack still ships an enormous fraction of the web. Vite is the default for new Vue, Svelte, and Solid projects. Turbopack powers next dev in modern Next.js. Rspack is quietly replacing Webpack inside large companies because it speaks the same config language but runs in Rust. None of them is dead. They're solving overlapping problems with different priorities, and the right question is never "which one wins?". It's "what is each one good at, and which trade-off am I actually buying?"

This piece walks through all four. What each one is, where it earns its place, where it bites you, and a practical way to pick.

The four players, in two sentences each

Before any comparison, you need to know what each tool actually is, because the names get used loosely and the marketing pages all sound similar.

Webpack. The bundler that defined the modern JS frontend, written by Tobias Koppers in 2012, runs on Node, configurable to the point of absurdity. Module Federation, code splitting, tree shaking, asset loaders: most of these patterns are either Webpack's invention or were standardised through it.

Vite. A dev server plus a build tool, started by Evan You in 2020, designed around a simple observation: in development, the browser supports native ES modules, so you don't have to bundle on every save. Vite serves source files directly in dev, uses esbuild for pre-bundling dependencies and transpiling TypeScript, and uses Rollup for the production build.

Turbopack. A Rust bundler from Vercel, also led by Tobias Koppers, designed as Webpack's successor with on-demand compilation and a long-term function-level cache. Ships as the engine behind next dev in modern Next.js; the production next build path is still being stabilised at the time of writing.

Rspack. A Rust bundler from ByteDance, aimed at Webpack API compatibility: the config shape, the loader interface, the plugin hooks are deliberately the same so you can swap it in with minimal changes. Reached 1.0 in 2024 and pairs with Rsbuild, a higher-level builder on top of it, similar to how Vite sits on top of Rollup.

That's the cast. Now the trade-offs.

What Vite actually does in dev (and why people misread it)

The single biggest source of confusion about Vite is the belief that it "doesn't bundle." That's half right and half wrong, and the half that's wrong is the half people quote.

In dev, Vite serves your source files individually through native ESM. When the browser asks for /src/main.ts, Vite reads the file off disk, transpiles the TypeScript with esbuild (in milliseconds), rewrites bare imports like import { ref } from "vue" to /node_modules/.vite/deps/vue.js?v=..., and streams it back. No bundling step. The browser walks the import graph itself, on demand, lazily, in the order pages are actually visited.

That's the part everyone talks about, and the reason vite dev starts so fast: for a project that would take Webpack 30 seconds to bundle on first load, Vite's cold-start can be under a second because most of the work is deferred until the browser requests a specific module.

But Vite does bundle in two places, and they matter:

  1. Pre-bundling of dependencies. Native ESM in browsers falls over fast on real node_modules trees: a single lodash-es import resolves to hundreds of tiny modules, and a Firefox dev tab opening 600 requests in serial is the kind of thing that makes you reach for a bundler. Vite preempts this by bundling each dependency into a single ESM file the first time it sees it, caching the result in node_modules/.vite/deps/. esbuild does the work because esbuild is roughly 50-100x faster than a pure-JS bundler on this workload.
  2. Production bundling. vite build is Rollup. Always has been. The dev-server story is the marketing, but the production output goes through the same conservative, tree-shaking, output-format-flexible bundler that the JS library ecosystem has used for years.

The reason this matters when picking a tool: Vite's dev experience is special, but its production output is "Rollup with sensible defaults." If you have a deeply un-Rollup-friendly production requirement, say a complex Module Federation graph or a Webpack-loader-only asset pipeline, you're going to feel that asymmetry. There's active work to move Vite to Rolldown, a Rust port of Rollup, which would unify dev and production around one Rust core. Until that lands, treat Vite's production build like Rollup's, because that's what it is.

TypeScript vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [vue()],
  // Most projects need nothing else. The whole point is that the
  // defaults are aggressive enough to ship to production unchanged.
  build: {
    target: "es2022",
    sourcemap: true,
  },
});

Compare that to a Webpack config of the same era and you'll understand why Vite's adoption curve looked the way it did. Less surface area to misconfigure.

Diagram titled 'What Vite actually runs': a vite dev lane transpiling .ts through esbuild and serving pre-bundled deps, beside a vite build lane running source through Rollup for tree-shaking and chunked output, with a footnote arrow pointing to Rolldown.

What Webpack still wins at

Webpack is the easy one to dismiss, and dismissing it is a mistake. It's slower than the Rust newcomers, the config language is famously baroque, and the bundle output isn't smaller than Rollup's. But three things keep it in production at scale, and you need to know about them before you migrate off.

Module Federation. This is the killer feature for micro-frontend architectures: a host app loads remote bundles at runtime, sharing dependencies between them so the same React copy isn't shipped twice. Vite has a community plugin for it; Turbopack and Rspack are working on first-class support; but the reference implementation and the largest production deployments still live on Webpack 5. If your architecture depends on Module Federation, switching tools is not a casual decision.

The loader ecosystem. Webpack's loader contract is the lingua franca of "transform this file type into a JS module." There are loaders for SVG-as-component, MDX, GraphQL .graphql files, Workers, WASM, custom DSLs, decades of accumulated weird build needs. Vite's plugin system is Rollup's plus a Vite-specific hook layer; it's powerful but newer, and some Webpack loaders just don't have a Vite equivalent yet.

Stability. Webpack moves slowly on purpose. A config that worked in Webpack 5.50 still works in Webpack 5.90. If you're maintaining a large app for a long time and your team isn't a frontend team, that stability is its own feature: you don't want your build tool to ship a 0.x release that breaks your CI on a Tuesday.

What Webpack loses on, very clearly: dev-server speed on large codebases. A 2000-module project takes Webpack a long, long second to do an initial bundle and a noticeable fraction of a second per HMR update. That's the gap Rspack and Turbopack exist to close.

JavaScript webpack.config.js
const path = require("path");

module.exports = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].[contenthash].js",
    clean: true,
  },
  resolve: { extensions: [".ts", ".tsx", ".js"] },
  module: {
    rules: [
      { test: /\.tsx?$/, use: "swc-loader" },
      { test: /\.css$/, use: ["style-loader", "css-loader"] },
    ],
  },
};

That's a minimal Webpack config in 2026. The reason swc-loader (or esbuild-loader, or babel-loader) appears in every modern Webpack setup: Webpack itself doesn't transpile TypeScript or JSX. The bundler resolves modules and produces chunks; the language transforms are delegated to whatever's fastest, which is now Rust- or Go-based.

What Rspack changes about that picture

Rspack's pitch is small and unusually honest: take the Webpack config you already have, swap the binary, get most of your build speed back.

It's a Rust bundler that re-implements Webpack's loader and plugin APIs closely enough that real Webpack plugins (MiniCssExtractPlugin, HtmlWebpackPlugin, CopyWebpackPlugin, and many of the loaders) work without code changes. The config keys are the same: entry, output, module.rules, plugins. The mental model is the same. The migration is a package.json swap and a few tweaks for the bits where Rspack hasn't reached parity.

JavaScript rspack.config.js
// This is, deliberately, almost identical to the webpack.config.js above.
const path = require("path");

module.exports = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].[contenthash].js",
    clean: true,
  },
  resolve: { extensions: [".ts", ".tsx", ".js"] },
  module: {
    rules: [
      // Rspack ships a built-in SWC-based transformer; you don't
      // need a separate ts-loader or swc-loader unless you want one.
      { test: /\.tsx?$/, type: "javascript/auto" },
      { test: /\.css$/, type: "css" },
    ],
  },
};

The reason Rspack matters: there's a long tail of large apps (internal admin tools, design systems, ten-year-old SaaS frontends) that cannot migrate to Vite without rewriting their build, but where the engineering team is bleeding ten minutes off every CI run because Webpack is slow on their module graph. Rspack is the upgrade path that doesn't require a rewrite. You keep the config, you keep the loaders, you keep the Module Federation setup, you just get a build that's 5-10x faster on real workloads.

What Rspack hasn't fully matched yet: the very long tail of Webpack plugins that reach deep into internal APIs. The common ones work. The obscure ones may not. Check your plugin list against the Rspack compatibility docs before you commit.

There's also Rsbuild, which is to Rspack what Vite is to Rollup, a higher-level builder that ships sensible defaults so you don't have to write a config. If you're starting fresh and want Webpack's mental model without writing 200 lines of module.rules, Rsbuild is the entry point.

What Turbopack is actually for

Turbopack is the trickiest one to write about because the marketing and the current reality are slightly out of phase, and the picture changes every release.

The core idea: Turbopack is a Rust bundler with function-level incremental computation. The unit of caching isn't a file or a module: it's the smallest unit of work the compiler does internally. When you change one line in one component, the cache invalidates the smallest possible set of downstream computations and re-runs only those. The promise is that a warm HMR update is almost instant regardless of project size.

Where this lands well today: next dev in Next.js. Turbopack has been stable for development since Next.js 15, and on big Next apps the dev-server experience is meaningfully faster than the Webpack-based equivalent, both cold-start and per-file updates. If you're running Next.js and you haven't tried next dev --turbo (or the --turbopack flag depending on your version), do that.

Where it's still maturing: production builds. As of writing, next build --turbopack is in beta: the test coverage is broad but not yet at the "every Next.js app passes" bar, and you'll see Vercel rolling stability fixes release by release. By the time you read this, it may be stable; check the Next.js release notes before you commit.

Where it's not really the answer at all: outside Next.js. Turbopack is technically a standalone bundler with its own config surface, but the practical reality is that almost nobody uses it outside the Next.js wrapper. If you're picking a build tool for a Vue project or a vanilla React project or a SvelteKit app, "should I use Turbopack?" is the wrong question. Vite (for now) or Rspack (if you want Webpack semantics) are the real options.

JavaScript next.config.js
// In Next.js, Turbopack is enabled via flags / config, not by writing
// a separate turbopack.config.js. The bundler is wired into the framework.
module.exports = {
  experimental: {
    turbo: {
      // Custom loader rules go here when you have them.
      rules: {
        "*.svg": { loaders: ["@svgr/webpack"], as: "*.js" },
      },
    },
  },
};

If you're not on Next.js, Turbopack isn't a tool you reach for in 2026. That's not a knock, it's just a deliberate scoping choice. Vercel is solving "make the Next.js dev experience scale to massive monorepos." Everyone else is solving different problems.

How to pick: a flowchart in three questions

You don't pick a bundler by benchmark. You pick by which migration path is cheapest and gets you the property you actually care about.

Question 1: Are you on Next.js?

If yes, your bundler is essentially decided for you. Use Turbopack for dev (it's already the default in modern Next.js), and let Vercel's release cadence decide when you turn it on for production. There's no good reason to fight the framework here.

Question 2: Do you have an existing Webpack config you'd rather not rewrite?

If yes, look at Rspack. It's the lowest-friction migration in the entire space. The value of "swap a binary, keep your loaders" cannot be overstated when your build config has accumulated five years of weird customizations that nobody fully remembers. Treat Vite as "the rewrite path" and Rspack as "the upgrade path," and pick based on whether the rewrite is worth it.

Question 3: Are you starting fresh, on Vue / Svelte / Solid / React (without Next.js)?

Then it's Vite for almost every project, almost every time. The dev experience is the best in the ecosystem, the production output is solid, the plugin community is large, and the defaults work without tuning. Reach for Webpack only if you specifically need Module Federation today, and reach for Rspack/Rsbuild if you want Webpack's mental model with Rust speed.

There is no question 4. The three above cover the realistic decision space for almost every team. The cases where someone genuinely needs to compare more than two options at once are rare, and when they happen the answer is usually "spike a prototype with the top two for a day, pick the one your team can debug."

The thing nobody tells you about benchmarks

Every bundler comparison blog includes a chart. Turbopack is 5x faster than Vite. Rspack is 10x faster than Webpack. Vite cold-starts in 200ms. Most of these numbers are not lying, but they're measuring the wrong thing.

The metric that matters in real life is not the cold build of create-react-app. It's the time from save to interactive feedback on the codebase you actually work in: a 50k-line app with twelve route bundles, three CSS-in-JS libraries, an SVG sprite system, a Storybook setup, and four years of legacy dependencies. That number is dominated by your config, your plugin chain, your type-checking pipeline, and your node_modules size. The bundler's raw speed is one input among many.

So when you're picking, do the comparison on your codebase, not on a demo repo. Migrate a single feature branch, time the dev-server start ten times, time the prod build ten times, time HMR ten times after a save. Then look at the dev-experience qualitative stuff (error messages, source maps, stack traces) and decide which one your team would actually rather debug at 2am. That's the comparison that matters.

The build tool landscape isn't a leaderboard. It's a set of trade-offs that map onto the shape of your codebase, your team's familiarity, and how much rewriting you're willing to do this quarter. Pick the one that lets you ship the next thing faster, not the one with the best chart.