Stop shipping 1,300 icons when your app uses 12
How I built a zero-config Vite plugin for Ionic icon tree-shaking
Stop shipping 1,300 icons when your app uses 12
How I built a zero-config Vite plugin for Ionic icon tree-shaking
The problem
If you’ve ever built a production app with Ionic and Vite, you’ve probably noticed something odd in your dist/ folder: a svg/ directory packed with over a thousand files, weighing in at around 4 MB — even if your entire app only displays a handful of icons.
This isn’t a bug. It’s just how Ionic works. Icons are referenced by name at runtime:
<ion-icon name="add-outline" />
Because the name is resolved dynamically by the browser, your bundler has no way of knowing at build time which SVG files are actually needed. The safe default is to copy them all.
The result: ~1,300 SVG files shipped to every user, regardless of how many you use.
The solution
I built [vite-plugin-ionic-icons](https://www.npmjs.com/package/vite-plugin-ionic-icons) — a Vite plugin that statically analyzes your source files, figures out exactly which icons you use, and emits only those as build assets.
The impact is immediate:
❌ Without the plugin — ~1,300 SVG files, ~4 MB ✅ With the plugin — only the icons you use, a few KB
How to use it
Install it as a dev dependency:
npm install -D vite-plugin-ionic-icons
Then add it to your Vite config:
// vite.config.ts
import { defineConfig } from 'vite';
import ionicIcons from 'vite-plugin-ionic-icons';
export default defineConfig({
plugins: [
ionicIcons(),
],
});
That’s it. No other configuration needed.
In dev mode, icons are served on-demand directly from node_modules via a lightweight middleware — nothing is copied, and your dev server starts instantly.
In build mode, the plugin scans your source files, collects every icon name it finds, and calls Vite’s emitFile() for each one — only those, nothing more.
Framework support
The scanner handles all common frameworks through a set of regex patterns tuned to each syntax.
React / JSX / TSX
<ion-icon name="add-outline" />
<ion-icon name="trash" size="small" />
Vue
<ion-icon name="add-outline" />
<ion-icon :name="'add-outline'" />
Angular
<ion-icon name="add-outline"></ion-icon>
<ion-icon [name]="'add-outline'"></ion-icon>
Svelte
<ion-icon name="add-outline" />
Mithril / Preact h()
m('ion-icon', { name: 'add-outline' })
h('ion-icon', { name: 'add-outline' })
Plain HTML
<ion-icon name="add-outline"></ion-icon>
What about dynamic icon names?
If you resolve icon names at runtime — from an API response, a config file, or a variable — the static scanner can’t detect them. For those cases, use extraIcons:
ionicIcons({
extraIcons: ['warning-outline', 'checkmark-circle', 'close-circle'],
})
These will always be included in the build output regardless of what the scanner finds.
How it works under the hood
The plugin hooks into two Vite lifecycle methods:
**configureServer* (dev mode) registers a middleware that intercepts any request to `/svg/.svgand pipes the file directly fromnode_modules/@ionic/core/dist/ionic/svg/`. No disk writes, no temp files.
**generateBundle** (build mode) runs the scanner once against your source directory, deduplicates the collected icon names, then calls this.emitFile() for each one. The scan result is cached so it only runs once per build session.
Build time
└── generateBundle()
├── scan srcDir recursively
├── apply regex patterns (one per framework)
├── deduplicate icon names
└── emitFile() for each used icon → dist/svg/<name>.svg
Dev time
└── configureServer()
└── middleware: GET /svg/<name>.svg
└── pipe from node_modules/@ionic/core/dist/ionic/svg/<name>.svg
All available options
ionicIcons({
// Directory (or array of dirs) to scan. Default: './src'
srcDir: './src',
// File extensions to include. Default: ['.js', '.ts', '.jsx', '.tsx', '.html', '.vue', '.svelte']
extensions: ['.js', '.ts', '.jsx', '.tsx', '.html', '.vue', '.svelte'],
// Icons to always include (for dynamic names). Default: []
extraIcons: ['warning-outline'],
// Where Ionic's SVGs live. Default: 'node_modules/@ionic/core/dist/ionic/svg'
iconSrcDir: 'node_modules/@ionic/core/dist/ionic/svg',
// Output sub-directory name. Default: 'svg'
iconDestDir: 'svg',
// Log detected icons and emit count. Default: false
verbose: true,
})
Try it
📦 npm: vite-plugin-ionic-icons 🐙 GitHub: DeJy/vite-plugin-ionic-icons
If you use Ionic with Vite, give it a try. Issues, feedback, and PRs are all welcome.
메타데이터
- post_id
- 36f01dcd32f8
- slug
- stop-shipping-1-300-icons-when-your-app-uses-12-36f01dcd32f8
- url
- https://medium.com/@touletan/stop-shipping-1-300-icons-when-your-app-uses-12-36f01dcd32f8
- canonical_url
- https://medium.com/@touletan/stop-shipping-1-300-icons-when-your-app-uses-12-36f01dcd32f8
- author_url
- https://medium.com/@touletan
- status
- ok
- fetched_at
- 2026-07-15 12:51:37