Webpack vs Vite vs Rollup: JS Bundler Guide
Compare Webpack, Vite, and Rollup for JavaScript bundling. Covers architecture differences, dev server performance, build output optimization, plugin ecosystems, configuration complexity, migration strategies, and choosing the right bundler for your project.
Webpack, Vite, and Rollup solve the same problem differently. This guide compares their architecture, performance, configuration, and ideal use cases with practical examples.
For bundler internals, see JavaScript Bundlers: An Advanced Architecture.
Architecture Comparison
// WEBPACK: Bundle-first architecture
// Bundles everything before serving, even in development
// webpack.config.js
module.exports = {
entry: "./src/index.js",
output: { filename: "bundle.js", path: __dirname + "/dist" },
module: {
rules: [
{ test: /\.jsx?$/, use: "babel-loader" },
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
],
},
devServer: {
hot: true,
port: 3000,
},
};
// VITE: Native ESM in dev, Rollup for production
// vite.config.js
import { defineConfig } from "vite";
export default defineConfig({
server: { port: 3000 },
build: {
rollupOptions: {
output: { manualChunks: { vendor: ["react", "react-dom"] } },
},
},
});
// ROLLUP: Library-focused, best tree shaking
// rollup.config.js
export default {
input: "src/index.js",
output: [
{ file: "dist/bundle.cjs.js", format: "cjs" },
{ file: "dist/bundle.esm.js", format: "es" },
{ file: "dist/bundle.umd.js", format: "umd", name: "MyLib" },
],
plugins: [resolve(), commonjs(), terser()],
};| Feature | Webpack | Vite | Rollup |
|---|---|---|---|
| Dev server approach | Bundles all, serves bundle | Native ESM, transforms on demand | No built-in dev server |
| Cold start (large app) | 10-30s | 0.5-2s | N/A |
| HMR speed | 1-5s | instant (<100ms) | N/A |
| Production bundler | Webpack itself | Rollup (under the hood) | Rollup |
| Tree shaking | Good | Excellent (Rollup) | Best |
| Code splitting | Excellent | Good | Good |
| Plugin ecosystem | Largest (2000+) | Growing (500+) | Moderate (300+) |
| Config complexity | High | Low | Medium |
| Asset handling | Loaders for everything | Built-in (CSS, JSON, assets) | Plugins needed |
| Multiple output formats | Limited | ESM + legacy | CJS, ESM, UMD, IIFE |
Development Server Performance
// WHY VITE IS FASTER IN DEVELOPMENT
// Webpack (traditional approach):
// 1. Read ALL source files
// 2. Build complete dependency graph
// 3. Transform ALL modules (Babel, TypeScript, etc.)
// 4. Bundle into single file
// 5. Serve bundle to browser
// Time: O(total_modules) on every startup
// Vite (ESM approach):
// 1. Start server immediately
// 2. Browser requests index.html
// 3. Browser parses <script type="module" src="/src/main.ts">
// 4. Browser requests /src/main.ts
// 5. Vite transforms ONLY main.ts (using esbuild, 10-100x faster than Babel)
// 6. Browser follows imports, requests only what's needed
// Time: O(route_modules) - only modules for current page
// HMR comparison
// Webpack HMR: rebuild affected chunk -> send full chunk -> replace
// Vite HMR: transform single file -> send module update -> hot swap
// Vite HMR is independent of app size
// Practical benchmark (1000 module app):
// Webpack cold start: ~18 seconds
// Vite cold start: ~1.2 seconds (15x faster)
// Webpack HMR: ~2.5 seconds
// Vite HMR: ~50ms (50x faster)Webpack Advanced Configuration
// webpack.config.js - production optimized
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
mode: "production",
entry: {
app: "./src/index.js",
vendor: ["react", "react-dom"],
},
output: {
filename: "[name].[contenthash:8].js",
chunkFilename: "[name].[contenthash:8].chunk.js",
path: path.resolve(__dirname, "dist"),
clean: true,
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: { drop_console: true, passes: 2 },
mangle: { safari10: true },
},
parallel: true,
}),
new CssMinimizerPlugin(),
],
splitChunks: {
chunks: "all",
cacheGroups: {
framework: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: "framework",
priority: 20,
},
commons: {
minChunks: 2,
priority: 10,
reuseExistingChunk: true,
},
},
},
runtimeChunk: "single",
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", { modules: false, useBuiltIns: "usage", corejs: 3 }],
"@babel/preset-react",
],
cacheDirectory: true,
},
},
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[contenthash:8].css",
}),
],
};Vite Advanced Configuration
// vite.config.js - production optimized
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig(({ mode }) => ({
plugins: [
react(),
mode === "analyze" && visualizer({
filename: "dist/stats.html",
open: true,
gzipSize: true,
}),
].filter(Boolean),
build: {
target: "es2020",
minify: "terser",
sourcemap: true,
cssMinify: true,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("node_modules")) {
if (id.includes("react")) return "framework";
if (id.includes("lodash")) return "vendor-lodash";
return "vendor";
}
},
chunkFileNames: "assets/[name]-[hash].js",
entryFileNames: "assets/[name]-[hash].js",
assetFileNames: "assets/[name]-[hash].[ext]",
},
},
chunkSizeWarningLimit: 500,
},
server: {
hmr: { overlay: true },
proxy: {
"/api": {
target: "http://localhost:4000",
changeOrigin: true,
},
},
},
resolve: {
alias: {
"@": "/src",
"@components": "/src/components",
"@utils": "/src/utils",
},
},
}));Rollup for Library Authoring
// rollup.config.js - library build
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";
import pkg from "./package.json" assert { type: "json" };
const external = [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
];
export default [
// Main build
{
input: "src/index.ts",
external,
output: [
{
file: pkg.main,
format: "cjs",
sourcemap: true,
exports: "named",
},
{
file: pkg.module,
format: "es",
sourcemap: true,
},
],
plugins: [
resolve(),
commonjs(),
typescript({ tsconfig: "./tsconfig.json" }),
terser(),
],
},
// Type declarations
{
input: "src/index.ts",
output: { file: "dist/index.d.ts", format: "es" },
plugins: [dts()],
},
];Migration: Webpack to Vite
// Step 1: Install Vite
// npm install -D vite @vitejs/plugin-react
// Step 2: Create vite.config.js (maps webpack concepts)
// Webpack loaders -> Vite handles natively or via plugins
// style-loader + css-loader -> Built-in CSS support
// file-loader -> Built-in asset handling
// babel-loader -> esbuild (default) or @vitejs/plugin-react
// Webpack DefinePlugin -> Vite define option
// webpack: new webpack.DefinePlugin({ 'process.env.API_URL': JSON.stringify(url) })
// vite:
export default defineConfig({
define: {
"process.env.API_URL": JSON.stringify(process.env.API_URL),
},
});
// Step 3: Move index.html to root (Vite requirement)
// Vite uses index.html as entry point, not a webpack HtmlPlugin template
// Add: <script type="module" src="/src/main.jsx"></script>
// Step 4: Update environment variables
// webpack: process.env.REACT_APP_*
// vite: import.meta.env.VITE_*
// Find and replace across codebase
// Step 5: Update require() to import
// Vite requires ES module syntax
// require("./style.css") -> import "./style.css"
// require("./logo.png") -> import logo from "./logo.png"
// Step 6: Handle Node.js globals
// Vite does not polyfill Node.js globals (process, Buffer, etc.)
export default defineConfig({
define: {
"process.env": {},
global: "globalThis",
},
});Rune AI
Key Insights
- Vite is 10-50x faster in development: Native ESM serving and esbuild transforms eliminate the bundling step during development, making cold start and HMR nearly instant
- Rollup produces the smallest bundles: Superior tree shaking and scope hoisting make Rollup the best choice for library output, and Vite inherits this for production builds
- Webpack has the largest plugin ecosystem: Over 2000 plugins and loaders handle virtually any use case, making it the safest choice for complex enterprise applications
- Migration from Webpack to Vite is straightforward: Most webpack concepts map directly to Vite equivalents, with the main changes being index.html placement, environment variable prefixes, and removing Node.js polyfills
- Choose based on project type: Vite for applications, Rollup for libraries, Webpack for legacy projects or when specific loaders are required
Frequently Asked Questions
Should I migrate from Webpack to Vite?
Which bundler produces the smallest output?
Can I use Vite without a framework like React or Vue?
Why does Rollup not have a dev server?
How do I handle CSS Modules across bundlers?
Conclusion
Webpack excels at complex applications with extensive plugin needs. Vite provides the best development experience with near-instant HMR. Rollup produces the most optimized output for libraries. For new projects, start with Vite. For bundler internals, see JavaScript Bundlers: An Advanced Architecture. For tree shaking configuration, see JavaScript Tree Shaking: A Complete Tutorial.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.