← 返回知识库
前端工程微前端qiankun组件库Design Tokentree-shaking工程化

微前端与自研组件库

qiankun 微前端选型与坑、样式隔离、JS 沙箱边界、通信与公共依赖;自研组件库的 Design Token、按需引入、tree-shaking 与防回退。

微前端与自研组件库

前端工程化的两个实战方向:把单体拆成微前端、把重复 UI 收敛成组件库。重心不在概念,而在决策链、坑、效益、代价

第一部分 · 微前端(qiankun)

为什么要拆:把痛点变成数字

答这题千万别说「因为微前端很流行」。先把旧架构问题量化:

维度单体痛在哪
构建时长全量构建 21 分钟,改一行也要等
发布节奏统一发版,一个团队没准备好全员卡住
技术栈锁定旧版本,升级牵一发动全身
故障半径任一模块报错可能白屏全站
组织4 团队改同一份路由表,必冲突

一句话:单体的瓶颈不在性能,在组织协作——微前端解决的首要问题是解耦发布权,其次才是技术栈自由度。

选型决策表

方案隔离能力体验改造成本适用
iframe最强差(路由不同步/弹窗被裁)异构老系统嵌入
qiankun好(JS 沙箱+样式隔离)存量 Vue/React 渐进迁移
Module Federation弱(自行处理隔离)中高新建项目/同栈运行时共享
wujie很强(iframe+Web Components)需要强隔离

上微前端的门槛是「多团队 + 独立发布诉求」,不是「代码多」

迁移六阶段

盘点 → 划界 → 基座 → 试点 → 批量 → 收尾

  1. 划界按业务域拆,不按技术分层拆(错的:header-app/table-app;对的:权限中心/资产管理)。主应用只留横切能力(登录态/权限/菜单/路由/埋点),绝不放业务组件——这条守不住,主应用半年后又变第二个单体。
  2. 基座:主应用持顶层路由表;token 存主应用,通过 props 下发;权限双层(路由级能不能进 + 按钮级能不能点)。
  3. 试点:选最小最独立模块先迁,产出《子应用接入 SOP》。
  4. 批量:路由级灰度(新旧路由并行、配置开关切流、出问题一键切回)。
  5. 收尾:清老路由/老配置,上子应用维度监控 + CI 门禁。

关键设计

样式隔离(弹窗是重灾区)

策略原理代价
strictStyleIsolationShadow DOM 真隔离弹窗挂 body 丢样式、三方库不兼容
experimentalStyleIsolation选择器加前缀挂 body 仍逃逸
构建期加前缀(PostCSS)构建时给选择器加子应用前缀最稳、无运行时开销

一句话:样式隔离的难点不是隔离子应用内部样式,是隔离挂到 body 上的那部分样式(弹窗、抽屉、下拉全在 body 上)。解法:统一 getPopupContainer + 构建期前缀 + experimentalStyleIsolation 兜底。

JS 沙箱边界(区分度所在)

qiankun 的 ProxySandbox 给每个子应用一个 fakeWindow,读写都走代理——但它只拦 window.xxx = ... 属性操作,拦不住三类副作用

  1. 事件监听window.addEventListener 沙箱不管,卸载后回调仍在跑
  2. 定时器setInterval 没清,切走还在跑
  3. 全局副作用:改 document.body 的 class、往 document 挂节点、prototype 污染

解法:自研副作用收集器,mount 时注册、unmount 时统一清理——把「记得清理」从人的纪律变成框架的默认行为

通信:三类场景三种手段

