最近在打包项目的时候发现打包极其的慢,直接vite打包栈溢出,打包失败
进行了一下排查,优化,最终发现罪魁祸首是 vite-plugin-svg-icons
主要原因是:loader 的时候每次都会完整构建一遍,复杂度随着项目文件数目和 svg 文件数目指数上升
-
依赖极其老旧,停止维护:仓库最后一次更新定格在 4 年前。
-
严重的性能与内存问题:由于其处理机制的问题,在大型项目中会导致打包极其缓慢,甚至出现栈溢出(OOM)报错(详见 Issue #112、#124)。
-
安全风险:安全扫描工具频频报出底层依赖的漏洞(详见 Issue #123)。
为了彻底解决这些痛点,我决定将图标系统重构。采用知名开源大佬 Anthony Fu (antfu) 维护的unplugin-icons来处理组件化图标,并结合最新的 Tailwind CSS v4 及 Iconify 官方插件来实现 CSS 类的自定义图标方案。这不仅极大提升了打包速度,还让图标的使用变得前所未有的灵活。
下面是详细的迁移与配置流程。
第一步:安装插件
pnpm i -D unplugin-icons
第二步:安装图标数据
使用 Iconify 作为图标数据源(支持 100+ 个图标集)
VS Code 用户:安装 Iconify IntelliSense 扩展以获得内联预览、自动完成和悬停信息
pnpm i -D @iconify/json
完整安装 这将安装所有图标集(约 120MB)。只有你实际使用的图标才会在生产环境中被打包。
安装单个图标集
仅安装你需要的图标集:
pnpm i -D @iconify-json/mdi @iconify-json/carbon
自动安装(实验性)
让 unplugin-icons 在你导入图标集时自动安装它们:
Icons({
autoInstall: true, // Auto-detects npm/yarn/pnpm
})
构建工具配置
// vite.config.ts
import Icons from 'unplugin-icons/vite'
export default defineConfig({
plugins: [
Icons({ /* options */ }),
],
})
依据使用的框架配置 compiler 选项
Icons({ compiler: 'vue3' })
通过在导入路径中添加 ?raw 来将图标作为原始 SVG 字符串导入。适用于直接在 HTML 模板中嵌入 SVG。
<script setup lang='ts'>
import RawMdiAlarmOff from '~icons/mdi/alarm-off?raw&width=4em&height=4em'
import RawMdiAlarmOff2 from '~icons/mdi/alarm-off?raw&width=1em&height=1em'
</script>
<template>
<!-- raw example -->
<pre>
import RawMdiAlarmOff from '~icons/mdi/alarm-off?raw&width=4em&height=4em'
{{ RawMdiAlarmOff }}
import RawMdiAlarmOff2 from '~icons/mdi/alarm-off?raw&width=1em&height=1em'
{{ RawMdiAlarmOff2 }}
</pre>
<!-- svg example -->
<span v-html="RawMdiAlarmOff" />
<span v-html="RawMdiAlarmOff2" />
</template>
每一个图标就是一个组件
自定义图标
unplugin-icons 默认支持通过 @iconify/json 使用海量的开源图标库,但由于我们是从旧插件迁移,项目里肯定有大量业务专属的本地 SVG 文件。
我们需要通过 FileSystemIconLoader 来加载这些自定义图标。
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' // 如果你使用的是 Vue
import Icons from 'unplugin-icons/vite'
import { FileSystemIconLoader } from 'unplugin-icons/loaders'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
vue(),
tailwindcss(), // Tailwind v4 的 Vite 插件
// unplugin-icons 配置
Icons({
// 指定编译器,根据你的框架选择 'vue3', 'react', 'svelte' 等
compiler: 'vue3',
autoInstall: true,
customCollections: {
// 这里的 'custom' 是你自定义图标集合的名称
// 参数一是你本地 SVG 文件夹的相对路径
// 参数二是可选的转换函数,通常用于将 svg 的 fill 或 stroke 替换为 currentColor 以支持 CSS 动态改色
'custom': FileSystemIconLoader(
'./src/assets/svg',
svg => svg.replace(/^<svg /, '<svg fill="currentColor" ')
),
},
}),
],
})
使用方式为
import IconAccount from '~icons/my-icons/account'
import IconFoo from '~icons/my-other-icons/foo'
import IconBar from '~icons/my-yet-other-icons/bar'
使用解析器自动导入
使用自动导入时,注册你的自定义集合名称:
// vite.config.ts
IconResolver({
customCollections: [
'local',
'my-other-icons',
'my-yet-other-icons',
],
})
直接使用
<i-local-account/>
组件命名
图标按照以下命名规则自动导入:
{prefix}-{collection}-{icon}
prefix : 组件名称前缀(默认值: i )
collection : Iconify 集合 ID(例如, mdi 、 carbon 、 fa-solid )
icon : 图标名称(kebab-case)
自定义前缀
IconsResolver({
prefix: 'icon', // Use 'icon' instead of 'i'
})
无前缀: false
<icon-mdi-account />
<mdi-account />
设置图标集别名
IconsResolver({
alias: {
park: 'icon-park', // Use <icon-park-* /> instead of <icon-icon-park-* />
fas: 'fa-solid', // Use <icon-fas-* /> instead of <icon-fa-solid-* />
}
})
配置 Tailwind CSS v4 及自定义图标(CSS 类方案)
安装
pnpm i -D @iconify/tailwind4
插件不包含图标。您需要添加要使用的图标集。
您也可以通过安装 @iconify-json/{prefix} 依赖项(其中"{prefix}"是图标集前缀)来仅安装您想要使用的图标集,例如 @iconify-json/mdi-light。
Tailwind CSS v4 带来了革命性的变化,最大的区别就是去掉了 tailwind.config.js,所有的配置直接在 CSS/全局样式文件中通过 CSS At-rules(@规则)完成。
借助 @iconify/tailwind4 插件,我们不仅能用原子类写公共开源图标,还能直接把本地存放 SVG 的文件夹映射为 Tailwind 的原子类!
修改你的主 CSS 文件(例如 src/style.css 或 src/main.css):
/* 引入 Tailwind v4 核心 */
@import "tailwindcss";
/* 1. 全局配置:直接引入完整的 Iconify 支持(如果需要用到海量开源图标) */
@plugin "@iconify/tailwind4";
/* 2. 自定义本地 SVG 配置 */
@plugin "@iconify/tailwind4" {
/* from-folder(前缀名, 文件夹路径)
这里我们将 src/assets/svg 文件夹映射为 `local` 集合
*/
icon-sets: from-folder(local, "./src/assets/icons");
}
注意:Iconify 插件在底层会自动清理并优化
from-folder加载的 SVG,如果图片是单色,它会自动转化为 mask,以完美支持 Tailwind 的text-red-500等颜色类名。
要使用图标,请为图标添加动态选择器,例如
<span class="icon-[mdi-light--home]"></span>
还可以自定义设置图标的前缀和大小(默认为1em)
@plugin "@iconify/tailwind4" {
prefix: "iconify";
scale: 1.2;
}
自定义图标
加载图标集有两种方法:
- 加载以 IconifyJSON 格式预解析的图标集。
- 加载本地文件夹中的所有svg文件
配置示例
@plugin "@iconify/tailwind4" {
icon-sets: from-json(test, "./icon-sets/test.json"), from-folder(test2, "./icon-sets/svgs");
}
在 CSS 的插件配置中添加“icon-sets”选项,选项集以逗号分隔。
从 JSON 文件加载速度更快,因为无需进行清理操作
文件必须为 IconifyJSON 格式,可使用 Iconify Tools 生成。
如果您项目里的本地 SVG 图标非常多(比如几百上千个),每次项目启动时使用 from-folder 让 Vite 在运行时去逐个读取、清理和转化 SVG,依然会消耗一定的构建时间。
更优雅且极致的解决方案是:使用 Iconify 官方提供的 @iconify/tools,编写一个独立js脚本或是vite插件,将所有的本地 SVG 预先处理、压缩,并打包成一个 .json 文件。 之后无论是 Tailwind v4 还是 unplugin-icons,直接读取这个 JSON 文件即可,实现“零运行时开销”。
具体详细配置可查看文档@iconify/tools
弊端:脚本在处理文件时会把所有颜色都换成了
currentColor。如果你的图标全是单色的菜单 Icon,这很完美。但如果你的文件夹里混入了一个多色的插画 SVG(比如带有蓝色衣服、黄色帽子的彩色 Logo),经过脚本处理后,它会变成黑乎乎的一团(也就是失去了原本的彩色)
额外类名
每个图标有 2 个类名:
图标的类名,例如“mdi-light--home”。
渲染模式的类名:"iconify" 或 "iconify-color"(可配置)。
所有图标均遵循相同的规则,图片 URL 除外。
为避免代码重复,通用规则已被拆分为实用类。此外,这还允许您选择图标的渲染方式:
“iconify” 会将图标渲染为蒙版图像,因此图标会采用与文本相同的颜色。若要更改图标颜色,请更改文本颜色。此方法适用于未硬编码配色方案的图标。
“iconify-color” 将图标渲染为背景图像。此功能适用于具有硬编码调色板的图标。
为什么需要配置?
Tailwind CSS 的工作原理是查找代码中的类名,并为这些类名生成相应的 CSS 样式。
在使用动态类名(例如“icon-[mdi-light--home]”)时,Tailwind CSS 会查找所有此类类名,并将它们传递给插件以生成 CSS。这意味着插件知道使用了哪些图标,并仅加载所需的图标。
然而,当使用普通类名(例如“mdi-light--home”)时,Tailwind CSS 需要先通过插件为所有可能的类名生成 CSS,然后再在项目中查找类名,最后移除未使用的类名。这意味着插件必须为所有可能存在的图标生成 CSS。
为每个图标生成 CSS 并非快速的过程。鉴于可用的图标超过 275,000 个,这可能会耗费大量时间。此外,Tailwind CSS 会将所有内容保存在内存中,这可能会导致 Tailwind CSS 内存不足。为避免这种情况,您必须指定要使用的图标集列表。
配置完后直接通过类名生成图标
<i class="text-blue-500 text-xl icon-[local--user]" />
总结
通过移除四年前的 vite-plugin-svg-icons,并引入 unplugin-icons + @iconify/tailwind4:
- 彻底告别了项目打包时的内存泄漏(OOM) ,打包速度肉眼可见地提升。
- 我们享受到了 antfu 和 Iconify 社区持续活跃维护带来的红利,告别了安全漏洞警告。
- 拥抱了下一代构建工具 Tailwind CSS v4 的极简 CSS 架构。
希望这篇文章能帮助正在使用老旧 Vben 等模板架构的开发者们成功渡劫!如果有问题,欢迎在评论区交流。