直接使用 Iconify 的朋友应该都遇到过这种情况:本地开发一切正常,图标花里胡哨随便用,但一旦把项目部署到内网、政务云、客户机房,或者拿到没网的演示环境里,页面上的 SVG 图标瞬间变成一个个方框。原因很简单——Iconify 的默认工作方式是从远程 API 动态拉取图标数据,网络一断,图标自然就没了。
我最近正好把一个中后台管理系统从“远程 API 加载图标”彻底改造成“全套离线方案”,过程中踩了不少坑,也把几种主流思路都试了一遍。这篇文章就把 Iconify 图标集离线使用的完整方案、选型逻辑、实操代码和排查技巧整理出来,给同样被内网部署、离线环境、加载速度折磨的朋友一个可以直接抄作业的参考。
1. 先搞清楚 Iconify 到底怎么工作的:为什么“联网”是它的默认设计
1.1 一个组件怎么就能渲染任意图标集
Iconify 的设计思路和传统字体图标、SVG Sprite 差别很大。它不要求你把图标文件下载到本地再导入,而是把几十个开源图标集(Material Design Icons、Tabler、Phosphor、Ant Design Icons 等)统一抽象成一套 JSON 数据格式,每个图标集对应一个 JSON 文件,每个图标在文件里记录的是 SVG 的 path 数据、viewBox、别名等信息。
当你写<Icon icon="mdi:home" />时候,@iconify/vue或@iconify/react组件其实做了几件事:
- 先检查本地有没有注册过
mdi这个集合的数据。 - 如果没有,组件会自动向
https://api.iconify.design/mdi.json?icons=home发起请求。 - 拿到返回的 JSON 后,解析出图标对应的 SVG 数据,再渲染成实际的
<svg>节点。
也就是说,Iconify 默认是一套“云端按需加载”的架构。它的好处很明显:可以用统一的组件 API 访问几乎所有开源图标集,图标数据不用打进前端 bundle,首次只加载用到的图标,内存和流量都省。但坏处也藏在这里——一旦 API 不可达,组件手里没有本地数据,就只能显示空白或占位方块。
1.2 远程 API 的天花板:内网、慢网、离线场景
在我的实际项目里,一开始图省事,全站直接用的@iconify/vue默认配置,开发环境跑得飞快。但到了部署阶段,问题一个个冒出来:
- 客户服务器在内网,只有白名单域名能访问外网,
api.iconify.design根本连不通。页面加载时控制台密密麻麻全是Failed to fetch,图标区域全部空白。 - 某次离线演示,现场没有网络,PPT 和系统都准备好了,结果一打开页面,顶栏的菜单图标、按钮图标、状态图标全变方框,场面一度非常尴尬。
- 即使外网可用,跨公网请求第三方 CDN 也存在稳定性隐患。网络一抖,图标就闪烁、延后渲染,视觉上非常掉价。
这些问题本质上都是同一个矛盾:Iconify 的“按需加载”默认依赖远程 API,而企业在生产环境里往往要求资源可控、网络隔离、加载稳定。“离线的核心难点”不是 Iconify 不能离线用,而是怎么把“按需加载”从“远程拉取”改成“本地提供”。
1.3 离线使用方案大致有几类思路
我梳理下来,市面上的离线方案基本可以分成四类:
- 构建期编译:用
unplugin-icons这类工具,在打包时把用到的图标编译成组件代码,运行时完全不依赖任何 API。 - 运行时本地注册:把图标集的 JSON 数据打包进 bundle,或在应用启动时通过
addCollection/addIcon注册到本地,然后组件就直接从本地数据渲染。 - 自建 API 镜像:把 Iconify 的 API 逻辑搬到自己的服务器上,客户端仍然用远程/本地加载的方式,但请求地址指向自己的服务。
- 全量内联打包:把整个图标集 JSON 全部注册进应用,没有远程请求,但 bundle 体积会膨胀。
这几类方案各有适用场景,后面我会逐个展开,给出配置和代码。先看一张对比表,方便你心里有数。
2. 五种离线方案横向对比:先选型再动手
2.1 方案总览表
| 方案 | 核心思路 | 运行时是否依赖网络 | 图标动态性 | 打包体积 | 适用场景 |
|---|---|---|---|---|---|
| unplugin-icons 构建期编译 | 打包时把图标编译成组件 | 完全离线 | 低,图标名需编译期确定 | 极小,只包含用到的图标 | Vite/Webpack 项目、图标相对固定 |
| @iconify-icons 按需注册 | 运行时通过 addIcon 注册单图标 | 完全离线 | 中,可从固定集合里选 | 小,只包含注册的图标 | Vue/React 中后台,图标可控 |
| @iconify-json 注册整个集合 | 运行时通过 addCollection 注册集合 | 完全离线 | 高,集合内任意图标 | 中等,看集合大小 | 内网交付、图标变化频繁 |
| 自建 Iconify API 镜像 | 本地部署 API,客户端动态拉取 | 仅局域网,不依赖公网 | 高,任意图标 | 很小,按需拉取 | 多项目共享、微前端、大型中台 |
| 字体图标(iconfont/Font Awesome) | 放弃 Iconify,改用本地字体 | 完全离线 | 低,需重新生成字体 | 很小 | 图标极简单、兼容老浏览器 |
2.2 方案选型决策逻辑
这些方案不是非此即彼,甚至在同一个项目里可以组合使用。比如主框架用unplugin-icons做构建期编译,同时保留一部分运行时注册的 JSON 数据来应对后台动态下发的图标名。
我做选型的时候,核心考虑三个问题:
- 图标是否动态:如果图标名完全由后端接口返回,比如菜单表里存了
mdi:home、fa:user这类字符串,那构建期编译方案就不太方便,更适合运行时注册或自建 API。 - 网络边界在哪:如果整个系统部署在纯内网,公网完全不通,那要么把数据打包进应用,要么在局域网内部署一个 API 服务。
- 团队维护成本:图标数量固定且少,用按需注册最省事;图标集庞大且不断新增,自建 API 镜像或全量集合注册更省心。
下面我就按方案逐个讲实操,代码比较多,建议收藏后对着敲一遍。
3. 方案一:构建期按需编译(unplugin-icons),Vite 项目首选
3.1 安装与配置
unplugin-icons是目前在 Vite 项目里体验最好的 Iconify 离线方案。它的原理是在构建时读取@iconify/json或本地 SVG 文件,把用到的图标编译成独立的组件代码,打包进最终产物。运行时不再有任何网络请求,也没有自己解析 JSON 的开销。
以 Vue 3 + Vite 为例,先安装依赖:
npm install -D unplugin-icons @iconify/json npm install -D unplugin-auto-import # 可选,用于自动注册图标组件然后在vite.config.ts里加插件:
import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import Icons from 'unplugin-icons/vite'; import IconsResolver from 'unplugin-icons/resolver'; import AutoImport from 'unplugin-auto-import/vite'; export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [IconsResolver()], }), Icons({ compiler: 'vue3', autoInstall: true, scale: 1.2, defaultClass: 'inline-block', }), ], });这里有几个配置点我单独说明一下:
autoInstall: true:当代码里用到了~icons/mdi/home但本地没有安装@iconify-json/mdi时,构建工具会自动安装对应的图标集包。团队协作时建议提前装好,避免构建时动态安装带来的网络依赖。compiler: 'vue3':根据你的框架选,React 项目用react,Vue 2 用vue2,还有svelte、solid等选项。scale:统一控制图标缩放。这是我比较习惯的设置,因为 Material Design Icons 默认是 24px 的 viewBox,项目中经常需要和其他字号对齐。
3.2 组件里的用法
配置完成后,使用方式很简单。手动引入方式:
<script setup lang="ts"> import IconMdiHome from '~icons/mdi/home'; import IconTablerSettings from '~icons/tabler/settings'; </script> <template> <div> <IconMdiHome class="text-red-500" /> <IconTablerSettings class="w-5 h-5" /> </div> </template>配合unplugin-auto-import的 resolver,你甚至不需要手动import,模板里直接用<icon-mdi-home />这种命名风格的组件即可:
<template> <div> <icon-mdi-home class="text-red-500" /> <icon-tabler-settings class="w-5 h-5" /> </div> </template>它的命名规则是icon-前缀 + 图标集名 + 图标名,所有连接符转成驼峰或短横线。我个人更推荐手动 import 的方式,因为 IDE 类型提示更明确,代码可读性也更好,不容易出现“模板里敲错名字但构建不报错”的问题。
3.3 动态图标名怎么绕过
unplugin-icons最大的局限是“图标名在编译期必须确定”。如果用~icons/${dynamicName}这种动态拼接,Vite 会直接报错,因为构建工具没法在打包时穷举所有可能的图标名。
如果业务里确实存在动态图标名(后台返回),我一般推荐两种处理方式:
- 维护一张本地映射表,把后端可能返回的图标名映射到
unplugin-icons编译好的组件上。适合图标数量可控的管理后台。 - 结合方案二,在应用启动时把动态图标用到的集合通过
addCollection注册,动态图标用<Icon>渲染,静态图标继续用unplugin-icons编译组件。这也是我最终采用的方式,两者互不冲突。
动态图标名映射表的示例:
import IconMdiHome from '~icons/mdi/home'; import IconMdiAccount from '~icons/mdi/account'; import IconTablerSettings from '~icons/tabler/settings'; const iconMap: Record<string, Component> = { 'mdi:home': IconMdiHome, 'mdi:account': IconMdiAccount, 'tabler:settings': IconTablerSettings, }; // 渲染时根据 name 动态取组件3.4 需要注意的点
unplugin-icons这套方案我用了挺久,整体体验很好,但有几个细节必须提醒:
- 图标文件会被编译成组件,所以每个图标实际是一个 Vue/React 组件。如果同一个页面里图标数量特别多(上百个),组件的实例化开销要比纯 SVG 字符串稍高一点,但现代浏览器里的差异几乎感知不到。
- 最好在项目里统一图标尺寸和颜色,避免出现“同一个页面里图标大小不一”的情况。我通常封装一个
AppIcon.vue组件,内部统一处理class和style,禁止业务代码直接使用~icons路径。 - 要注意 tree-shaking。
unplugin-icons的按需编译本身是 tree-shaking 友好的,但如果把图标组件集中导出到一个index.ts并全量 re-export,反而可能让打包器把没用到的组件也打进去。最好保持按需 import,别做“图标大全”式的集中导出。
4. 方案二:运行时注册本地 JSON,前后端通用
4.1 先安装数据源
运行时注册的思路,是把图标数据提前放到应用里,组件渲染时直接从本地取。这是“离线”这个词最直接的理解方式。
Iconify生态提供了两种数据包形式:
@iconify-icons/*:例如@iconify-icons/mdi,每个图标是一个独立模块,支持 tree-shaking。@iconify-json/*:例如@iconify-json/mdi,每个包是完整的图标集合 JSON,导入后是整个集合对象。
还有一种大而全的@iconify/json,包含了所有支持的图标集,但体积很大,直接用会让打包产物非常臃肿,所以我一般不在前端应用里直接依赖它,只在脚本或服务端用它做数据处理。
安装方式:
# 按单图标引入(推荐) npm install @iconify-icons/mdi # 或按图标集整体引入 npm install @iconify-json/mdi4.2 全局注册与单图标注册
如果你用@iconify/vue或@iconify/react,可以直接从包里引入注册方法。
全局注册整个图标集合:
import { addCollection } from '@iconify/vue'; import { icons as mdiIcons } from '@iconify-json/mdi'; addCollection(mdiIcons);注册单个图标:
import { addIcon } from '@iconify/vue'; import mdiHome from '@iconify-icons/mdi/home'; addIcon('mdi:home', mdiHome);注册完成后,模板里照常使用<Icon icon="mdi:home" />,但组件不会再发起网络请求,直接使用本地注册的数据渲染。
这里有个容易踩的坑:@iconify-json/mdi导入的icons对象本身是一个完整的IconifyJSON对象,里面包含prefix、icons、aliases等字段,直接用addCollection没问题。而@iconify-icons/mdi/home导出的对象是单个图标的IconifyIcon对象,必须用addIcon来注册。两者别混用,否则组件拿不到正确的数据结构。
4.3 维护一个 icons.ts 统一注册
很多项目用着用着就乱了:有人直接在任意组件里addIcon,有人重复注册同一个集合,还有人不小心覆盖了已有图标。我建议在项目里建一个src/icons.ts,统一管理所有需要注册的图标。
我的做法大概是这样:
import { addCollection, addIcon } from '@iconify/vue'; import { icons as mdiIcons } from '@iconify-json/mdi'; import { icons as tablerIcons } from '@iconify-json/tabler'; import homeIcon from '@iconify-icons/mdi/home'; import settingsIcon from '@iconify-icons/mdi/cog-outline'; // 常用集合整体注册 addCollection(mdiIcons); addCollection(tablerIcons); // 单独补充注册某些图标 addIcon('mdi:home', homeIcon); addIcon('mdi:cog-outline', settingsIcon); export function setupIcons() { // 空函数,只在入口调用一次,保证模块执行顺序 }然后在main.ts里调用一次:
import { setupIcons } from './icons'; setupIcons();这样所有图标注册逻辑都在一个文件里,后期排查“哪个图标没有注册”“哪个集合重复了”都非常方便。
4.4 Vue/React 用法差异
Vue 3 项目里,@iconify/vue的Icon组件支持直接在模板中使用:
<template> <Icon icon="mdi:home" width="24" height="24" /> </template> <script setup lang="ts"> import { Icon } from '@iconify/vue'; </script>React 项目里,@iconify/react的用法基本一致:
import { Icon } from '@iconify/react'; export function HomeButton() { return <Icon icon="mdi:home" width={24} height={24} />; }注册方法addCollection、addIcon在@iconify/vue和@iconify/react里都有 re-export,不需要额外安装iconify核心包。
4.5 这个方案有什么坑
这套方案最大的坑是“一次性注册整个集合”带来的体积膨胀。以@iconify-json/mdi为例,它包含几千个图标,注册后虽然只有用到的图标会被打包器 tree-shake,但如果用import { icons } from '@iconify-json/mdi'这种方式导入,打包器很难静态分析出哪些图标会被用到,导致整个集合被完整注入 bundle。实际体积可能在几百 KB 到几 MB 不等,具体看集合大小。
我的建议是:如果图标集整体不大(比如常用的几个集合),直接整体注册省心;如果图标集特别大,尽量用@iconify-icons/*单图标引入,或者走unplugin-icons编译方案。体积敏感的正式项目,一定要在打包后检查一下 dist 目录里图标数据占了多大。
5. 方案三:自建 Iconify API 镜像(内网部署/多项目共享)
5.1 原理与 addAPIProvider 用法
方案三是目前解决“既要保持 Iconify 动态按需加载能力、又不能依赖公网 API”的最优解。思路很简单:在自有服务器或局域网内部署一个我们自己的“迷你版” Iconify API,客户端通过配置把默认请求地址从api.iconify.design改成自己的服务器地址。
Iconify提供了addAPIProvider方法,可以自定义 API 地址:
import { addAPIProvider } from '@iconify/vue'; addAPIProvider('', { host: '/iconify', // 相对路径,也支持完整地址,例如 https://icons.example.com });这里的第一个参数是 provider 名称,传空字符串表示默认 provider。配置之后,<Icon icon="mdi:home" />组件请求的地址会变成/iconify/mdi.json?icons=home。
注意:这个host如果是相对路径,请求会基于当前站点域名发起;如果是完整地址,则指向你自己的服务。对于内网部署,我通常建议用相对路径,这样 Nginx 反代、域名切换都不用改前端代码。
5.2 静态文件托管方式
最省事的方式,是把图标集 JSON 文件直接拷贝到静态服务器上,用 Nginx 托管,不需要写任何后端代码。
具体操作分两步:
第一步,把@iconify/json里的 JSON 文件拷贝到你想要托管的目录。可以使用脚本筛选出项目需要的图标集,比如只关心mdi、tabler、ant-design,就把这几个 JSON 拷出来:
mkdir -p /data/iconify-json cp node_modules/@iconify/json/json/mdi.json /data/iconify-json/ cp node_modules/@iconify/json/json/tabler.json /data/iconify-json/ cp node_modules/@iconify/json/json/ant-design.json /data/iconify-json/第二步,配置 Nginx:
location /iconify/ { alias /data/iconify-json/; add_header Access-Control-Allow-Origin *; }这样前端请求/iconify/mdi.json?icons=home时,Nginx 会把mdi.json原样返回。注意静态文件方式不会处理icons查询参数,也就是会把整个集合的大 JSON 全部返回,再由客户端从中提取需要的图标。流量会多出一些,但对内网环境一般无所谓。
如果需要更好的响应体量控制,可以用一个简单的后端服务做按需过滤,下面看代码。
5.3 轻量过滤 API 的实现
如果你希望请求/iconify/mdi.json?icons=home时只返回home这个图标的数据,可以用 Express 写一个轻量 API。核心逻辑是:从本地 JSON 文件读取数据,根据icons查询参数裁剪出需要的图标字段,再返回给客户端。
const express = require('express'); const fs = require('fs'); const path = require('path'); const app = express(); const JSON_DIR = path.join(__dirname, 'json'); // 支持跨域 app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); next(); }); app.get('/iconify/:prefix.json', (req, res) => { const prefix = req.params.prefix; const filePath = path.join(JSON_DIR, `${prefix}.json`); if (!fs.existsSync(filePath)) { return res.status(404).json({ error: `Icon set "${prefix}" not found` }); } const fullData = JSON.parse(fs.readFileSync(filePath, 'utf8')); const iconsParam = req.query.icons; // 没有 icons 参数,返回全部数据 if (!iconsParam) { return res.json(fullData); } const iconNames = String(iconsParam).split(',').filter(Boolean); const result = { prefix: fullData.prefix, icons: {}, aliases: {}, }; // 保留公共字段(高度、宽度等) if (fullData.width !== undefined) result.width = fullData.width; if (fullData.height !== undefined) result.height = fullData.height; iconNames.forEach((name) => { if (fullData.icons[name]) { result.icons[name] = fullData.icons[name]; } if (fullData.aliases && fullData.aliases[name]) { result.aliases[name] = fullData.aliases[name]; } }); res.json(result); }); app.listen(3000, () => { console.log('Iconify local API listening on port 3000'); });把这个服务部署到内网,前端配置addAPIProvider('', { host: '/iconify' }),就能在不依赖公网的情况下获得和官方 API 几乎一样的按需加载体验。这个方案特别适合中大型项目,尤其是多个前端应用共享一套图标服务的时候,部署一次,所有项目都能受益。
5.4 这个方案的适用场景
我实际把方案三用在一个多团队协作的中台项目上,效果非常稳。为什么最终选择它而不是方案一或方案二?因为那个项目里有十几个子应用,每个团队的图标使用习惯不一样,如果让每个子应用都自己打包图标,不仅冗余,而且无法统一版本。自建 API 之后,所有子应用统一访问一个内网地址,图标集合的更新只需要替换服务器上的 JSON 文件,不需要每个应用重新发版,运维成本明显更低。
但这个方案也有门槛:你需要有服务器或容器环境,能部署 Node 服务或配置 Nginx。如果项目只是单机交付、买家只有一套系统,搞服务端反而多余,直接走方案一更合适。
6. 方案四:全量打包内联,以及备选字体方案
6.1 bundle 内联 JSON 的正确姿势
有些场景比较极端:整个系统交付给客户,客户现场断网,也没有企业内部服务器,唯一能运行的就是一个静态目录。这种时候,把图标数据直接打进前端 bundle 是最稳妥的。
全量内联其实也是用addCollection,但导入方式要选对。以 Vue 项目为例:
import { addCollection } from '@iconify/vue'; import mdi from '@iconify-json/mdi/icons.json'; import tabler from '@iconify-json/tabler/icons.json'; addCollection(mdi); addCollection(tabler);注意这里用的是@iconify-json/mdi/icons.json这种直接导入 JSON 文件的写法,打包器会把整个 JSON 当作静态资源处理,不会做 tree-shaking。得到的 bundle 会明显变大,mdi 集合可能占几百 KB,如果是大集合,加上其他集合,总增量可能超过 1MB。
这种方法最大的问题是体积。Iconify 支持的图标集加起来总数据量是百 MB 级别的,前端不可能全部内联。所以实际使用时要克制,只内联项目最常用的 2~3 个集合,并且想清楚“这些图标真的都需要吗”。
6.2 什么时候才建议全量打包
我自己的判断标准是:静态站点 + 纯离线 + 图标使用范围不可控,同时又不想起后端服务。比如给客户做演示机、离线展厅大屏、单机版桌面应用,这类场景里 bundle 体积多点不是核心矛盾,稳定可靠才是第一位。
如果体积实在敏感,可以退一步:把全量 JSON 按集合拆出来,放在本地静态目录,运行时按需 fetch。这其实就是方案三里“静态文件托管”的前端版,不需要服务器,缺点是会发出局域网或本地请求,纯 file:// 协议下 fetch 可能受限,需要注意部署方式。
6.3 备选:退回到字体图标
在某些传统项目里,团队可能不太想引入 Iconify 的组件体系,只想简单地把图标显示出来。这时候可以放弃 Iconify 本身,直接用字体图标。
最经典的做法是用iconfont.cn把选好的图标打包成字体文件,下载iconfont.ttf、iconfont.woff2等文件放到项目里,然后引入 CSS:
@font-face { font-family: 'iconfont'; src: url('/fonts/iconfont.woff2') format('woff2'), url('/fonts/iconfont.ttf') format('truetype'); } .iconfont { font-family: 'iconfont' !important; font-size: 16px; font-style: normal; -webkit-font-smoothing: antialiased; }HTML 里直接用:
<i class="iconfont icon-home"></i>这种方案的好处是极其简单,兼容性极好,老浏览器完全没问题。坏处也明显:图标的颜色、尺寸、动画都受字体限制,多色图标基本没法实现,SVG 的高级特性(渐变、描边、滤镜)更是想都别想。如果你只用到少量单色图标,而且团队不想引入复杂工具链,字体方案是够用的。但如果你已经决定统一用 Iconify,我不建议为了“简单”退回字体,因为后续图标扩展和视觉定制会非常痛苦。
6.4 字体方案和 Iconify 方案的取舍
这里展开说下我对字体方案的看法。很多老项目用 Font Awesome 或 iconfont 用了很多年,图省事继续沿用。但现代前端项目里,图标的显示需求已经不止“显示一个符号”了,经常要换颜色、换尺寸、加动画、甚至做多色渐变。字体图标在“多色”和“矢量细节”上天生吃亏,Iconify 这种 SVG 数据驱动的方案才是未来的主流。
所以我的建议是:除非项目极其简单或浏览器兼容性要求极高,否则还是先考虑前面几个离线方案,字体方案作为最后兜底。
7. 常见问题与排查技巧实录
7.1 图标显示为方块或空白
这是离线场景下最常见的现象。排查顺序我一般从三个层面入手:
第一,打开浏览器控制台,看 Network 面板里有没有api.iconify.design或自定义 API 的请求。如果有且状态码是失败或超时,说明组件还在尝试远程加载,本地没有对应数据。
第二,确认本地注册是否生效。在代码里打印图标数据:
import { getIcon } from '@iconify/vue'; const iconData = getIcon('mdi:home'); console.log(iconData); // 如果是 undefined,说明没有注册成功第三,检查图标名是否正确。同一个图标可能在不同的图标集里名字不同,比如mdi:account在 Tabler 里叫tabler:user。建议去 Iconify 官网搜索确认图标名,再复制到项目里。
7.2 离线后控制台仍在请求 API
这种情况通常是没有正确设置addAPIProvider,或者代码执行顺序不对。addAPIProvider必须在注册图标、渲染任何<Icon>组件之前执行。如果注册放在某个异步模块里,而页面已经渲染了<Icon>,组件已经发出了远程请求,那就晚了。
我习惯把addAPIProvider放在入口文件的第一行,甚至比createApp还早:
import { addAPIProvider } from '@iconify/vue'; addAPIProvider('', { host: '/iconify' }); // 其他初始化代码 createApp(App).mount('#app');7.3 别名/变体图标渲染不出来
Iconify 的图标数据里有很多“别名”。比如一个图标可能是另一个图标的变体,名字叫mdi:home-outline,实际数据指向mdi:home但有额外的渲染参数。如果只注册了基础图标而没注册别名,组件可能无法正确渲染。
避免这个问题的方法很简单:尽量注册整个集合的 JSON(addCollection),因为集合 JSON 里包含了完整的aliases映射关系。如果只用addIcon注册单个图标,注意也要把对应的别名注册进去,否则遇到变体名称会失败。
另外,Iconify 支持渲染参数,例如icon="mdi:home"也可以通过flip="horizontal"、rotate={90}等属性实现变换。这些参数是组件层面的,不依赖本地数据,所以离线后依然可用。
7.4 bundle 体积失控
打包后发现图标数据占了很大体积,基本都是因为把整个图标集 JSON 都 import 进来了。解决办法:
- 检查是否误用了
@iconify/json(全量集合包),换成@iconify-json/*或@iconify-icons/*。 - 检查是否有集中 re-export 图标组件的文件,改为按需 import。
- 如果用的是
unplugin-icons,确保没有把@iconify/json作为全量依赖打包,正常配置下只编译用到的图标,不该有这个问题。
7.5 SSR、微前端等场景注意点
SSR/SSG 场景里,@iconify/vue服务端渲染时不会触发网络请求。如果服务端渲染的 HTML 里已经包含了图标 SVG,说明数据在构建时已经可用了;如果没有,可能是注册数据没有在服务端执行。建议把图标初始化逻辑放到一个公共模块里,入口文件、服务端入口、微前端子应用入口都调用一次。
微前端场景里,如果多个子应用都用了@iconify/vue,会有重复注册、域名隔离、样式冲突等问题。我建议在主应用统一初始化图标数据和 API Provider,所有子应用直接从本地数据渲染,避免每个子应用都去注册导致内存浪费。如果子应用里有特殊图标需求,可以在子应用内部再追加注册。
8. 我个人在实际项目中的选择
文章最后分享下我自己的最终落地组合,不做什么大而全的总结,就说点实际的。
我这次的中后台项目最后采用的是“双轨制”:静态图标全部走unplugin-icons构建期编译,动态图标(菜单表里配置的图标名)用@iconify-json整体注册了几个常用集合,同时内网里挂了一个 Node 写的轻量 Iconify API 作为兜底。这样页面里 90% 的图标在构建期就固定下来了,加载零延迟;剩下 10% 的后台动态图标也能稳定渲染,不会因为某个集合没注册出现方框。
按这个方案落地后,图标相关的问题基本清零。最直观的变化是:以前每次打开页面,Network 面板里都有一堆图标请求,白屏和闪跳都跟这个有关;改完之后,图标要么在 HTML 里,要么在本地注册数据里,完全没有任何外部请求,体感快了很多。
如果你正被 Inner 网部署、离线演示、图标加载慢这些问题困扰,我的建议是:先别急着全盘推翻,从“项目里到底哪些图标是静态的、哪些是动态的”开始梳理,挑一个最小范围先试点。离线这件事,不需要一步到位,把方案拆细了,你反而会发现它没有那么难。