Introduction
The JavaScript build tooling landscape has transformed dramatically. Where webpack once dominated, a new generation of tools built on native ES modules and compiled languages (Rust, Go) delivers 10-100x faster builds. This guide covers the major tools, their strengths, and when to use each.
Why Build Tools Matter
Modern JavaScript projects need build tools to:
- Bundle many files into fewer HTTP requests
- Transform TypeScript, JSX, and modern syntax for older browsers
- Tree-shake unused code to reduce bundle size
- Optimize assets (minification, compression, code splitting)
- Enable Hot Module Replacement (HMR) during development
Vite: The Modern Standard
Vite is the most popular modern build tool. It uses native ES modules in development (no bundling needed) and Rollup for production builds.
Why Vite is Fast
- Dev server: serves files as native ES modules — no bundling, instant startup
- HMR: updates only the changed module, not the whole bundle
- Production: uses Rollup with optimizations
Setup
# Create a new project
npm create vite@latest my-app -- --template react
npm create vite@latest my-app -- --template vue
npm create vite@latest my-app -- --template vanilla-ts
cd my-app
npm install
npm run dev # dev server
npm run build # production build
npm run preview # preview production build
vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
// Dev server
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://localhost:4000',
changeOrigin: true,
},
},
},
// Build options
build: {
outDir: 'dist',
sourcemap: true,
minify: 'esbuild', // or 'terser' for better compression
rollupOptions: {
output: {
// Manual chunk splitting
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'date-fns'],
},
},
},
},
// Path aliases
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
},
},
// Environment variables
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
},
});
Vite Plugins
# Popular plugins
npm install @vitejs/plugin-react # React with Fast Refresh
npm install @vitejs/plugin-vue # Vue 3
npm install vite-plugin-pwa # PWA support
npm install vite-tsconfig-paths # TypeScript path aliases
npm install rollup-plugin-visualizer # Bundle size visualization
import { visualizer } from 'rollup-plugin-visualizer';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [
react(),
VitePWA({ registerType: 'autoUpdate' }),
visualizer({ open: true }), // opens bundle analysis after build
],
});
esbuild: The Speed Champion
esbuild is written in Go and is 10-100x faster than webpack. It’s used internally by Vite for dependency pre-bundling and TypeScript transpilation.
Speed Comparison
Bundle a 3MB React app:
webpack 5: ~30 seconds
Rollup: ~8 seconds
Parcel: ~5 seconds
esbuild: ~0.3 seconds ⚡ (100x faster than webpack)
Basic Usage
// build.mjs
import * as esbuild from 'esbuild';
// Simple bundle
await esbuild.build({
entryPoints: ['src/index.tsx'],
bundle: true,
outfile: 'dist/bundle.js',
minify: true,
sourcemap: true,
target: ['es2020', 'chrome90', 'firefox88'],
format: 'esm',
jsx: 'automatic', // React 17+ automatic JSX transform
});
// Multiple entry points
await esbuild.build({
entryPoints: ['src/app.ts', 'src/worker.ts'],
bundle: true,
outdir: 'dist',
splitting: true, // code splitting
format: 'esm',
chunkNames: 'chunks/[name]-[hash]',
});
Dev Server with esbuild
import * as esbuild from 'esbuild';
const ctx = await esbuild.context({
entryPoints: ['src/index.tsx'],
bundle: true,
outfile: 'dist/bundle.js',
sourcemap: true,
});
// Watch mode
await ctx.watch();
// Dev server
const { host, port } = await ctx.serve({
servedir: 'public',
port: 3000,
});
console.log(`Dev server: http://${host}:${port}`);
When to Use esbuild
- Building libraries (fast, minimal output)
- CI/CD pipelines where build speed matters
- Simple applications without complex plugin needs
- As a TypeScript transpiler (faster than
tsc)
esbuild Limitations
- No CSS modules support (use PostCSS separately)
- Limited plugin ecosystem vs webpack/Rollup
- No HMR built-in (use with a separate dev server)
Turbopack: Next.js’s Bundler
Turbopack is Vercel’s Rust-based bundler, designed as the successor to webpack for Next.js.
Key Features
- Written in Rust — extremely fast
- Incremental compilation (only rebuilds what changed)
- Optimized for Next.js App Router
- Lazy compilation (only compiles what’s requested)
Usage with Next.js
# Create Next.js app with Turbopack
npx create-next-app@latest --turbo
# Or enable in existing project
# next.config.js
module.exports = {
experimental: {
turbo: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
},
};
# Dev with Turbopack
next dev --turbo
# Build (still uses webpack for production as of 2026)
next build
Turbopack vs webpack (Next.js)
| Metric | webpack | Turbopack |
|---|---|---|
| Cold start | ~10s | ~1.5s |
| HMR update | ~500ms | ~50ms |
| Large app startup | ~30s | ~3s |
Rollup: The Library Bundler
Rollup excels at bundling JavaScript libraries. It produces clean, tree-shaken output and supports multiple output formats.
When to Use Rollup
- Building npm libraries
- When you need multiple output formats (ESM, CJS, UMD)
- When bundle size is critical
rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import terser from '@rollup/plugin-terser';
import dts from 'rollup-plugin-dts';
export default [
// Main bundle
{
input: 'src/index.ts',
output: [
{
file: 'dist/index.cjs',
format: 'cjs',
sourcemap: true,
},
{
file: 'dist/index.mjs',
format: 'esm',
sourcemap: true,
},
],
plugins: [
resolve(),
commonjs(),
typescript(),
terser(),
],
external: ['react', 'react-dom'], // don't bundle peer deps
},
// Type declarations
{
input: 'src/index.ts',
output: { file: 'dist/index.d.ts', format: 'esm' },
plugins: [dts()],
},
];
Webpack: Still Relevant
webpack remains the most feature-complete bundler with the largest plugin ecosystem. Use it when you need:
- Complex code splitting strategies
- Specific loaders not available elsewhere
- Module Federation (micro-frontends)
- Legacy project compatibility
// webpack.config.js
module.exports = {
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
},
module: {
rules: [
{ test: /\.tsx?$/, use: 'ts-loader' },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
],
},
optimization: {
splitChunks: { chunks: 'all' },
},
};
Choosing the Right Tool
| Scenario | Recommended Tool |
|---|---|
| New React/Vue/Svelte app | Vite |
| Next.js app | Next.js built-in (Turbopack in dev) |
| npm library | Rollup or tsup |
| Need maximum build speed | esbuild |
| Complex enterprise app | webpack (or Vite with plugins) |
| Simple TypeScript transpilation | esbuild |
| Remix app | Vite |
Bundle Analysis
Always analyze your bundle size:
# Vite
npm install rollup-plugin-visualizer
# Add to vite.config.ts, run build, opens browser
# webpack
npm install webpack-bundle-analyzer
# esbuild
npm install esbuild-bundle-analyzer
Resources
- Vite Documentation
- esbuild Documentation
- Turbopack Documentation
- Rollup Documentation
- Bundlephobia — check package sizes before adding
Comments