# Bundle Size Optimization
## Table of Contents
- [Analysis Tools](#analysis-tools)
- [Heavy Dependencies](#heavy-dependencies)
- [Code Splitting](#code-splitting)
- [Tree Shaking](#tree-shaking)
---
## Analysis Tools
```bash
# Webpack - generates interactive treemap
npx webpack-bundle-analyzer dist/stats.json
# Generate stats file first
webpack --profile --json > dist/stats.json
# Vite
npx vite-bundle-visualizer
# Source map explorer
npx source-map-explorer dist/**/*.js
# Check package size before adding
npx bundlephobia lodash
```
---
## Heavy Dependencies
### moment → date-fns/dayjs
```javascript
// Before: moment (67KB)
import moment from 'moment';
moment(date).format('YYYY-MM-DD');
// After: date-fns (tree-shakeable, ~2KB per function)
import { format } from 'date-fns';
format(date, 'yyyy-MM-dd');
// After: dayjs (2KB total, moment-compatible API)
import dayjs from 'dayjs';
dayjs(date).format('YYYY-MM-DD');
```
### lodash → cherry-pick or native
```javascript
// Before: entire lodash (72KB)
import _ from 'lodash';
_.uniq(array);
_.debounce(fn, 300);
// After: cherry-pick (2KB each)
import uniq from 'lodash/uniq';
import debounce from 'lodash/debounce';
// After: native alternatives
[...new Set(array)]; // uniq
// debounce - use custom or lodash-es/debounce
```
### Other common swaps
| Heavy | Light Alternative |
|-------|-------------------|
| `axios` (13KB) | `fetch` (native) or `ky` (3KB) |
| `uuid` (4KB) | `crypto.randomUUID()` (native) |
| `classnames` (1KB) | template literals |
---
## Code Splitting
### React.lazy
```javascript
import { lazy, Suspense } from 'react';
const Chart = lazy(() => import('./Chart'));
const AdminPanel = lazy(() => import('./AdminPanel'));
function App() {
return (
}>
{showChart && }
{isAdmin && }
);
}
```
### Next.js dynamic
```javascript
import dynamic from 'next/dynamic';
// Client-only component
const Map = dynamic(() => import('./Map'), { ssr: false });
// With loading state
const Chart = dynamic(() => import('./Chart'), {
loading: () =>
});
```
### Route-based splitting (automatic in most frameworks)
```javascript
// Next.js - each page is a separate chunk
// pages/dashboard.js → chunks/pages/dashboard.js
// pages/admin.js → chunks/pages/admin.js
// React Router with lazy
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Admin = lazy(() => import('./pages/Admin'));
```
### Manual chunks (Vite/Rollup)
```javascript
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
charts: ['recharts', 'd3'],
}
}
}
}
};
```
---
## Tree Shaking
### Enable in webpack
```javascript
// webpack.config.js
module.exports = {
mode: 'production', // enables tree shaking
optimization: {
usedExports: true,
sideEffects: true,
}
};
```
### Mark package as side-effect free
```json
// package.json
{
"sideEffects": false
}
// Or specify files with side effects
{
"sideEffects": ["*.css", "*.scss"]
}
```
### Write tree-shakeable exports
```javascript
// Bad: default export of object
export default { foo, bar, baz };
// Good: named exports
export { foo, bar, baz };
```