Bundlers — What They Actually Do
IntermediateA bundler resolves your import graph, transforms each file, and emits chunks the browser can load efficiently. Tree shaking and code splitting are the two outputs that matter for users.
Overview
Browsers can load ES modules natively, so it is fair to ask why a bundler exists at all. The answer is everything around the modules: hundreds of separate requests are slow, node_modules resolution is not a browser concept, TypeScript and JSX need transforming, and unused code should not ship. A bundler does all of that and emits a small number of hashed files. You rarely configure one from scratch — Next and Vite come pre-configured — but understanding tree shaking and code splitting is what lets you diagnose a 2MB bundle.
Tree Shaking
Dropping code nothing imports. It only works under specific conditions, which is why it often silently does not.
// This ships one function, not the whole library
import { debounce } from 'lodash-es'
// This ships all of lodash — CommonJS cannot be statically analysed
const _ = require('lodash')
import _ from 'lodash'
// Requirements for tree shaking to work:
// 1. ES modules (static import/export), not CommonJS
// 2. No side effects at module top level
// 3. "sideEffects": false in the package's package.json
// A top-level side effect defeats it:
// utils.js
export function unused() { }
window.analytics = init() // runs on import, so the module is kept
// Barrel files are the usual culprit in application code:
// components/index.ts re-exporting 80 components means importing
// one can pull the graph of all 80 into the analysis.
import { Button } from '@/components' // risky
import { Button } from '@/components/Button' // predictableCode Splitting
Not everything is needed on first paint. A dynamic import becomes a separate chunk fetched on demand.
// Static — always in the initial bundle
import { Editor } from './Editor'
// Dynamic — its own chunk, fetched when this line runs
const { Editor } = await import('./Editor')
// In React
const Editor = lazy(() => import('./Editor'))
<Suspense fallback={<Skeleton />}><Editor /></Suspense>
// In Next
const Editor = dynamic(() => import('./Editor'), {
ssr: false, // for anything that touches window
loading: () => <Skeleton />,
})
// What is worth splitting: the code editor, chart libraries,
// markdown renderers, PDF generators, admin-only routes, modals.
// Anything heavy that most visitors never open.
// Routes split automatically in Next — each page is its own chunk.Diagnosing Size
Measure before optimising. The answer is almost always one oversized dependency.
# Next
ANALYZE=true npm run build # with @next/bundle-analyzer
# Any project
npx source-map-explorer 'dist/**/*.js'
npx vite-bundle-visualizer
// The usual offenders and their fixes:
// moment -> date-fns, or Intl
// lodash -> lodash-es, or a native method
// an icon set -> import individual icons, never the barrel
// a chart library -> dynamic import
// a duplicate dep -> two versions of react in the tree; npm ls react
// Budgets, so a regression is caught in review rather than in a
// support ticket: fail CI if the initial chunk exceeds a threshold.Key Points to Remember
- 1A bundler resolves the import graph, transforms files and emits a few hashed chunks instead of hundreds of requests
- 2Tree shaking requires ES modules, no top-level side effects, and a sideEffects declaration — CommonJS defeats it
- 3Barrel files can pull far more into the graph than the one component you imported
- 4A dynamic import creates a separate chunk loaded on demand — split editors, charts, modals and admin routes
- 5Measure with a bundle analyzer before optimising; the cause is usually one oversized dependency
Interview Questions
Sign in to ask AriaWhat is tree shaking and what stops it from working?
How does a dynamic import differ from a static one at build time?
Your initial JavaScript bundle is 2MB. How do you find out why?
Ask Aria about Bundlers — What They Actually Do
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.