The State of JavaScript Build Tools in 2026
TL;DR
Vite won the DX war. Turbopack is winning production performance. Webpack still runs half the web. Vite (~25M weekly downloads) is the default choice for new projects in 2026 — instant dev server, hot reload in <50ms, Rollup for production. Turbopack (~8M, bundled with Next.js 15) is the fastest production bundler — up to 10x faster than Webpack for large apps. Webpack (~30M) is everywhere but rarely chosen for new projects. esbuild (~20M) powers most other bundlers as the underlying transformer.
Key Takeaways
- Webpack: ~30M weekly downloads — legacy dominant, rarely chosen new, but everywhere
- esbuild: ~20M downloads — fastest transpiler/bundler, powers Vite dev mode
- Vite: ~25M downloads — developer's choice, instant HMR, ESM-native
- Turbopack: ~8M downloads — Next.js 15 bundler, Rust-based, fastest production builds
- Rollup: ~22M downloads — library bundling standard, powers Vite production
The Landscape in 2026
What Changed Since 2023
The bundler landscape consolidated dramatically:
- Webpack's decline — New projects rarely choose Webpack. Create React App (Webpack) was deprecated. Vite replaced it as the "safe default."
- Vite's dominance — Vite is now the default for Vue, SvelteKit, Remix, Qwik, Astro, and vanilla projects. Even React's official recommendation shifted to Vite.
- Turbopack's rise — Bundled with Next.js 15, Turbopack graduated from beta and is now the default bundler for Next.js. For large-scale Next.js apps, it's 3-5x faster cold builds than Webpack.
- esbuild as infrastructure — Nobody deploys esbuild directly, but it's the transformer inside Vite, Parcel, and most other tools. Its 10-100x speed over tsc transformed what "fast" means.
- Bun's bundler — Bun ships a built-in bundler that's competitive with esbuild. For Bun-first projects, it's the natural choice.
Vite (The Developer Standard)
// vite.config.ts — modern config
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [
react(), // HMR for React, JSX transform
tsconfigPaths(), // TypeScript path aliases
],
build: {
target: 'es2022', // Modern output — smaller bundles
sourcemap: true,
rollupOptions: {
output: {
// Manual chunk splitting for vendor caching
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
query: ['@tanstack/react-query'],
},
},
},
},
server: {
port: 5173,
// Proxy API calls to backend during dev
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
},
},
},
// Environment variables
// .env.local: VITE_APP_URL=http://localhost:5173
// Access via: import.meta.env.VITE_APP_URL
});
# Vite speed (2026 benchmarks, medium React app ~500 components)
# Cold start: ~180ms (vs Webpack: ~8,000ms)
# HMR update: ~20ms (vs Webpack: ~400ms)
# Production build: ~8s (vs Webpack: ~35s)
# Dev server: instant (serves unbundled ESM, browser does resolution)
# Production: uses Rollup for optimized output
Turbopack (Next.js Default)
// next.config.js — Turbopack is default in Next.js 15
/** @type {import('next').NextConfig} */
const nextConfig = {
// No config needed — Turbopack is the default bundler in Next.js 15
// turbo: {} // optional Turbopack-specific config
};
export default nextConfig;
# Turbopack speed (large Next.js app, 1000+ routes)
# Cold start: ~1.2s (vs Webpack: ~15s)
# HMR update: ~60ms (vs Webpack: ~2,000ms)
# Production build: ~45s (vs Webpack: ~180s) ← major win
# Turbopack benchmarks (Vercel's data):
# 700ms cold start → 76% faster than Webpack
# 96.3% HMR → 96.3% faster route updates vs Webpack
# Enable (still opt-in in Next.js 14):
# next dev --turbo
// Turbopack — custom transforms (turbo.config.ts)
import type { TurboConfig } from 'next';
const turboConfig: TurboConfig = {
resolveAlias: {
// Path aliases in Turbopack
'@components': './src/components',
'@lib': './src/lib',
},
resolveExtensions: ['.ts', '.tsx', '.js', '.jsx'],
rules: {
// Custom file transforms
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
};
esbuild (The Speed Baseline)
// esbuild — direct usage (library bundling, scripts)
import * as esbuild from 'esbuild';
// Bundle a Node.js CLI tool
await esbuild.build({
entryPoints: ['src/cli.ts'],
bundle: true,
platform: 'node',
target: 'node20',
outfile: 'dist/cli.js',
format: 'esm',
sourcemap: true,
// Tree-shake and minify for production
minify: true,
treeShaking: true,
// External deps (don't bundle node_modules for Node.js)
packages: 'external',
});
// Bundle browser code
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
platform: 'browser',
target: ['chrome120', 'firefox120', 'safari17'],
outdir: 'dist',
format: 'esm',
splitting: true, // Code splitting for dynamic imports
minify: true,
sourcemap: true,
});
# esbuild speed (large TypeScript app, ~1000 files)
# Bundle time: ~0.4s (vs tsc: ~8s, vs Rollup: ~12s)
# No type checking — just transforms syntax
# Use tsc --noEmit separately for type checking
Rollup (Library Bundling)
// rollup.config.js — library bundling standard
import { defineConfig } from 'rollup';
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';
import dts from 'rollup-plugin-dts';
export default defineConfig([
// Main bundle
{
input: 'src/index.ts',
external: ['react', 'react-dom'], // Peer deps — don't bundle
output: [
{ file: 'dist/index.cjs', format: 'cjs', sourcemap: true },
{ file: 'dist/index.esm.js', format: 'esm', sourcemap: true },
],
plugins: [
resolve(),
commonjs(),
typescript({ tsconfig: './tsconfig.json' }),
terser(),
],
},
// Type declarations
{
input: 'src/index.ts',
output: { file: 'dist/index.d.ts', format: 'esm' },
plugins: [dts()],
},
]);
Best for: Publishing npm packages that need CJS + ESM dual output with proper tree-shaking.
Build Speed Comparison (2026)
| Bundler | Dev Cold Start | HMR | Prod Build (500 comp) | Language |
|---|---|---|---|---|
| Turbopack | ~300ms | ~30ms | ~25s | Rust |
| esbuild | ~100ms | N/A | ~2s | Go |
| Vite (dev) | ~200ms | ~20ms | ~8s (Rollup) | JS+Go |
| Rspack | ~400ms | ~50ms | ~10s | Rust |
| Webpack 5 | ~8,000ms | ~400ms | ~50s | JS |
| Parcel 2 | ~2,000ms | ~100ms | ~20s | Rust+JS |
When to Choose
| Scenario | Pick |
|---|---|
| New React / Vue / Svelte project | Vite |
| Next.js app | Turbopack (default) |
| npm library publishing | Rollup |
| CLI tool or Node.js script bundling | esbuild |
| Existing Webpack app (large) | Migrate to Vite or Rspack |
| Monorepo build orchestration | Turborepo (build orchestrator, uses above) |
| Bun-first project | Bun bundler |
| Need Webpack ecosystem (loaders) | Rspack (Webpack-compatible, Rust-based) |
Migration Guide: Webpack → Vite
# 1. Install Vite
npm install -D vite @vitejs/plugin-react
# 2. Add vite.config.ts (see above)
# 3. Update package.json scripts
{
"scripts": {
"dev": "vite", # was: react-scripts start
"build": "vite build", # was: react-scripts build
"preview": "vite preview"
}
}
# 4. Move index.html to root (Vite serves from root)
# 5. Update env var prefix: REACT_APP_ → VITE_
# 6. Update env access: process.env → import.meta.env
# Most migrations take ~1-2 hours for a medium app
# 90%+ of Webpack plugins have Vite equivalents
Compare bundler package health on PkgPulse.
See the live comparison
View vite vs. webpack on PkgPulse →