📦 What is a Bundler?
📖 Definition
A Bundler is a tool that combines multiple files and modules into one or a few files. It optimizes various resources such as JavaScript, CSS, and images, and transforms them to be efficiently loaded in browsers. Representative examples include Webpack, Vite, Rollup, and Parcel.
🎯 Understanding Through Analogies
Moving Company Analogy
Comparing a bundler to a moving company:
Before Moving (Before Bundling)
Household Items:
├─ Living Room: Sofa, TV, Table (JavaScript files)
├─ Bedroom: Bed, Desk, Wardrobe (CSS files)
├─ Kitchen: Refrigerator, Dishes (Image files)
└─ Bathroom: Sink, Towels (Font files)
Problems:
- Moving items one by one = Inefficient
- Unorganized = Hard to find
- Waste of space = Slow
Moving Company (Bundler):
1. Categorize items
2. Pack in boxes
3. Label
4. Efficient arrangement
5. Load on truck
After Moving (After Bundling):
📦 Box 1: Furniture (main.js)
📦 Box 2: Kitchen items (styles.css)
📦 Box 3: Small items (assets)
Advantages:
- Transport all at once = Fast
- Organized = Easy to find
- Space efficient = Optimized
Library Analogy
Scattered Books (Before Bundling)
- 1000 books scattered on the floor
- Hard to find
- Waste of space
- Dust accumulation
Library System (Bundler)
1. Categorize by subject
2. Organize on shelves
3. Create index
4. Apply labels
Organized Library (After Bundling)
- Shelves by category
- Fast search
- Space efficiency
- Clean management
Cooking Preparation Analogy
Before Preparing Ingredients (Before Bundling)
Entire Refrigerator:
- 20 types of vegetables
- 5 types of meat
- 30 types of seasonings
- All in different locations
Chef (Bundler):
1. Check today's menu
2. Select only necessary ingredients
3. Prepare
4. Arrange in cooking order
Mise en Place (After Bundling)
- Only necessary ingredients organized
- Ready to cook immediately
- Efficient
- Time-saving
⚙️ How It Works
1. Need for Module System
// Problem: Global pollution
// Loading multiple scripts in HTML
<!DOCTYPE html>
<html>
<head>
<script src="utils.js"></script>
<script src="math.js"></script>
<script src="app.js"></script>
</head>
</html>
// utils.js
var name = 'Utils';
function log(msg) {
console.log(msg);
}
// math.js
var name = 'Math'; // ❌ Conflict! Overwrites name from utils.js
function add(a, b) {
return a + b;
}
// app.js
console.log(name); // 'Math' is printed (expected: 'Utils')
log('Hello'); // Works but unclear where the function comes from
// Problems:
// 1. Variable name conflicts
// 2. Dependency order matters
// 3. Global namespace pollution
// 4. Many HTTP requests for many files
// 5. Loads unused code
2. Module System
// ✅ ES6 Modules (ESM)
// utils.js
export const name = 'Utils';
export function log(msg) {
console.log(msg);
}
// math.js
export const name = 'Math';
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// app.js
import { log } from './utils.js';
import { add, subtract } from './math.js';
log('Hello'); // Clear!
console.log(add(1, 2)); // 3
// Advantages:
// - Namespace isolation
// - Explicit dependencies
// - Tree Shaking possible
// - Easy to reuse
// But problems:
// 1. Not supported in older browsers
// 2. Still many HTTP requests
// 3. Slow module resolution
// → Bundler needed!
3. Bundler Operation Process
// Step 1: Entry Point
// webpack.config.js
module.exports = {
entry: './src/index.js' // Starting point
};
// Step 2: Dependency Graph
// index.js
import { render } from './render.js';
import './styles.css';
import logo from './logo.png';
// render.js
import { createElement } from './dom.js';
import { formatDate } from './utils.js';
// dom.js
import { debounce } from './helpers.js';
// Dependency Tree:
index.js
├─ render.js
│ ├─ dom.js
│ │ └─ helpers.js
│ └─ utils.js
├─ styles.css
└─ logo.png
// Step 3: Loaders (Transformation)
// - CSS → JavaScript
// - SCSS → CSS → JavaScript
// - Images → Base64 or file
// - TypeScript → JavaScript
// - JSX → JavaScript
// Step 4: Plugins (Additional tasks)
// - HTML generation
// - Code minification
// - Environment variable injection
// - Sourcemap generation
// Step 5: Output
// dist/
// ├─ bundle.js (all JS combined)
// ├─ styles.css (extracted CSS)
// └─ logo.abc123.png (hash added)
// Result:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="dist/styles.css">
</head>
<body>
<script src="dist/bundle.js"></script>
<!-- One file! -->
</body>
</html>
4. Tree Shaking
// math.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
export function divide(a, b) {
return a / b;
}
// app.js
import { add, subtract } from './math.js';
console.log(add(1, 2)); // Used
console.log(subtract(5, 3)); // Used
// multiply and divide are not used!
// Bundler (After Tree Shaking)
// bundle.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
console.log(add(1, 2));
console.log(subtract(5, 3));
// multiply and divide are removed!
// Bundle size reduced!
// Tree Shaking Requirements:
// 1. Use ES6 Modules
// 2. Code without side effects
// 3. Production mode
5. Code Splitting
// Code Splitting
// ❌ All code in one bundle
import Home from './pages/Home';
import About from './pages/About';
import Dashboard from './pages/Dashboard';
import Admin from './pages/Admin';
// bundle.js (2MB) - Too large!
// ✅ Split with dynamic import
// app.js
const routes = {
'/': () => import('./pages/Home'),
'/about': () => import('./pages/About'),
'/dashboard': () => import('./pages/Dashboard'),
'/admin': () => import('./pages/Admin')
};
// Router
async function navigate(path) {
const loadPage = routes[path];
if (loadPage) {
const module = await loadPage();
module.default.render();
}
}
// Result:
// bundle.js (100KB) - Base code
// home.chunk.js (50KB) - Home page only
// about.chunk.js (30KB) - About page only
// dashboard.chunk.js (200KB) - Dashboard only
// admin.chunk.js (500KB) - Admin only
// Advantages:
// - Fast initial loading
// - Load only when needed
// - Users feel it's faster
// Code Splitting in React
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Dashboard />
</Suspense>
);
}