A Vite Plugin Splits Lucide Icons into 5 Chunks by First Letter for True On-Demand Loading
Icon libraries like Lucide are large enough to bloat bundles, but dynamic admin UIs can't rely on static imports. This pattern solves both sides of the trade-off without per-icon chunk spam or manual maintenance of split points.
Dynamic icon selectors need every icon available, but regular pages should load only the few they actually render. Existing approaches fail: per-icon dynamic imports create thousands of chunks and network requests, while static tree-shaking can't handle runtime icon names.
The fix is a Vite plugin that reads Lucide's ESM icon directory at build time and generates five virtual modules—a-c, d-l, m-p, q-s, t-z—each exporting every icon in its range. A runtime helper maps an icon name to the correct batch, calls `defineAsyncComponent`, and caches the result. The icon selector triggers all five chunks; a page using a single `Smile` icon loads only the a-c chunk.
Build output stays clean: five extra chunks totaling roughly 500 KB uncompressed (around 130 KB gzipped), with no per-icon file fragmentation. The plugin uses explicit imports instead of re-exports so Rolldown inlines all icons into one chunk per batch.
Virtual modules are the key enabler here: they let the plugin define chunk boundaries at build time without writing intermediate files to disk, so there is zero ongoing maintenance when the icon library updates.
Grouping by first letter is a pragmatic heuristic that balances chunk count against per-page waste. The largest chunk (a-c, 514 icons) is still only 144 KB uncompressed, which is acceptable even if a page uses a single icon from that range.
Using explicit `import` + `export const` instead of re-exports is a deliberate Rolldown optimization—it forces all icons in a batch into one chunk rather than letting the bundler split them again.
The runtime cache on `lucideCache` prevents `defineAsyncComponent` from creating a new async wrapper on every render, which would otherwise cause flickering as the same icon re-resolves.
Element Plus icons are handled separately via global registration because they are already split into individual components; the Lucide approach is necessary only when a library ships as a flat directory of icon files.