彻底淘汰老旧 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 等模板架构的开发者们成功渡劫!如果有问题,欢迎在评论区交流。

相关推荐
LlNingyu2 小时前
文艺复兴,什么是XSS,常见形式(二)
前端·安全·xss
明君879972 小时前
说说我为什么放弃使用 GetX,转而使用 flutter_bloc + GetIt
前端·flutter
Jingyou2 小时前
用 Astro 搭建个人博客:从零到上线的完整实践
前端
吴声子夜歌2 小时前
JavaScript——call()、apply()和bind()
开发语言·前端·javascript
高桥凉介发量惊人2 小时前
质量与交付篇(2/6):CI/CD 实战——自动构建、签名、分发
前端
leafyyuki2 小时前
SSE 同域长连接排队问题解析与前端最佳实践
前端·javascript·人工智能
高桥凉介发量惊人2 小时前
质量与交付篇(3/6):崩溃分析与线上问题回溯机制
前端
angerdream2 小时前
最新版vue3+TypeScript开发入门到实战教程之路由详解三
前端·javascript·vue.js
毕设源码-郭学长3 小时前
【开题答辩全过程】以 基于Web的网上问诊系统的设计与实现为例,包含答辩的问题和答案
前端