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. See Javascript Guide for more context. See Javascript Guide for more context.
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