While digging into app size on a production Flutter EHR, I found the builds were running with --no-tree-shake-icons. The flag had been added long ago because the build demanded it: just three non-const IconData constructions (in PDF theming code) were enough to make the icon tree-shaker refuse to run at all.
The cost of that flag: the app shipped roughly 7,447 icon glyphs from one icon pack when only 52 were actually used — about 3.7 MB of icon fonts in every install.
Fixing the three call sites and re-enabling tree-shaking shrank five of the six bundled fonts by ~98% immediately. But one vendored icon font stubbornly stayed at only a ~19% reduction. The reason taught me how the tree-shaker actually works: those icons were being resolved through runtime string-map lookups — a map from icon-name strings to IconData. The compiler can’t trace which entries a runtime string might hit, so it conservatively kept nearly everything.
Rewriting that map into const getters — one per icon actually referenced — made every usage statically traceable. That single font went from 1.22 MB to 3.8 KB, a 99.7% cut. Total icon-font weight across the app: ~3.7 MB down to ~48 KB.
The rules this left me with:
- Treat
--no-tree-shake-iconsin your build scripts as a debt marker, not a setting. Find out why it’s there. IconDatamust be const-constructible everywhere; one dynamic construction poisons the whole optimization.- Icon-by-string-name APIs are convenient and quietly cost megabytes. If you need dynamic icons, enumerate the allowed ones as consts and map to those.