Webpack and Bundlers Explained
Interview Question: "Explain how webpack works and how it compares to Vite."
How Webpack Works
"Webpack is a module bundler that builds a dependency graph starting from one or more entry points, applies transformations through loaders, applies optimizations through plugins, and outputs one or more bundles."
The Dependency Graph
entry: src/index.js
â
âââ import './App.jsx'
â âââ import './Header.jsx'
â âââ import './styles.css' â needs css-loader
â âââ import './logo.svg' â needs file-loader
âââ import 'react'
âââ import 'react-dom'
Output: dist/bundle.js (everything resolved and concatenated)"Webpack starts at the entry point, follows every
import/require, and builds a complete graph of every module your application needs. Then it bundles everything into output files, replacing module syntax with its own runtime."
Loaders vs Plugins
module.exports = {
module: {
rules: [
{ test: /\.tsx?$/, use: "ts-loader" },
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{ test: /\.svg$/, use: ["@svgr/webpack"] },
],
},
plugins: [
new HtmlWebpackPlugin({ template: "./src/index.html" }),
new MiniCssExtractPlugin(),
new BundleAnalyzerPlugin(),
],
};"Loaders transform individual files â they're per-module transforms. They run bottom-to-top in the array (css-loader parses CSS, then style-loader injects it). Plugins operate on the entire compilation â they hook into webpack's lifecycle events to do things loaders can't: generate HTML, extract CSS into files, analyze bundle size."
Code Splitting
// Route-based splitting with dynamic imports
const Dashboard = React.lazy(() => import("./pages/Dashboard"));
const Settings = React.lazy(() => import("./pages/Settings"));
// Webpack creates separate chunks:
// main.js â shared code
// dashboard.js â loaded when user navigates to /dashboard
// settings.js â loaded when user navigates to /settingsWebpack's splitting strategies:
- Entry points â multiple entry configs create separate bundles
- Dynamic imports â
import()creates a new chunk automatically - SplitChunksPlugin â extracts shared dependencies (e.g., React used by all routes goes into
vendors.js)
Tree Shaking
// math.js
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
export function complexCalculation() { /* 500 lines */ }
// app.js
import { add } from "./math";
console.log(add(1, 2));
// multiply and complexCalculation are eliminated from the bundle"Tree shaking relies on ES module static analysis â webpack can see at build time which exports are used and eliminate dead code. It doesn't work with CommonJS (
require) because those are dynamic. This is whyimport/exportmatters."
Hot Module Replacement (HMR)
"HMR patches modules in the running application without a full page reload. Webpack's dev server maintains a WebSocket connection to the browser. When a file changes, it sends a JSON manifest of what changed, the browser requests the updated modules, and they're hot-swapped in memory."
How Vite Works (and Why It's Faster)
Dev Mode: Native ESM
Browser requests: /src/App.tsx
â
âž
Vite dev server:
1. Transforms App.tsx with esbuild (10-100x faster than Babel)
2. Serves it as a native ES module
3. Browser handles the import graph natively
â
âž
Browser sees: import './Header.tsx'
â Requests /src/Header.tsx from Vite
â Vite transforms on-demand"Vite doesn't bundle in development. The browser loads ES modules natively, and Vite transforms files on-demand with esbuild. Startup is near-instant because Vite only processes files the browser actually requests, not the entire dependency graph."
Why Vite Dev Is Faster Than Webpack Dev
| Webpack | Vite | |
|---|---|---|
| Startup | Bundles everything upfront | Serves files on-demand |
| Transpiler | Babel/ts-loader (JS, slow) | esbuild (Go, 10-100x faster) |
| HMR | Rebuilds affected chunk | Invalidates single module |
| Cold start 10K modules | 30-60 seconds | 1-3 seconds |
Production: Rollup Under the Hood
"Vite uses Rollup for production builds â not esbuild â because Rollup has more mature code splitting, tree shaking, and plugin ecosystem. This means dev and prod use different bundlers, which can occasionally cause behavior differences."
When to Choose Which
| Scenario | Recommendation |
|---|---|
| New project, greenfield | Vite â faster DX, simpler config |
| Large legacy codebase | Webpack â more mature, battle-tested migration paths |
| Library publishing | Rollup or Vite (library mode) |
| Need fine-grained control over bundling | Webpack â more configurable |
| Monorepo with custom requirements | Turborepo + Vite or webpack (depends on needs) |
| SSR framework | Next.js (Turbopack/webpack), Nuxt (Vite) |
Bundle Optimization Strategies
1. Analyze First
# Webpack
npx webpack-bundle-analyzer dist/stats.json
# Vite
npx vite-bundle-visualizer"Never optimize blind. Bundle analysis reveals the actual bottlenecks â usually it's one massive dependency, not your code."
2. Common Optimizations
// Dynamic import for heavy libraries
const { marked } = await import("marked");
// Replace heavy libraries with lighter alternatives
// moment.js (300KB) â date-fns (tree-shakeable) or Temporal API
// lodash (70KB) â lodash-es (tree-shakeable) or native methods
// Externalize large dependencies in library builds
// Don't bundle React into your component library3. Compression
// Vite config for compression
import viteCompression from "vite-plugin-compression";
export default defineConfig({
plugins: [
viteCompression({ algorithm: "brotliCompress" }),
],
});What Interviewers Look For
- Conceptual understanding â dependency graph, module resolution, not just config memorization
- Loaders vs plugins â knowing the difference and when each applies
- Code splitting strategies â route-based, component-based, vendor extraction
- Tree shaking prerequisites â ES modules, side effects,
"sideEffects": falsein package.json - Vite's architecture â why native ESM is faster in dev, why Rollup for prod
Common Mistakes
- Thinking Vite "doesn't bundle" â it doesn't bundle in dev, but uses Rollup for production
- Confusing loaders and plugins in webpack
- Not knowing tree shaking requires ES modules
- Ignoring
sideEffectsin package.json â webpack can't tree-shake without it - Over-splitting â too many tiny chunks cause waterfall requests that are slower than one medium bundle
- Assuming Vite is always better â webpack has more plugins and handles edge cases Vite can't
Follow-Up Questions
"What is Turbopack and where does it fit?"
"Turbopack is Vercel's Rust-based bundler designed for Next.js. It aims to be webpack-compatible but orders of magnitude faster by using incremental computation (only recomputes what changed). It's the successor to webpack within the Next.js ecosystem, though it's still maturing."
"How does tree shaking actually work?"
"It relies on static analysis of ES module
import/exportstatements. The bundler marks all imports, then walks the dependency graph to find which exports are actually used. Unused exports are removed during dead code elimination. ThesideEffectsfield in package.json tells the bundler whether unused imports can be safely removed entirely â some modules have side effects on import (polyfills, CSS)."
"What's the difference between chunk and bundle?"
"A bundle is the final output file. A chunk is an intermediate grouping of modules. An entry chunk is the initial bundle, async chunks are lazy-loaded bundles from dynamic imports, and vendor chunks are extracted shared dependencies. Multiple chunks may end up in a single bundle depending on configuration."
Red Flags
- Cannot explain what a dependency graph is
- Confusing loaders and plugins
- Saying "Vite is just faster webpack" without understanding the architectural difference
- Not knowing what tree shaking requires (ESM, sideEffects)
- Never having used bundle analysis tools