Bundling & Code-Splitting — Cheat Sheet

FE Lead prep · ref 05

The goal & the three levers

Ship the least JS that makes THIS page work. JS = downloaded + parsed + compiled + executed on the main thread (the costly part).
1. Code-split (route > component) · 2. Tree-shake (drop unused) · 3. Prune/swap heavy & dup deps → guard in CI.

Code-splitting

Tree-shaking

Bundlers — have an opinion

ToolLangUse / trade-off
WebpackJSMature, vast plugins, handles anything · complex config, slow start
RollupJSClean ESM + best tree-shaking → publishing libraries; was Vite's prod bundler
esbuildGoExtreme speed; was Vite's dep pre-bundler · thin plugin/split support
Oxc+RolldownRustOxc = Rust toolchain (parser, oxlint, fmt, minifier, resolver); Rolldown = bundler on it, Rollup-compatible. New engine under Vite
ViteDev = native ESM (no bundling). Vite 8 (Mar'26) unified on Rolldown (was esbuild-dev/Rollup-prod). SPA default
TurbopackRustWebpack's successor, incremental, Next.js/Vercel
Why Vite is instant: Webpack bundles whole app before serving (start scales w/ size). Vite serves source over native ESM, transforms a module only when requested; HMR invalidates one module. 2026: toolchain consolidating onto one Rust core — Oxc powers Rolldown + oxlint (≈50–100× faster than ESLint); Vite 8 unified dev+prod on Rolldown, killing the dev/prod "split-brain." SWC = Rust Babel-replacement.

Find & govern

Lead one-liners (memorize)

Goal  “JS is parsed & run on the main thread — the most expensive byte. So I optimize what we ship: route-split, tree-shake, and a CI size budget.”
Tree-shake  “Only works because ESM imports are static — CJS can't be shaken. sideEffects:false drops whole unused files. Killers: barrel files & whole-namespace imports.”
Vite  “Webpack bundles the whole app before serving; Vite serves source over native ESM — that's the near-instant dev start. Vite 8 unified dev+prod onto Rolldown (Rust, on Oxc), ending the esbuild/Rollup split-brain.”
2026  “The toolchain is consolidating onto one Rust core — Oxc (parser/resolver) powers Rolldown, oxlint (~50–100× ESLint), and oxfmt — shared infra instead of re-implemented per tool.”
Governance  “Across many teams it's a budget, not a cleanup: route-split + de-dupe + a size-limit CI gate that fails the PR.”
Split spinner  “Splitting moved the cost from load to navigation; I pay it back with prefetch-on-intent (hover/viewport) and keep the old page alive with startTransition — instant click, no re-bloat.”

Sources: web.dev — code-splitting · webpack — tree shaking · vite.dev — why Vite · MDN — tree shaking

Don't fail the interview

1. JS is the most expensive byte (executed on main thread) — "ship less JS," not "fewer bytes."
2. Split point = dynamic import(). Route-split first, then heavy below-fold. Static import = initial bundle.
3. Tree-shaking needs ESM — CommonJS can't be shaken. Prefer the -es/ESM build of a dep.
4. sideEffects:false can delete your CSS — list it: "sideEffects":["*.css"].
5. Tree-shaking killers: CJS deps · barrel index.js · whole-namespace import (import _ from 'lodash'). Verify in analyzer.
6. Over-splitting = request waterfall (many round trips + spinners). Don't lazy-load per component. Same for vendor: one giant chunk busts cache on any dep bump, too many = waterfall — split into a few groups.
7. Don't migrate bundlers for fashion — what you ship beats which tool ships it. Migrate on measured pain.
8. Route-split spinner = cost moved to navigation, not added. Fix with prefetch-on-intent + startTransition, not by reverting. Don't eager-prefetch everything (mobile data; respect Save-Data).