微前端图标治理方案


一、背景与问题

在微前端架构下,主应用长期积累了 5 套图标方案并存 的混乱局面:

# 方案 位置 使用方式 核心问题
1 iconfont JS assets/icon.min.js(115KB) <SvgIcon name="xxx">#icon-xxx 全量加载无 Tree-shaking,iconfont 平台维护成本高
2 本地 SVG assets/svgs/(30 个文件) vite-plugin-svg-icons → SVG Sprite 仅主应用可用,子应用无法共享
3 @purge-icons + Iconify Icon.vue <Icon icon="ep:edit"> 运行时渲染,依赖 @purge-icons/generated
4 IconJson 硬编码 Icon/src/data.ts(1962 行) IconSelect 组件消费 手动维护 EP / FA 图标名列表,极易过时
5 CmcIcon @cmclink/ui <CmcIcon name="xxx"> 已有基础但只支持 SVG Sprite,未与其他方案打通

核心痛点

  • 子应用无法共享主应用图标,每个应用各自维护
  • 同一个图标可能通过 3 种不同方式引用
  • iconfont JS 全量加载 115KB,无法按需
  • 1962 行硬编码图标列表,维护成本极高
  • 中后台系统 90% 以上使用通用图标,不需要每个应用单独管理

二、治理目标

复制代码
统一入口 + 集中管理 + 零配置共享
  • 一个组件<CmcIcon> 统一消费所有图标
  • 一个图标包@cmclink/icons 集中管理 SVG 资源
  • 零配置:子应用迁入 Monorepo 后自动获得所有共享图标
  • 按需加载:Element Plus 图标异步 import,不影响首屏

三、方案架构

bash 复制代码
┌──────────────────────────────────────────────────────┐
│                    使用层(所有子应用)                  │
│                                                      │
│  <CmcIcon name="Home" />           --- SVG Sprite 图标  │
│  <CmcIcon name="ep:Edit" />        --- Element Plus 图标 │
│  <CmcIcon name="Star" size="lg" color="primary" />   │
├──────────────────────────────────────────────────────┤
│               @cmclink/ui --- CmcIcon 组件              │
│                                                      │
│  ┌─────────────┐    ┌──────────────────┐             │
│  │ SVG Sprite  │    │ Element Plus     │             │
│  │ <svg><use>  │    │ 动态 import      │             │
│  │ 无前缀      │    │ ep: 前缀         │             │
│  └─────────────┘    └──────────────────┘             │
├──────────────────────────────────────────────────────┤
│            @cmclink/icons --- 共享图标资源包              │
│                                                      │
│  packages/icons/src/svg/                             │
│  ├── Home.svg                                        │
│  ├── Star.svg                                        │
│  ├── Logo.svg                                        │
│  └── ... (30+ 通用图标)                               │
├──────────────────────────────────────────────────────┤
│           @cmclink/vite-config --- 构建自动集成           │
│                                                      │
│  vite-plugin-svg-icons 自动扫描:                      │
│  1. packages/icons/src/svg/  (共享图标,优先)          │
│  2. apps/{app}/src/assets/svgs/ (本地图标,可覆盖)     │
└──────────────────────────────────────────────────────┘

四、CmcIcon 组件设计

4.1 Props 接口

typescript 复制代码
interface CmcIconProps {
  /**
   * 图标名称
   * - 无前缀: SVG Sprite 图标(如 "Home"、"Star")
   * - "ep:" 前缀: Element Plus 图标(如 "ep:Edit"、"ep:Delete")
   */
  name: string
  /** 尺寸:数字(px) | 预设('xs'|'sm'|'md'|'lg'|'xl') | CSS 字符串 */
  size?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
  /** 颜色:CSS 值 | 主题色('primary'|'success'|'warning'|'danger'|'info') */
  color?: string
  /** 旋转角度 */
  rotate?: number
  /** 旋转动画 */
  spin?: boolean
  /** 禁用状态 */
  disabled?: boolean
  /** 可点击 */
  clickable?: boolean
}

