彻底淘汰老旧 SVG 插件:unplugin-icons 与 Tailwind CSS v4 自定义图标最佳实践

最近在打包项目的时候发现打包极其的慢,直接vite打包栈溢出,打包失败 进行了一下排查,优化,最终发现罪魁祸首是 vite-plugin-svg-icons

主要原因是:loader 的时候每次都会完整构建一遍,复杂度随着项目文件数目和 svg 文件数目指数上升

  • 依赖极其老旧,停止维护:仓库最后一次更新定格在 4 年前。

  • 严重的性能与内存问题 :由于其处理机制的问题,在大型项目中会导致打包极其缓慢,甚至出现栈溢出(OOM)报错(详见 Issue #112#124)。

  • 安全风险 :安全扫描工具频频报出底层依赖的漏洞(详见 Issue #123)。

为了彻底解决这些痛点,我决定将图标系统重构。采用知名开源大佬 Anthony Fu (antfu) 维护的unplugin-icons来处理组件化图标,并结合最新的 Tailwind CSS v4 及 Iconify 官方插件来实现 CSS 类的自定义图标方案。这不仅极大提升了打包速度,还让图标的使用变得前所未有的灵活。

下面是详细的迁移与配置流程。

第一步:安装插件

bash 复制代码
pnpm i -D unplugin-icons

第二步:安装图标数据

使用 Iconify 作为图标数据源(支持 100+ 个图标集)

VS Code 用户:安装 Iconify IntelliSense 扩展以获得内联预览、自动完成和悬停信息

bash 复制代码
pnpm i -D @iconify/json

完整安装 这将安装所有图标集(约 120MB)。只有你实际使用的图标才会在生产环境中被打包。

安装单个图标集

仅安装你需要的图标集:

bash 复制代码
pnpm i -D @iconify-json/mdi @iconify-json/carbon

自动安装(实验性)

unplugin-icons 在你导入图标集时自动安装它们:

ts 复制代码
Icons({
  autoInstall: true, // Auto-detects npm/yarn/pnpm
})

构建工具配置

ts 复制代码
// vite.config.ts
import Icons from 'unplugin-icons/vite'

export default defineConfig({
  plugins: [
    Icons({ /* options */ }),
  ],
})

依据使用的框架配置 compiler 选项

ts 复制代码
Icons({ compiler: 'vue3' })

通过在导入路径中添加 ?raw 来将图标作为原始 SVG 字符串导入。适用于直接在 HTML 模板中嵌入 SVG。

vue 复制代码
<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 来加载这些自定义图标。

ts 复制代码
// 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" ')
        ),
      },
    }),
  ],
})

使用方式为

vue 复制代码
import IconAccount from '~icons/my-icons/account'
import IconFoo from '~icons/my-other-icons/foo'
import IconBar from '~icons/my-yet-other-icons/bar'

使用解析器自动导入

使用自动导入时,注册你的自定义集合名称:

ts 复制代码
// vite.config.ts
IconResolver({
  customCollections: [
    'local',
    'my-other-icons',
    'my-yet-other-icons',
  ],
})

直接使用

vue 复制代码
<i-local-account/>

组件命名

图标按照以下命名规则自动导入:

ts 复制代码
{prefix}-{collection}-{icon}

prefix : 组件名称前缀(默认值: i

collection : Iconify 集合 ID(例如, mdicarbonfa-solid

icon : 图标名称(kebab-case)

自定义前缀

ts 复制代码
IconsResolver({
  prefix: 'icon', // Use 'icon' instead of 'i'
})
无前缀: false
vue 复制代码
  <icon-mdi-account />
vue 复制代码
 <mdi-account />

设置图标集别名

ts 复制代码
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 类方案)

安装

bash 复制代码
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.csssrc/main.css):

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 等颜色类名。

要使用图标,请为图标添加动态选择器,例如

vue 复制代码
<span class="icon-[mdi-light--home]"></span>

还可以自定义设置图标的前缀和大小(默认为1em)

css 复制代码
@plugin "@iconify/tailwind4" {
  prefix: "iconify";
  scale: 1.2;
}

自定义图标

加载图标集有两种方法:

  • 加载以 IconifyJSON 格式预解析的图标集。
  • 加载本地文件夹中的所有svg文件

配置示例

css 复制代码
@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 内存不足。为避免这种情况,您必须指定要使用的图标集列表。

配置完后直接通过类名生成图标

vue 复制代码
<i class="text-blue-500 text-xl icon-[local--user]" />

总结

通过移除四年前的 vite-plugin-svg-icons,并引入 unplugin-icons + @iconify/tailwind4

  1. 彻底告别了项目打包时的内存泄漏(OOM) ,打包速度肉眼可见地提升。
  2. 我们享受到了 antfuIconify 社区持续活跃维护带来的红利,告别了安全漏洞警告。
  3. 拥抱了下一代构建工具 Tailwind CSS v4 的极简 CSS 架构。

希望这篇文章能帮助正在使用老旧 Vben 等模板架构的开发者们成功渡劫!如果有问题,欢迎在评论区交流。

相关推荐
码事漫谈3 小时前
大模型输出的“隐性结构塌缩”问题及对策
前端·后端
这儿有一堆花4 小时前
前端三件套真的落后了吗?揭开现代 Web 开发的底层逻辑
前端·javascript·css·html5
.Cnn4 小时前
JavaScript 前端基础笔记(网页交互核心)
前端·javascript·笔记·交互
小兵张健4 小时前
强程序员在 AI 时代的赚钱路径
程序员·openai
醉酒的李白、4 小时前
Vue3 组件通信本质:Props 下发,Emits 回传
前端·javascript·vue.js
anOnion5 小时前
构建无障碍组件之Window Splitter Pattern
前端·html·交互设计
NotFound4865 小时前
实战分享Python爬虫,如何实现高效解析 Web of Science 文献数据并导出 CSV
前端·爬虫·python
徐小夕5 小时前
PDF无限制预览!Jit-Viewer V1.5.0开源文档预览神器正式发布
前端·vue.js·github
WangJunXiang65 小时前
Haproxy搭建Web群集
前端
吴声子夜歌5 小时前
Vue.js——自定义指令
前端·vue.js·flutter