Production Optimization
build-tooling1-1 previewed minification as one of three separate jobs. This chapter delivers the full production-optimization picture — and closes the loop build-tooling1-2 opened when it explained exactly why ESM, not CommonJS, is what makes one of these techniques possible at all.
Tree Shaking — Removing Code That's Never Used
build-tooling1-2's own static-vs-dynamic distinction is the entire mechanism here. Because a bundler can determine with certainty which exports from an ESM module are actually imported anywhere in the reachable dependency graph, it can safely remove any exported code nothing ever actually imports — genuine dead-code elimination at the module level.
This is exactly why build-tooling1-2 singled out CommonJS as unreliable for this purpose — a bundler can't be certain whether a possibly-conditional, possibly-computed require() call will actually execute, so removing seemingly-unused CommonJS code risks breaking real functionality a hidden runtime path genuinely needed.
Code Splitting — Not Loading Everything Upfront
A different, complementary optimization: rather than removing unused code, code splitting deliberately breaks the output into multiple separate chunks, loaded on demand instead of all at once. A rarely-visited settings page doesn't need to be downloaded by every user on their very first load — it can be its own chunk, fetched only when that route is actually reached.
This is the bundle-level version of exactly the principle web-vitals1-6 already covered for images and below-the-fold content: don't make a user download something they don't need yet.
Minification — Shrinking What Remains
build-tooling1-1 previewed this briefly; here's the full picture: renaming variables to shorter names, stripping whitespace and comments, and more aggressive transforms like eliminating genuinely unreachable branches (if (false) { ... }) or inlining small functions. Real tools: Terser — the long-standing standard JS minifier, itself written in JavaScript, a genuine case where a JS-native tool remains the established choice for a specific job rather than everything having moved to a compiled language — and esbuild's own built-in minifier, applying build-tooling1-6's own speed advantage directly to this step too.
Worth being explicit: minification is genuinely separate from tree shaking and code splitting, not just another word for "making the bundle smaller." It shrinks whatever code survives those two earlier decisions, regardless of when that code loads.
How These Three Fit Together
| Technique | Question it answers | Real mechanism |
|---|---|---|
| Tree shaking | Is this code used anywhere at all? | Static ESM analysis (build-tooling1-2) removes genuinely unreachable exports |
| Code splitting | Is this code needed right now? | Dynamic import() creates separate, on-demand-loaded chunks |
| Minification | Can this remaining code be made smaller? | Variable renaming, whitespace removal, dead-branch elimination |
"sideEffects": false in package.json specifically to tell the bundler it's safe to tree-shake aggressively — without that marker, even genuinely static ESM code may not get the full tree-shaking benefit this chapter otherwise promises.
build-tooling1-9's own capstone configures a real project's build pipeline end to end, combining every technique from this entire course.
Coding Challenges
Given this chapter's own five-function utils.js example, where app.js only imports formatPrice and slugify, explain what tree shaking does here, and explain why this is only reliably possible because the import is ESM rather than CommonJS.
📄 View solutionTree shaking and code splitting are often both loosely described as "reducing bundle size," but this chapter frames them as answering genuinely different questions. Using this chapter's own three-technique table, explain the real difference between the two.
📄 View solutionExplain the "sideEffects: false" gotcha this chapter names: why can't a bundler always safely tree-shake even statically-analyzable ESM code, and what does this package.json field do to help?
📄 View solutionChapter 8 Quick Reference
- Tree shaking — removes exports nothing ever imports, relying entirely on ESM's static analyzability (build-tooling1-2); unreliable with CommonJS
- Code splitting — defers loading code until it's actually needed, via dynamic import(); the bundle-level version of web-vitals1-6's own lazy-loading principle
- Minification — shrinks whatever code remains (Terser, or esbuild's own faster built-in minifier), independent of when that code loads
- Three genuinely different questions: used at all? / needed right now? / can it be smaller? — not three names for the same optimization
- Module side effects can block full tree-shaking even with ESM —
"sideEffects": falsein package.json tells a bundler it's safe to be aggressive - Next chapter: Capstone — Configuring a Real Project's Build Pipeline