skip to content

After a telecom self-service app adopts its design system's icon package, the bundle grows by hundreds of kilobytes although it uses twelve icons; what causes this, and how should the package be shaped?

level: seniorimportance: should knowfreq 35%

answer

  1. what the bundler can prove
  2. lookup by name string
  3. registration at import time
  4. one module per icon
  5. a budget on a one-icon fixture

basics

~20 s

The package defeats tree-shaking - usually a name-string lookup over a map of every icon, a single file, or registration on import. Ship one module per icon, imported statically, keep name-based lookup opt-in, and guard it with a size budget.

solid answer

~50 s

The bundler can drop an icon only if it can prove nothing uses it. The usual culprit is an icon component that takes a **name string** and looks it up in an object holding every icon: the bundler cannot know which strings will be passed at runtime, so it keeps the whole map. Other causes are one concatenated file of all icons, or a module that registers every icon when imported. The fix is **one module per icon**, imported statically by name in app code, so references are visible at build time. If the product genuinely needs icons chosen by name from server content, make that an opt-in path that loads icons on demand. Then add a **size budget** - a fixture app importing one icon must stay small - so the regression cannot return silently.

go deeper

for a junior

Recall that an app should import only the icons it uses, and that a single component choosing icons by name can pull in the whole set.

for a middle

Explain why a runtime name lookup, one large file or import-time registration each stops the bundler from dropping unused icons.

for a senior

Walk through diagnosing the bloat with a bundle report, reshaping the package into per-icon modules, keeping a dynamic path opt-in, and guarding with a size budget.

for a principal

Decide how the system's asset packages - icons, fonts, illustrations - guarantee pay-for-what-you-use across web and native consumers.

## How an icon package defeats tree-shaking **Tree-shaking** removes modules a bundler can prove are unused. An icon set is the easiest place to break that proof, because icon sets are large - often hundreds or thousands of glyphs - and icon APIs are tempting to make dynamic. Common causes, roughly in order of frequency: | Package shape | Why every icon ships | |---|---| | An icon component taking a name string, backed by a map of all icons | Which names are passed is decided at runtime, so the whole map is kept | | One file exporting every icon | The file's top-level code runs as a unit and often cannot be split | | A module that registers all icons on import | Registration is a side effect, so the module is always kept | | A package with no side-effect declaration | The bundler keeps every re-exported icon it cannot prove harmless | In a telecom self-service app using twelve icons - signal, SIM, roaming, bill, top-up and a few more - any of these ships the other several hundred. ## Diagnosing it 1. **Inspect the bundle** with a bundle-analysis report and look for the icon package's share of the output. 2. **Check how app code imports icons**: by name string through one component, or each icon as its own import. 3. **Read the package's module layout**: one module per icon, or one large file. 4. **Check for import-time registration** and the package's side-effect declaration. ## Shaping the package - **One module per icon**, each exporting a single component or vector definition. - **Static imports in app code**: the app imports the signal icon and the bill icon by name as code, so the bundler sees exactly which twelve are used. - **A correct side-effect declaration** marking icon modules effect-free. - **No import-time registration**; if a registry is needed, the app registers the icons it uses explicitly. - **An opt-in dynamic path** for content-driven cases: a name-based component that loads each icon on demand as its own chunk, so only icons actually requested are fetched. ## Fonts and other assets The same logic applies to fonts shipped with the system. A font file contains every glyph and weight it was built with; unless it is subset at build time, the app downloads all of them. Shipping fonts as separate assets the app hosts lets each app subset and preload what it needs. Whether icons should be vector modules, sprites or a font at all is a separate delivery-format decision; the packaging point is that whatever format is chosen, the app must be able to include only what it uses. ## Native mobile On native platforms, image and vector resources bundled into a module are not removed by code-level dead-code elimination, so an icon pack added as one resource bundle ships whole. Native teams get the same result by generating per-icon resources, splitting packs into smaller modules, or trimming unused resources at build time. ## Costs of the dynamic path The opt-in, load-on-demand path is not free, which is why it should stay opt-in: - **Extra requests**: each first use of an icon fetches it over the network, which matters on the slow mobile connections a telecom app's users often have. - **A flash of missing icon**: the icon appears after its chunk arrives, so the component needs a fixed-size placeholder to avoid layout shift. - **Caching and offline behavior**: icons fetched on demand must be cached, or an offline screen shows gaps. - **Weaker static checks**: a misspelled name is found only at runtime, not at build time. ## Keeping it fixed Add a **size budget** to the library's CI: a fixture app importing one icon must stay under a small threshold, and a second fixture importing twelve must grow roughly linearly. Pair it with a guideline - or a lint rule - against importing the whole icon index in app code. Without a guard, the next convenience API reintroduces the problem.

  • The product needs icons chosen by name from server content. How do you support that without shipping every icon?
    Make it a separate, opt-in path: a name-based component that loads each icon on demand as its own chunk, or an app-level registry filled only with the icons the content can reference. The static per-icon import stays the default, so screens with fixed icons still ship only what they use.
  • How do you stop icon bloat from coming back?
    Add a size budget in CI: a fixture importing one icon must stay under a small threshold, and a fixture importing many must grow roughly in proportion. Pair it with a lint rule or review guideline against importing the whole icon index from app code.
  • Does switching to an icon font fix the bloat?
    Not by itself. A font ships every glyph it contains unless it is subset at build time, and it brings its own rendering and accessibility trade-offs. Per-icon modules let the bundler include only what is referenced; the choice of format is a separate delivery decision.

saying these in an interview costs you the question

  • Tree-shaking removes unused icons whatever the icon API looks like.
  • Looking icons up by a name string has no bundle cost.
  • An icon font is always the smallest way to ship an icon set.
  • Twelve icons cannot matter, so the growth must be the framework.
  • Registering every icon at import time is harmless because each is small.