场景手段
主 → 子:传上下文props(登录态/request 实例,最简单可靠)
状态共享(广播)initGlobalState + onGlobalStateChange
事件通知(一次性)事件总线(基于 CustomEvent,成对 on/off 防泄漏

原则:能用 props 就别用全局状态(隐式依赖等于把单体耦合搬了个地方)。

公共依赖共享

Vue/UI 库/axios 走 externals 统一共享(大且版本能统一);业务工具库各自打包。公共依赖升版本必须主应用先升 + 全子应用回归

坑清单(15 条核心)

#一句话根因
1弹窗/抽屉样式全丢挂到 body,跑出隔离范围
2子应用样式污染全局全局 CSS 未加前缀
3定时器越跑越多setInterval 未清、监听未解绑,沙箱不拦
4打包后资源 404publicPath 写死
5主子发版不同步状态错乱全局状态/事件契约无版本兼容
6Vue/UI 库加载两份公共依赖未 external
7子应用本地开发困难未做独立运行入口
8刷新 404history 模式 base 与 fallback 未配
9首屏并发风暴prefetch: all 与首屏抢带宽
10mount 拿不到全局状态通信时序问题
11发版后用户仍老页面HTML Entry 被缓存
12三方库沙箱报错依赖 window 真实性
13版本矩阵爆炸子应用独立发版组合态失控
14主题/国际化不生效全局变量读不到或读快照
15埋点/错误归属混乱上报归属规则未定

代价与边界:什么时候不该上

  • 只有一个团队/一个业务域 → 拆了收益为零
  • 模块间耦合极深 → 改造前必须先解耦
  • 应用体量不大(20 页面内)→ 升级构建工具+代码分割就够
  • 团队没有自动化测试和监控 → 微前端把编译期错误变运行期错误,没兜底就是裸奔

金句:微前端是「组织架构的镜像」——解决多团队独立发布的问题。技术问题用技术解,组织问题才用架构解。


第二部分 · 自研组件库(qdma-ui)

为什么自研:先回答「这不是造轮子吗」

先说什么时候不该自研:只需要通用基础组件、团队 <5 人、没有明确业务组件诉求。

再说不自研解决不了的

  1. 三库并存契约不统一(同一业务 3 种按钮、2 套弹窗)——换库只是换一家,多套在契约就永远不统一
  2. 体积失控(三方库大量组件用不上却打不进 tree-shaking)
  3. 业务定制被卡(地址级联、扫码上传三方库改不动)

一句话:自研的不是「基础组件」,是「业务组件契约 + 体积控制权」

Design Token 三层(顺序不能反)

1. 原始值(palette):--qd-color-red-6: #e54d42
2. 语义层(semantic):--qd-color-primary: var(--qd-color-red-6)  ← 业务只认这层
3. 组件级(component):--qd-button-height: 40px

业务只用语义层,换主题 = 覆盖语义层变量;组件内不写死任何颜色值(用 stylelint 卡住硬编码)。

按需引入与 tree-shaking(最经典坑)

「写了 ESM」不等于「能 tree-shake」。体积不降反升的根因:index.tsexport * 全量导出,构建工具因无法确定副作用不敢裁。

解法五件套:

  1. package.json"sideEffects": ["*.css"](明确只有样式有副作用)
  2. 提供多入口@qdma/ui/es/button 按路径引入)
  3. babel-plugin-import 兜底老构建链路
  4. externalpeerDependencies 成对(Vue 必须 external,否则双 Vue 实例响应式失效)
  5. CI 体积对比门禁

坑清单(12 条核心)

#根因
1打包进了 Vue,双实例external 没配全(带路径引用没匹配上)
2体积没降反升全量导出,tree-shaking 失效
3样式顺序错乱CSS 产物顺序不可控
4TS 没类型提示未产出 .d.ts
5按需引入配置复杂没用 ESM 多入口
6组件 API 一改下游全红破坏性变更未走 major
7Storybook 和产物不一致文档跑源码、业务用产物
8主题切换不彻底组件内硬编码色值
9覆盖率高仍有 bug只测函数没测交互路径
10构建产物残缺组件间循环依赖
11业务装不上版本冲突/peerDependencies 没声明
12体积反弹没依赖准入与体积门禁

长效防回退三件套

  1. 体积门禁:PR 对比产物体积,增长 <3% 通过、3~10% 警告、>10% fail
  2. 依赖准入:新增运行时依赖必须评审(有无现成能力/体积多大/能否按需引入)
  3. 性能预算:Lighthouse/体积阈值写进 CI,性能回归一票否决

金句:性能优化不是一次性战役,是要把它变成流水线的一道门禁。没有门禁的优化,三个月后一定回退。