The Crime Scene
The 4-Step Investigation Process
- Identify: Find the largest block in the visualizer. In this case: @mui/material + @mui/icons-material.
- Understand: Why is the entire library included? Because of import * as Material from '@mui/material' — a wildcard import used to build a namespaced UI object. Tree-shaking can't follow it.
- Confirm: Comment out the import, rebuild. Bundle drops from 5 MB to 811 KB immediately. Diagnosis confirmed.
- Fix properly: Replace wildcard imports with named imports for only the components actually used. Bundle stabilizes at 878 KB.
The Patterns That Bloat Bundles
- Wildcard + namespace pattern (import * as X; export const Ui = \{ X \}) defeats tree-shaking entirely. Beautiful DX, terrible for bundle size.
- Non-ESM libraries (like classic Lodash) can't be tree-shaken regardless of how you import them. Run npx is-esm [package] to check before you commit.
- Duplicate-purpose libraries: This project had three separate date libraries (date-fns, Moment, Luxon) doing the same job. Consolidating to date-fns alone cut another 20% off the bundle.
- Transitive dependencies sneak in through other packages. Use npx npm-why [package] to find who's actually pulling them in.