4.2 预设尺寸

尺寸 像素 场景
xs 12px 辅助文字旁小图标
sm 14px 表单项内图标
md 16px 默认,正文行内图标
lg 20px 按钮内图标
xl 24px 标题旁图标

4.3 主题色

使用 CSS 变量自动跟随 Element Plus 主题:

typescript 复制代码
const colorMap = {
  primary: 'var(--el-color-primary, #004889)',
  success: 'var(--el-color-success, #10b981)',
  warning: 'var(--el-color-warning, #f59e0b)',
  danger:  'var(--el-color-danger, #ef4444)',
  info:    'var(--el-color-info, #3b82f6)',
}

4.4 Element Plus 图标异步加载

typescript 复制代码
// ep: 前缀触发异步加载,不影响首屏 bundle
watch(
  () => props.name,
  async (name) => {
    if (!name.startsWith('ep:')) return
    const iconName = name.slice(3) // "ep:Edit" → "Edit"
    const icons = await import('@element-plus/icons-vue')
    elIconComponent.value = icons[iconName] ?? null
  },
  { immediate: true }
)

五、Vite 构建集成

5.1 主应用配置(main-app.ts)

typescript 复制代码
createSvgIconsPlugin({
  iconDirs: [
    // 共享图标库(@cmclink/icons)--- 所有子应用共享
    resolve(root, '../../packages/icons/src/svg'),
    // 应用本地图标(可覆盖共享图标,或放置业务特有图标)
    resolve(root, 'src/assets/svgs'),
  ],
  symbolId: 'icon-[dir]-[name]',
  svgoOptions: true,
})

5.2 子应用配置(child-app.ts)

typescript 复制代码
// svgIcons 选项默认 true,子应用零配置即可共享图标
export interface ChildAppOptions {
  svgIcons?: boolean  // 默认 true
  // ...
}

关键设计iconDirs 数组中共享图标在前、本地图标在后,本地同名 SVG 可覆盖共享图标,实现灵活的图标定制能力。

六、迁移实施

6.1 迁移映射表

旧用法 新用法 说明
<SvgIcon name="Home" :size="20" /> <CmcIcon name="Home" :size="20" /> 仅改标签名
<Icon icon="ep:edit" /> <CmcIcon name="ep:Edit" /> iconname,PascalCase
<Icon icon="ep:user-filled" /> <CmcIcon name="ep:UserFilled" /> kebab → PascalCase
<Icon icon="fontisto:email" /> <CmcIcon name="ep:Message" /> 替换为 EP 等效图标
<svg><use href="#icon-xxx" /></svg> <CmcIcon name="xxx" /> 直接使用组件

6.2 实施清单

已完成 ✅

步骤 变更 影响文件数
创建 @cmclink/icons 共享图标包 packages/icons/ 新建
迁移 SVG 到共享包 assets/svgs/packages/icons/src/svg/ 30 个 SVG
重写 CmcIcon 组件 支持 SVG Sprite + ep: 前缀 1 个文件
main-app.ts 配置共享图标扫描 iconDirs 新增共享目录 1 个文件
child-app.ts 同步配置 新增 svgIcons 选项 1 个文件
替换 <SvgIcon><CmcIcon> 删除 import + 替换标签 10 个文件
替换 <Icon><CmcIcon> iconname,PascalCase 9 个文件
删除 icon.min.js 移除 iconfont 全量加载 -115KB
删除 Icon/ 目录 Icon.vue + IconSelect.vue + data.ts -1962 行
删除 SvgIcon.vue 旧 SVG 图标组件 1 个文件
清理 setupGlobCom 移除旧 Icon 全局注册 1 个文件
清理 Form.vue <Icon><CmcIcon> (JSX) 1 个文件

6.3 收益量化

指标 治理前 治理后 收益
图标方案数量 5 套 1 套 维护成本降低 80%
首屏资源 +115KB (iconfont JS) 0KB (按需加载) -115KB
硬编码图标列表 1962 行 0 行 消除过时风险
子应用图标配置 每个应用单独维护 零配置 开发效率提升
图标使用入口 3 个组件 1 个组件 心智负担降低

七、使用指南

7.1 SVG Sprite 图标(推荐)

vue 复制代码
<!-- 基础用法 -->
<CmcIcon name="Home" />

<!-- 预设尺寸 -->
<CmcIcon name="Star" size="lg" />

<!-- 自定义像素 -->
<CmcIcon name="Document" :size="32" />

<!-- 主题色 -->
<CmcIcon name="Warning" color="danger" />

<!-- 旋转动画 -->
<CmcIcon name="Loading" spin />

<!-- 可点击 -->
<CmcIcon name="Close" clickable @click="handleClose" />

7.2 Element Plus 图标

vue 复制代码
<!-- ep: 前缀,异步加载 -->
<CmcIcon name="ep:Edit" />
<CmcIcon name="ep:Delete" color="danger" />
<CmcIcon name="ep:Search" :size="18" />
<CmcIcon name="ep:Loading" spin />

7.3 添加新图标

  1. 将 SVG 文件放入 packages/icons/src/svg/
  2. 文件名即图标名(如 MyIcon.svg<CmcIcon name="MyIcon" />
  3. 无需任何额外配置,Vite HMR 自动生效
  4. 所有子应用自动可用

7.4 应用级图标覆盖

如果某个子应用需要定制某个图标的样式:

  1. apps/{app}/src/assets/svgs/ 放入同名 SVG
  2. 本地版本自动覆盖共享版本
  3. 其他子应用不受影响

八、目录结构

bash 复制代码
packages/
├── icons/                          # 共享图标包
│   ├── package.json                # @cmclink/icons
│   ├── README.md                   # 使用文档
│   └── src/
│       ├── index.ts                # 导出图标目录路径常量
│       └── svg/                    # 所有共享 SVG 图标
│           ├── Home.svg
│           ├── Star.svg
│           ├── UnStar.svg
│           ├── Logo.svg
│           ├── TopMenu.svg
│           └── ...
├── ui/
│   └── src/base/CmcIcon/
│       ├── index.ts
│       └── src/CmcIcon.vue         # 统一图标组件
└── vite-config/
    └── src/
        ├── main-app.ts             # iconDirs: [共享, 本地]
        └── child-app.ts            # svgIcons 选项

九、FAQ

Q: IconSelect 组件删除后,图标选择功能怎么办?

A: IconSelect 依赖已删除的 data.ts(1962 行硬编码列表)。如果业务确实需要图标选择器,建议基于 @element-plus/icons-vue 的导出列表动态生成,而非硬编码。后续可在 @cmclink/ui 中实现新版 CmcIconPicker

Q: 子应用还在外部独立仓库,如何使用共享图标?

A: 当前 child-app.tsiconDirs 使用相对路径 ../../packages/icons/src/svg,仅适用于 Monorepo 内的子应用。外部子应用迁入 Monorepo 后自动生效。迁入前可通过 extraPlugins 自行配置 vite-plugin-svg-icons

Q: 第三方图标库(如 Font Awesome)怎么处理?

A: 当前 CmcIcon 支持 SVG Sprite 和 Element Plus 两种源。如需扩展第三方图标库,可在 CmcIcon 中增加新的前缀识别(如 fa: → Font Awesome),通过异步 import 按需加载。但中后台系统建议优先使用 Element Plus 图标,保持设计一致性。

相关推荐
mCell12 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell13 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭13 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清13 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木13 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_6070766013 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声14 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易14 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得014 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
anOnion14 小时前
构建无障碍组件之Dialog Pattern
前端·html·交互设计