微前端对比 & 各方案实现原理

笔试题(6 题)

1. 微前端核心问题

微前端要解决什么问题?它引入了哪些新的复杂度?适用和不适用的场景分别是什么?

【作答】:

要解决的问题:

1. 团队协作问题:
   - 多团队并行开发,避免代码冲突
   - 不同团队技术栈独立(React、Vue、Angular 可共存)
   - 降低团队间的沟通成本和依赖

2. 代码膨胀问题:
   - 单体应用代码量过大(10万+行代码)
   - 构建时间长(5-10分钟)
   - 开发体验差(热更新慢)

3. 发布部署问题:
   - 单体应用牵一发动全身,任何改动都需要全量发布
   - 发布风险高,回滚代价大
   - 无法做到按模块独立部署

4. 业务隔离问题:
   - 核心业务与边缘业务耦合
   - 不同业务线需要独立迭代
   - 历史代码重构成本高

5. 技术债务问题:
   - 老代码技术栈无法升级(如 React 15 → React 18)
   - 新老代码共存,逐步迁移

引入的新复杂度:

1. 技术复杂度:
   - JS 隔离(沙箱机制)
   - CSS 隔离(样式冲突)
   - 路由劫持与同步
   - 应用间通信机制
   - 公共依赖管理与版本冲突

2. 工程复杂度:
   - 构建配置复杂(主应用 + 多个子应用)
   - 联调困难(本地开发需要启动多个应用)
   - 部署流程复杂(独立部署 + 资源加载)
   - 版本管理复杂(多仓库/Monorepo)

3. 运维复杂度:
   - 资源加载性能(多个应用的 JS/CSS 加载)
   - 错误边界与降级(某个子应用挂了不能影响主应用)
   - 监控与日志(需要区分不同应用的错误)
   - 灰度发布与回滚(多应用协同)

4. 开发体验复杂度:
   - 调试困难(跨应用调试)
   - 本地开发环境搭建复杂
   - 需要学习微前端框架(qiankun、Module Federation)

适用场景:

✅ 大型中后台系统:
   - 多个业务模块(订单、库存、财务、CRM 等)
   - 多团队协作(5+ 团队)
   - 代码量大(10万+ 行)
   - 示例:企业 ERP、电商后台、政务系统

✅ 跨技术栈整合:
   - 历史系统是 Vue,新系统用 React
   - 逐步迁移技术栈,新老系统共存
   - 收购的第三方系统需要整合

✅ 独立业务模块:
   - 业务模块相对独立(低耦合)
   - 各模块独立部署需求强烈
   - 不同模块由不同供应商开发

✅ 多租户 SaaS 平台:
   - 不同租户可能需要不同的定制模块
   - 插件化需求(可动态加载/卸载模块)

不适用场景:

❌ 小型项目:
   - 代码量 < 5万行
   - 团队 < 5 人
   - 引入微前端收益 < 成本

❌ 高性能要求场景:
   - C端应用对首屏性能要求极高(如电商首页)
   - 微前端额外的 JS 加载会影响性能
   - 应优先考虑代码分割、懒加载

❌ 强交互应用:
   - 子应用间需要频繁通信(如实时协作工具)
   - 组件级复用需求强(应考虑组件库,而非微前端)
   - 状态共享复杂(Redux/MobX 全局状态)

❌ 移动端 H5:
   - 移动端性能敏感
   - 额外的框架代码和沙箱机制会影响性能
   - 应优先考虑 PWA、离线化

❌ 技术栈统一的团队:
   - 团队已经统一技术栈(如全部 React)
   - 使用 Monorepo + 模块化即可解决问题
   - 不需要微前端的隔离能力

决策原则:
- 收益 > 成本:解决的问题价值 > 引入的复杂度
- 团队规模:5+ 团队才考虑
- 代码规模:10万+ 行代码才考虑
- 独立部署:如果不需要独立部署,不要用微前端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

2. 微前端方案对比

主流方案对比:iframe / single-spa / qiankun / Module Federation 的核心差异,优缺点对比表。

【作答】:

iframe:
优点:
  ✅ 天然隔离(JS、CSS、DOM 完全隔离)
  ✅ 实现简单,零技术门槛
  ✅ 浏览器原生支持,稳定性好
  ✅ 可以加载任意第三方页面

缺点:
  ❌ 性能差(每个 iframe 是独立的浏览器上下文)
  ❌ 路由同步困难(iframe 内部路由无法同步到主应用 URL)
  ❌ 弹窗/遮罩层体验差(受 iframe 边界限制)
  ❌ 白屏时间长(独立加载所有资源)
  ❌ 内存占用高(多个完整的浏览器上下文)
  ❌ 通信复杂(postMessage,不同域需要 CORS)
  ❌ SEO 不友好

适用场景:
  - 嵌入第三方页面(如支付页面、BI 报表)
  - 对性能要求不高的后台管理系统
  - 需要强隔离的场景(安全性要求高)

---

single-spa:
优点:
k/
   - 各应用独立的组件
   - 原因:独立迭代、避免耦合

2. 治理策略:

   策略1:Monorepo 统一管理
   - 所有应用在一个仓库
   - package.json 统一版本
   - 好处:版本强制统一

   project/
   ├── packages/
   │   ├── main-app/        (主应用)
   │   ├── sub-app-1/       (子应用1)
   │   └── sub-app-2/       (子应用2)
   └── package.json         (统一依赖版本)

   策略2:依赖版本清单
   - 维护一份依赖版本清单(versions.json)
   - 所有应用遵循清单版本

   // versions.json
   {
     "react": "18.2.0",
     "react-dom": "18.2.0",
     "antd": "5.0.0"
   }

   策略3:CI 检查
   - 在 CI 阶段检查依赖版本
   - 不符合规范则阻止部署

   // 检查脚本
   const checkVersions = () => {
     const pkg = require('./package.json')
     if (pkg.dependencies.react !== '18.2.0') {
       throw new Error('React 版本不符合规范')
     }
   }

   策略4:公共依赖中心化
   - 主应用统一管理公共依赖
   - 子应用通过主应用获取

   // 主应用暴露依赖
   window.__SHARED_LIBS__ = {
     react: React,
     reactDOM: ReactDOM,
     antd: antd
   }

   // 子应用使用
   const React = window.__SHARED_LIBS__.react

3. 依赖升级流程:

   步骤1:评估影响范围
   - 哪些应用使用了该依赖
   - 升级是否有 Breaking Changes

   步骤2:统一升级
   - 所有应用同步升级
   - 避免版本碎片化

   步骤3:测试验证
   - 主应用测试
   - 所有子应用测试

   步骤4:灰度发布
   - 先发布一个子应用
   - 验证无问题后全量发布

---

external(外部化):

定义:
- 将依赖标记为外部依赖
- 不打包到 bundle 中
- 运行时从外部获取(如 CDN、window 全局变量)

配置方式:
// webpack.config.js
module.exports = {
  externals: {
    react: 'React',          // window.React
    'react-dom': 'ReactDOM', // window.ReactDOM
    jquery: 'jQuery'         // window.jQuery
  }
}

使用场景:
1. CDN 加载:
   - 通过 CDN 加载 React
   - 所有应用共享同一个 CDN 资源

   <!-- index.html -->
   <script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
   <script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>

2. 主应用提供:
   - 主应用加载依赖到 window
   - 子应用通过 external 使用

   // 主应用
   import React from 'react'
   window.React = React

   // 子应用 webpack 配置
   externals: {
     react: 'React'
   }

优点:
✅ 减少子应用体积
✅ 利用浏览器缓存(CDN)
✅ 强制版本统一(都用同一个 window.React)

缺点:
❌ 需要手动管理加载顺序
❌ CDN 不稳定可能影响可用性
❌ 调试困难(CDN 压缩代码)
❌ 无法按需加载(必须全量加载)

最佳实践:
- 适合稳定的大型依赖(React、Vue)
- 生产环境使用,开发环境关闭(便于调试)
- 配合 CDN fallback(CDN 失败时本地加载)

---

shared(共享依赖,Module Federation):

定义:
- Webpack 5 Module Federation 的共享机制
- 运行时动态协商依赖版本
- 自动去重,按需加载

配置方式:
new ModuleFederationPlugin({
  shared: {
    // 简单配置
    react: '^18.0.0',

    // 详细配置
    'react-dom': {
      singleton: true,           // 单例模式
      requiredVersion: '^18.0.0', // 版本要求
      eager: false,              // 是否立即加载
      strictVersion: false       // 严格版本检查
    }
  }
})

工作机制:
1. 编译时:
   - 分析依赖,生成共享清单
   - 记录版本信息

2. 运行时:
   - 主应用和子应用协商版本
   - 选择合适的版本(通常是最高兼容版本)
   - 共享同一个实例

优点:
✅ 自动版本协商(无需手动管理)
✅ 按需加载(懒加载,减少首屏时间)
✅ 灵活性高(可配置降级策略)
✅ 支持多版本共存(兼容性好)

缺点:
❌ 配置复杂(需要理解 singleton、eager 等概念)
❌ 调试困难(运行时动态加载)
❌ 版本冲突时可能加载多份(体积增加)

适用场景:
- 使用 Webpack 5 + Module Federation
- 依赖版本可能不完全一致
- 需要灵活的版本管理

对比 external:
| 特性       | external       | shared              |
|-----------|----------------|---------------------|
| 加载时机   | 编译时决定      | 运行时协商           |
| 版本管理   | 手动           | 自动                |
| 灵活性    | 低             | 高                  |
| 体积优化   | 好             | 中等                |
| 适用场景   | 稳定依赖       | 动态依赖            |

---

singleton(单例模式):

定义:
- 保证全局只有一个依赖实例
- 所有应用共享同一个实例
- 版本冲突时强制使用一个版本

配置方式:
shared: {
  react: {
    singleton: true  // 开启单例
  }
}

工作机制:
1. 首次加载:
   - 主应用加载 React 18.2.0
   - 注册到全局共享作用域

2. 子应用请求:
   - 子应用需要 React 18.1.0
   - 检测到已有 React 18.2.0
   - 复用 18.2.0(而非加载新版本)

3. 版本冲突:
   - 如果子应用需要 React 17.0.0(不兼容)
   - strictVersion: false → 告警,但仍使用 18.2.0
   - strictVersion: true → 直接报错

为什么需要 singleton:
1. React 的要求:
   - React 不支持多实例(Hooks 依赖单例)
   - 多个 React 实例会导致 Hooks 报错

   错误示例:
   Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

2. 状态管理:
   - Redux、MobX 等状态管理库需要单例
   - 多实例会导致状态不同步

3. 性能考虑:
   - 减少重复加载
   - 降低内存占用

适用依赖:
✅ 必须 singleton:
   - React / React-DOM
   - Vue
   - Redux / MobX
   - React-Router

⚠️ 可选 singleton:
   - Ant Design(建议单例,但多实例也能用)
   - lodash(多实例无影响,但浪费资源)

❌ 不需要 singleton:
   - 纯工具函数
   - 业务组件

配置示例:
shared: {
  // 必须单例
  react: {
    singleton: true,
    strictVersion: true  // 严格模式,版本不匹配报错
  },

  // 建议单例
  antd: {
    singleton: true,
    strictVersion: false  // 宽松模式,版本不匹配告警
  },

  // 可选单例
  lodash: {
    singleton: false  // 允许多版本
  }
}

---

版本冲突处理:

1. 预防策略:

   策略1:版本锁定(package-lock.json)
   - 锁定所有依赖的精确版本
   - 避免安装时版本变化

   策略2:版本规范
   - 制定版本规范文档
   - 统一主要依赖的版本(React、Ant Design)

   策略3:依赖检查工具
   - 开发工具检查依赖版本
   - CI 阶段强制检查

   // check-deps.js
   const checkDeps = () => {
     const mainPkg = require('../main-app/package.json')
     const subPkg = require('../sub-app/package.json')

     if (mainPkg.dependencies.react !== subPkg.dependencies.react) {
       throw new Error('React 版本不一致')
     }
   }

2. 运行时处理:

   方案1:宽松模式(默认)
   shared: {
     react: {
       singleton: true,
       strictVersion: false,  // 不严格检查
       requiredVersion: '^18.0.0'
     }
   }
   - 版本不匹配时告警,但继续运行
   - 风险:可能运行时错误

   方案2:严格模式
   shared: {
     react: {
       singleton: true,
       strictVersion: true  // 严格检查
     }
   }
   - 版本不匹配直接报错
   - 强制修复版本问题

   方案3:降级加载
   shared: {
     react: {
       singleton: false  // 允许多版本
     }
   }
   - 各应用加载各自版本
   - 缺点:体积增加、多实例问题

3. 监控与告警:

   // 版本冲突监控
   const monitorSharedDeps = () => {
     const sharedScope = window.__webpack_share_scopes__.default

     Object.keys(sharedScope).forEach(dep => {
       const versions = Object.keys(sharedScope[dep])
       if (versions.length > 1) {
         console.warn(`${dep} 存在多个版本:`, versions)
         // 上报监控系统
         reportVersionConflict(dep, versions)
       }
     })
   }

4. 实际案例与解决方案:

   案例1:React 版本冲突
   问题:主应用 React 18,子应用 React 17

   方案:升级子应用
   ✅ 推荐:升级子应用到 React 18
   ⚠️ 临时:singleton: false,加载两份(可能 Hooks 报错)

   案例2:Ant Design 版本冲突
   问题:不同子应用使用 Ant Design 4 和 5

   方案1:统一升级到 Ant Design 5
   方案2:各自加载(不共享 antd)

   shared: {
     // antd 不共享,各自打包
   }

   案例3:工具库版本冲突
   问题:lodash 4.17.20 和 4.17.21

   方案:允许多版本(影响小)
   shared: {
     lodash: {
       singleton: false
     }
   }

5. 最佳实践总结:

   ✅ 核心依赖必须统一版本(React、Vue)
   ✅ 使用 Monorepo 或版本清单管理
   ✅ CI 阶段检查依赖版本
   ✅ 建立版本升级机制(定期统一升级)
   ✅ 监控运行时版本冲突

   ❌ 避免过度使用 strictVersion(容易报错)
   ❌ 避免版本碎片化(每个应用版本都不同)
   ❌ 避免手动修改 package-lock.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413

面试题(4 题)

1. 微前端适用性判断

你的业务适不适合微前端?给出判断标准、评估维度与反例(什么情况下不应该用)。

【作答】:

判断标准(评分表):

维度1:团队规模(权重 30%)
├─ 5+ 个独立团队:+30分 ✅ 强烈推荐
├─ 3-4 个团队:+20分 ⚠️ 可以考虑
├─ 1-2 个团队:+5分 ❌ 不推荐
└─ 理由:微前端主要解决多团队协作问题

维度2:代码规模(权重 25%)
├─ 10万+ 行代码:+25分 ✅ 强烈推荐
├─ 5-10万 行代码:+15分 ⚠️ 可以考虑
├─ < 5万 行代码:+5分 ❌ 不推荐
└─ 理由:代码量大才有拆分价值

维度3:独立部署需求(权重 25%)
├─ 必须独立部署:+25分 ✅ 强烈推荐
├─ 偶尔独立部署:+15分 ⚠️ 可以考虑
├─ 不需要独立部署:+0分 ❌ 不推荐
└─ 理由:独立部署是微前端的核心价值

维度4:技术栈差异(权重 15%)
├─ 多技术栈(React+Vue):+15分 ✅ 推荐
├─ 单技术栈,但需要渐进式迁移:+10分 ⚠️ 可以考虑
├─ 单技术栈统一:+0分 ⚠️ 收益不明显
└─ 理由:技术栈统一可用 Monorepo

维度5:业务耦合度(权重 5%)
├─ 业务模块独立(低耦合):+5分 ✅ 推荐
├─ 业务有一定依赖:+3分 ⚠️ 可以考虑
├─ 业务高度耦合:-10分 ❌ 不推荐
└─ 理由:高耦合会导致频繁跨应用通信

评分规则:
- 80-100分:✅ 强烈推荐微前端
- 60-79分:⚠️ 可以考虑,需评估收益成本
- < 60分:❌ 不推荐,收益 < 成本

---

评估维度(详细):

1. 团队维度:
   ✅ 适合:
   - 多团队并行开发(5+ 团队)
   - 团队地域分散(不同城市)
   - 团队技术栈不同
   - 团队独立性强(各自负责一块业务)

   ❌ 不适合:
   - 单一小团队(< 5人)
   - 团队紧密协作(频繁跨团队开发)
   - 团队技术能力弱(学习成本高)

2. 业务维度:
   ✅ 适合:
   - 多个独立业务模块(订单、库存、财务)
   - 业务模块相对独立(低耦合)
   - 不同业务迭代节奏不同
   - 中后台系统(ToB)

   ❌ 不适合:
   - 单一业务(如单纯的博客系统)
   - 业务高度耦合(频繁跨模块调用)
   - C端高性能要求(首屏性能敏感)
   - 强交互应用(实时协作)

3. 技术维度:
   ✅ 适合:
   - 历史技术栈迁移(Vue 2 → Vue 3 / React)
   - 多技术栈并存需求
   - 大型单体应用重构
   - 需要独立升级技术栈

   ❌ 不适合:
   - 技术栈统一且稳定
   - 团队对微前端不熟悉
   - 对性能要求极高(额外开销不可接受)

4. 部署维度:
   ✅ 适合:
   - 需要独立部署(高频发布)
   - 不同模块发布周期不同
   - 需要灰度发布
   - 回滚风险隔离

   ❌ 不适合:
   - 统一发布即可
   - 发布频率低(月度发布)
   - 部署流程复杂度不可接受

5. 规模维度:
   ✅ 适合:
   - 代码量 > 10万行
   - 构建时间 > 5分钟
   - 应用启动时间 > 30秒
   - 团队 > 10人

   ❌ 不适合:
   - 小型项目(< 5万行)
   - 构建时间可接受(< 2分钟)
   - 团队小(< 5人)

---

反例(不应该用微前端的场景):

反例1:个人博客系统
- 代码量:5000 行
- 团队:1 人
- 业务:单一博客功能
- 结论:❌ 完全没必要,用 Next.js 单体应用即可
- 理由:引入微前端纯属过度设计

反例2:创业公司 MVP 产品
- 代码量:2 万行
- 团队:3 人
- 业务:快速迭代验证
- 结论:❌ 不适合,浪费时间
- 理由:
  · 团队小,不存在协作问题
  · 业务变化快,架构复杂度是负担
  · 应优先验证产品,而非过度工程化

反例3:C端电商首页
- 场景:首屏性能要求极高(< 1秒)
- 用户:百万级 DAU
- 结论:❌ 不适合
- 理由:
  · 微前端额外的 JS 加载影响首屏
  · 应优先考虑 SSR、代码分割、CDN
  · 可以用微前端做中后台管理,但不做C端

反例4:强交互应用(实时协作工具)
- 场景:在线文档编辑(类 Google Docs)
- 特点:组件间频繁通信、状态高度共享
- 结论:❌ 不适合
- 理由:
  · 应用间通信成本高
  · 状态同步复杂
  · 应优先考虑组件化,而非应用化

反例5:技术栈统一的团队
- 团队:全部使用 React
- 规模:10 人
- 代码:8 万行
- 结论:⚠️ 不一定需要微前端
- 理由:
  · 使用 Monorepo + 代码分割可能更合适
  · 微前端的技术栈隔离能力用不上
  · 除非有强烈的独立部署需求

反例6:移动端 H5 应用
- 场景:移动端商城
- 特点:性能敏感、流量成本高
- 结论:❌ 不适合
- 理由:
  · 额外的框架代码影响性能
  · 移动端网络不稳定,加载多个应用慢
  · 应优先考虑 PWA、离线化

---

决策流程图:

第一步:基础判断
Q1: 团队 >= 5 人?
  ├─ 否 → ❌ 不建议(考虑 Monorepo)
  └─ 是 → 继续

Q2: 代码 >= 10 万行?
  ├─ 否 → ❌ 不建议(考虑模块化)
  └─ 是 → 继续

Q3: 需要独立部署?
  ├─ 否 → ❌ 不建议(微前端核心价值缺失)
  └─ 是 → 继续

第二步:收益评估
Q4: 是否存在以下痛点(2个以上)?
  □ 多团队代码冲突频繁
  □ 构建时间过长(> 5分钟)
  □ 技术栈迁移困难
  □ 发布风险大(牵一发动全身)
  □ 业务模块耦合严重

  ├─ 是(2+ 痛点)→ ✅ 推荐微前端
  └─ 否 → ❌ 不建议

第三步:成本评估
Q5: 团队是否能承受?
  □ 学习成本(qiankun / Module Federation)
  □ 改造成本(子应用需要改造)
  □ 维护成本(多应用联调、部署)

  ├─ 能承受 → ✅ 可以尝试
  └─ 不能承受 → ❌ 暂缓

---

实际案例分析:

案例1:某大型电商后台(适合)
- 团队:15 人,3 个业务组
- 代码:30 万行
- 业务:订单、库存、财务、CRM
- 技术栈:React 为主,历史遗留 jQuery
- 痛点:
  · 构建时间 8 分钟
  · 任何改动都需要全量发布
  · 新人上手困难(代码太多)
- 方案:✅ 使用 qiankun 微前端
- 效果:
  · 构建时间降至 2 分钟(各子应用独立构建)
  · 独立部署,发布频率从月度到周度
  · 新人只需关注自己的子应用

案例2:某政务系统(适合)
- 团队:多个外包团队(技术能力参差不齐)
- 代码:50 万行+
- 业务:审批、公文、档案、统计等20+ 模块
- 技术栈:不统一(有 Vue、React、Angular)
- 方案:✅ 使用 qiankun,iframe 兜底
- 收益:
  · 各团队独立开发,减少冲突
  · 技术栈隔离,各团队自主选择
  · 模块独立部署,降低风险

案例3:某创业公司 ToC 产品(不适合)
- 团队:5 人
- 代码:3 万行
- 业务:社交 App
- 方案:❌ 不使用微前端
- 理由:
  · 团队小,不存在协作问题
  · 代码量小,模块化即可
  · 性能敏感,不能接受额外开销
  · 应专注于产品迭代

---

决策建议:

何时应该用微前端:
✅ 大型中后台系统(ERP、CRM、政务)
✅ 多团队协作(5+ 团队)
✅ 代码量大(10万+ 行)
✅ 需要独立部署
✅ 技术栈迁移需求
✅ 收益明显 > 成本

何时不应该用微前端:
❌ 小型项目(< 5万行)
❌ 小团队(< 5人)
❌ C端高性能要求
❌ 强交互应用
❌ 不需要独立部署
❌ 技术栈统一且稳定
❌ 团队技术能力不足

中间地带(谨慎评估):
⚠️ 中型项目(5-10 万行)
⚠️ 中型团队(5-10 人)
⚠️ 有独立部署需求,但频率不高
⚠️ 技术栈统一,但有少量历史代码

建议:先从简单的方案开始(Monorepo、代码分割),问题严重时再引入微前端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266

2. 微前端基础设施

微前端下如何做"统一登录态/权限/埋点/错误上报"?如何设计主子应用的通信机制?

【作答】:

一、统一登录态

问题:
- 各子应用都需要获取用户信息
- Token 需要统一管理
- 登录状态需要同步

方案设计:

1. 主应用统一管理(推荐):

   架构:
   ┌──────────────────────────┐
   │      主应用(Main App)    │
   │  ├─ 登录页面              │
   │  ├─ Token 存储(LocalStorage)│
   │  ├─ 用户信息缓存           │
   │  └─ 登出逻辑              │
   └──────────────────────────┘
              ↓ props传递
   ┌──────────────────────────┐
   │      子应用(Sub App)     │
   │  ├─ 接收 token           │
   │  ├─ 接收 user info       │
   │  └─ 接收 logout 方法      │
   └──────────────────────────┘

   实现(qiankun):
   // 主应用
   import { registerMicroApps, start } from 'qiankun'

   const getToken = () => localStorage.getItem('token')
   const getUserInfo = () => JSON.parse(localStorage.getItem('userInfo'))
   const logout = () => {
     localStorage.removeItem('token')
     window.location.href = '/login'
   }

   registerMicroApps([
     {
       name: 'app1',
       entry: '//localhost:8081',
       container: '#container',
       activeRule: '/app1',
       props: {
         getToken,
         getUserInfo,
         logout,
         // 或者直接传递
         token: getToken(),
         userInfo: getUserInfo()
       }
     }
   ])

   // 子应用
   export async function mount(props) {
     const { getToken, getUserInfo, logout } = props

     // 使用 token
     axios.defaults.headers.common['Authorization'] = `Bearer ${getToken()}`

     // 使用用户信息
     const userInfo = getUserInfo()
     store.commit('SET_USER', userInfo)

     // 登出
     <Button onClick={logout}>退出登录</Button>
   }

2. 共享 Token 存储:

   方案1:LocalStorage 命名空间
   // 主应用设置
   localStorage.setItem('global:token', token)
   localStorage.setItem('global:userInfo', JSON.stringify(userInfo))

   // 子应用读取
   const token = localStorage.getItem('global:token')

   方案2:Cookie(跨域需要配置)
   // 主应用设置 cookie(设置 domain)
   document.cookie = `token=${token}; path=/; domain=.example.com`

   // 子应用自动携带(同域)

3. SSO 单点登录(企业级):

   流程:
   1. 用户访问子应用
   2. 子应用检测无 token,跳转 SSO 登录页
   3. SSO 登录成功,颁发 token
   4. 回调到子应用,携带 token
   5. 子应用保存 token

   优点:
   - 多系统统一认证
   - 安全性高
   - 支持第三方应用

   缺点:
   - 需要独立的 SSO 服务
   - 实现复杂度高

---

二、统一权限管理

问题:
- 不同用户权限不同(菜单、按钮、路由)
- 各子应用都需要权限控制

方案设计:

1. 主应用统一权限(推荐):

   // 主应用
   const permissions = {
     menu: ['order', 'inventory', 'finance'],  // 菜单权限
     button: ['order:create', 'order:delete'], // 按钮权限
     api: ['/api/order/*']                      // 接口权限
   }

   registerMicroApps([{
     name: 'app1',
     props: {
       permissions,
       hasPermission: (code) => permissions.button.includes(code)
     }
   }])

   // 子应用使用
   export function mount(props) {
     const { hasPermission } = props

     // 按钮权限控制
     {hasPermission('order:create') && <Button>创建订单</Button>}

     // 路由权限控制
     <Route path="/order/create"
            render={() => hasPermission('order:create')
              ? <CreateOrder />
              : <NoPermission />
            }
     />
   }

2. 权限路由自动生成:

   // 主应用
   const routes = [
     { path: '/app1', app: 'app1', permission: 'app1:access' },
     { path: '/app2', app: 'app2', permission: 'app2:access' }
   ]

   const accessibleRoutes = routes.filter(route =>
     hasPermission(route.permission)
   )

   // 根据权限动态注册子应用
   registerMicroApps(accessibleRoutes.map(route => ({
     name: route.app,
     activeRule: route.path,
   })))

3. 菜单权限过滤:

   // 主应用菜单配置
   const menuConfig = [
     { title: '订单管理', path: '/app1', permission: 'order:view' },
     { title: '库存管理', path: '/app2', permission: 'inventory:view' },
     { title: '财务管理', path: '/app3', permission: 'finance:view' }
   ]

   const filteredMenu = menuConfig.filter(menu =>
     hasPermission(menu.permission)
   )

---

三、统一埋点

问题:
- 需要收集用户行为(页面访问、按钮点击)
- 各子应用需要统一埋点标准

方案设计:

1. 主应用提供埋点SDK(推荐):

   // 主应用初始化埋点
   import Track from '@company/track-sdk'

   const tracker = new Track({
     appId: 'main-app',
     userId: getUserId()
   })

   // 注入到子应用
   registerMicroApps([{
     name: 'app1',
     props: {
       tracker,
       track: (event, data) => tracker.track(event, data)
     }
   }])

   // 子应用使用
   export function mount(props) {
     const { track } = props

     // 页面访问埋点
     useEffect(() => {
       track('page_view', { page: '/order/list' })
     }, [])

     // 按钮点击埋点
     <Button onClick={() => {
       track('button_click', { button: 'create_order' })
       createOrder()
     }}>创建订单</Button>
   }

2. 路由切换自动埋点:

   // 主应用监听路由变化
   import { addGlobalUncaughtErrorHandler } from 'qiankun'

   window.addEventListener('popstate', () => {
     tracker.track('route_change', {
       from: prevPath,
       to: location.pathname,
       app: getCurrentApp()
     })
   })

3. 全局事件自动采集(无侵入):

   // 主应用全局监听
   document.addEventListener('click', (e) => {
     const target = e.target
     const trackData = target.dataset.track

     if (trackData) {
       tracker.track('auto_click', JSON.parse(trackData))
     }
   }, true)

   // 子应用使用 data 属性
   <Button data-track='{"type":"create_order"}'>创建</Button>

4. 性能监控埋点:

   // 主应用
   const performance = {
     appLoad: (app, time) => {
       tracker.track('app_load', { app, time })
     },
     apiRequest: (url, time) => {
       tracker.track('api_request', { url, time })
     }
   }

   // qiankun 生命周期埋点
   addGlobalUncaughtErrorHandler((event, app) => {
     tracker.track('app_load_time', {
       app,
       time: performance.now()
     })
   })

---

四、统一错误上报

问题:
- 子应用错误需要捕获
- 错误需要区分来源(哪个子应用)
- 不能让子应用错误影响主应用

方案设计:

1. 全局错误捕获(主应用):

   // 主应用
   import { addGlobalUncaughtErrorHandler } from 'qiankun'

   // JS 错误捕获
   window.addEventListener('error', (event) => {
     const currentApp = getCurrentApp()

     reportError({
       type: 'js_error',
       app: currentApp,
       message: event.message,
       stack: event.error?.stack,
       url: location.href
     })
   })

   // Promise 错误捕获
   window.addEventListener('unhandledrejection', (event) => {
     reportError({
       type: 'promise_error',
       app: getCurrentApp(),
       reason: event.reason
     })
   })

   // qiankun 应用加载错误
   addGlobalUncaughtErrorHandler((event, app) => {
     reportError({
       type: 'app_load_error',
       app: app.name,
       error: event
     })
   })

2. 子应用错误边界:

   // React 子应用
   class ErrorBoundary extends React.Component {
     componentDidCatch(error, errorInfo) {
       // 通过 props 上报错误
       this.props.reportError({
         type: 'react_error',
         error: error.toString(),
         componentStack: errorInfo.componentStack
       })
     }

     render() {
       if (this.state.hasError) {
         return <h1>子应用出错了</h1>
       }
       return this.props.children
     }
   }

   // mount 时包裹
   export function mount(props) {
     const { reportError } = props

     ReactDOM.render(
       <ErrorBoundary reportError={reportError}>
         <App />
       </ErrorBoundary>,
       container
     )
   }

3. 错误隔离与降级:

   // 主应用
   registerMicroApps([{
     name: 'app1',
     entry: '//localhost:8081',
     loader: (loading) => {
       if (loading) {
         showLoading()
       } else {
         hideLoading()
       }
     }
   }], {
     // 错误处理
     errorBoundary: (error, app) => {
       // 显示降级UI
       showErrorPage(app.name, error)

       // 上报错误
       reportError({ app: app.name, error })

       // 阻止错误冒泡,保护主应用
       return true
     }
   })

4. 错误上报服务:

   // 错误上报 SDK
   class ErrorReporter {
     report(error) {
       const errorData = {
         ...error,
         timestamp: Date.now(),
         userAgent: navigator.userAgent,
         url: location.href,
         userId: getUserId(),
         // 区分应用来源
         source: error.app || 'main-app'
       }

       // 上报到监控平台(Sentry、阿里云 ARMS)
       fetch('/api/error-report', {
         method: 'POST',
         body: JSON.stringify(errorData)
       })
     }
   }

---

五、主子应用通信机制设计

问题:
- 主应用需要通知子应用(如登出、权限变更)
- 子应用需要通知主应用(如标题变更、badge 更新)
- 子应用间需要通信(如数据共享)

方案对比:

1. Props 传递(单向,简单场景):

   适用:主→子单向传递

   // 主应用
   registerMicroApps([{
     name: 'app1',
     props: {
       data: { user: {} },
       onEvent: (type, data) => { /* 主应用处理子应用事件 */ }
     }
   }])

   // 子应用
   export function mount(props) {
     const { data, onEvent } = props

     // 使用数据
     console.log(props.data.user)

     // 通知主应用
     onEvent('badge-update', { count: 5 })
   }

   优点:简单直接
   缺点:只能主→子,子应用无法主动获取最新数据

2. Actions 通信(qiankun 推荐):

   适用:主↔子双向通信,状态共享

   // 主应用
   import { initGlobalState } from 'qiankun'

   const actions = initGlobalState({
     user: { name: '张三' },
     token: 'xxx',
     theme: 'dark'
   })

   // 监听变化
   actions.onGlobalStateChange((state, prev) => {
     console.log('状态变化', state, prev)
   })

   // 修改状态
   actions.setGlobalState({ user: { name: '李四' } })

   // 传递给子应用
   registerMicroApps([{
     name: 'app1',
     props: { actions }
   }])

   // 子应用
   let globalActions

   export function mount(props) {
     globalActions = props.actions

     // 监听变化
     globalActions.onGlobalStateChange((state, prev) => {
       // 更新本地状态
       store.commit('SET_USER', state.user)
     })

     // 修改全局状态
     globalActions.setGlobalState({ theme: 'light' })
   }

   优点:双向通信,状态同步
   缺点:耦合度较高

3. EventBus(自定义事件):

   适用:松耦合通信,跨应用事件

   // 事件总线(主应用创建)
   class EventBus {
     constructor() {
       this.events = {}
     }

     on(event, callback) {
       if (!this.events[event]) {
         this.events[event] = []
       }
       this.events[event].push(callback)
     }

     emit(event, data) {
       if (this.events[event]) {
         this.events[event].forEach(cb => cb(data))
       }
     }

     off(event, callback) {
       if (this.events[event]) {
         this.events[event] = this.events[event].filter(cb => cb !== callback)
       }
     }
   }

   const eventBus = new EventBus()

   // 主应用
   eventBus.on('logout', () => {
     // 处理登出
   })

   registerMicroApps([{
     props: { eventBus }
   }])

   // 子应用1发布
   export function mount(props) {
     const { eventBus } = props
     eventBus.emit('logout')
   }

   // 子应用2订阅
   export function mount(props) {
     const { eventBus } = props
     eventBus.on('logout', () => {
       // 响应登出
     })
   }

   优点:松耦合,灵活
   缺点:难以追踪,调试困难

4. SharedWorker(跨标签页通信):

   适用:多标签页数据同步

   const worker = new SharedWorker('/shared-worker.js')

   // 发送消息
   worker.port.postMessage({ type: 'update', data: {} })

   // 接收消息
   worker.port.onmessage = (e) => {
     console.log(e.data)
   }

5. LocalStorage + 监听(简单场景):

   // 主应用
   localStorage.setItem('global_message', JSON.stringify({ type: 'logout' }))

   // 子应用监听
   window.addEventListener('storage', (e) => {
     if (e.key === 'global_message') {
       const message = JSON.parse(e.newValue)
       handleMessage(message)
     }
   })

---

通信机制选择建议:

| 场景 | 推荐方案 | 理由 |
|------|---------|------|
| 主→子传递初始数据 | Props | 简单直接 |
| 主↔子双向通信 | Actions | 官方推荐,状态同步 |
| 子→主事件通知 | Props回调 | 简单场景 |
| 子↔子通信 | EventBus | 解耦 |
| 跨标签页通信 | SharedWorker / BroadcastChannel | 浏览器原生能力 |
| 复杂状态管理 | Redux + 共享store | 统一状态管理 |

最佳实践:
✅ 优先使用 qiankun Actions(官方方案)
✅ 状态最小化(只共享必要的状态)
✅ 单向数据流(主应用是唯一数据源)
✅ 避免频繁通信(性能开销)
✅ 清理监听器(unmount 时off)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589

3. 微前端部署与发布

讲清楚"资源加载、预加载、灰度发布、回滚"在微前端架构里的方案设计。

【作答】:

一、资源加载方案

问题:
- 子应用资源如何加载(JS、CSS、图片)
- 资源路径如何处理(相对路径 vs 绝对路径)
- 如何避免资源404

方案设计:

1. HTML Entry(qiankun 推荐):

   原理:加载子应用的 HTML,提取其中的 JS/CSS

   优点:
   ✅ 与子应用部署方式一致
   ✅ 支持相对路径自动转换
   ✅ 支持动态脚本、样式

   // qiankun 使用
   registerMicroApps([{
     name: 'app1',
     entry: 'http://localhost:8081',  // HTML 地址
     container: '#container'
   }])

   加载流程:
   1. 主应用请求 http://localhost:8081/index.html
   2. qiankun 解析 HTML,提取 \<script\>、<link>
   3. 提取的资源相对路径转绝对路径
   4. 按顺序加载 JS/CSS
   5. 执行 JS,调用生命周期函数

2. JS Entry(传统方案):

   原理:直接加载子应用的 JS 入口文件

   // single-spa 使用
   registerApplication({
     name: 'app1',
     app: () => System.import('http://localhost:8081/main.js')
   })

   要求:
   - 子应用必须打包为 UMD 格式
   - 需要手动处理 CSS 加载
   - 需要配置 publicPath(资源路径)

   // webpack.config.js(子应用)
   output: {
     library: 'app1',
     libraryTarget: 'umd',
     publicPath: 'http://localhost:8081/'  // 必须配置
   }

3. 资源路径处理:

   问题:子应用使用相对路径导致 404

   // 子应用代码
   <img src="./assets/logo.png" />
   ↓ 实际请求
   http://main.com/assets/logo.png  // ❌ 错误(主应用域名)

   解决方案:

   方案1:配置 publicPath(编译时)
   // webpack.config.js
   output: {
     publicPath: process__.env.NODE_ENV === 'production'
       ? 'https://cdn.app1.com/'
       : 'http://localhost:8081/'
   }

   方案2:动态 publicPath(运行时)
   // 子应用入口文件
   if (window.__POWERED_BY_QIANKUN__) {
     __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
   }

   方案3:使用绝对路径(推荐)
   <img src="https://cdn.app1.com/assets/logo.png" />

4. 按需加载:

   // React 懒加载
   const OrderList = React.lazy(() => import('./OrderList'))

   <Suspense fallback={<Loading />}>
     <OrderList />
   </Suspense>

   注意:路由懒加载也需要配置 publicPath

---

二、预加载方案

问题:
- 首次进入子应用白屏时间长
- 用户体验差

方案设计:

1. qiankun 预加载(prefetch):

   // 启动时配置
   start({
     prefetch: true  // 默认 true
   })

   策略:
   - prefetch: true
     → 第一个子应用加载完成后,自动预加载其他子应用

   - prefetch: 'all'
     → 主应用 mounted 后,立即预加载所有子应用

   - prefetch: ['app1', 'app2']
     → 只预加载指定子应用

   - prefetch: (apps) => { /* 自定义策略 */ }
     → 自定义预加载逻辑

2. 手动预加载:

   import { prefetchApps } from 'qiankun'

   // 鼠标悬停时预加载
   <MenuItem
     onMouseEnter={() => prefetchApps(['app1'])}
     onClick={() => navigateTo('/app1')}
   >
     订单管理
   </MenuItem>

   // 空闲时预加载
   if ('requestIdleCallback' in window) {
     requestIdleCallback(() => {
       prefetchApps(['app1', 'app2'])
     })
   }

3. 预加载优化策略:

   策略1:核心应用优先
   prefetchApps(['order-app'], {
     fetch: (url) => fetch(url, { priority: 'high' })
   })

   策略2:网络状况判断
   if (navigator.connection.effectiveType === '4g') {
     prefetchApps(['app1', 'app2'])
   } else {
     // 弱网不预加载
   }

   策略3:分时预加载
   setTimeout(() => prefetchApps(['app1']), 3000)  // 3秒后
   setTimeout(() => prefetchApps(['app2']), 6000)  // 6秒后

4. Service Worker 缓存:

   // sw.js
   self.addEventListener('install', (event) => {
     event.waitUntil(
       caches.open('micro-app-cache').then((cache) => {
         return cache.addAll([
           'http://localhost:8081/main.js',
           'http://localhost:8081/vendor.js'
         ])
       })
     )
   })

   // 拦截请求
   self.addEventListener('fetch', (event) => {
     event.respondWith(
       caches.match(event.request).then((response) => {
         return response || fetch(event.request)
       })
     )
   })

5. HTTP/2 Server Push:

   // Nginx 配置
   location / {
     http2_push /app1/main.js;
     http2_push /app1/vendor.js;
   }

---

三、灰度发布方案

问题:
- 新版本需要灰度测试
- 出问题需要快速回滚
- 不同用户看到不同版本

方案设计:

1. 基于用户的灰度(推荐):

   架构:
   ┌─────────────┐
   │  用户访问    │
   └──────┬──────┘
          ↓
   ┌─────────────┐
   │ 主应用       │
   │ ├─ 灰度规则  │  ← 从配置中心获取
   │ └─ 动态路由  │
   └──────┬──────┘
          ↓
     根据规则加载不同版本
          ↓
   ┌──────────────────┐
   │ 10% 用户 → V2    │  新版本
   │ 90% 用户 → V1    │  老版本
   └──────────────────┘

   实现:
   // 主应用
   const getAppEntry = (appName) => {
     const userId = getUserId()
     const grayConfig = await fetch('/api/gray-config').then(r => r.json())

     // 判断用户是否在灰度名单
     if (isInGrayList(userId, grayConfig)) {
       return `https://app1-v2.example.com`  // 新版本
     } else {
       return `https://app1-v1.example.com`  // 老版本
     }
   }

   registerMicroApps([{
     name: 'app1',
     entry: await getAppEntry('app1'),
     container: '#container'
   }])

   灰度规则(配置中心):
   {
     "app1": {
       "version": "v2",
       "grayType": "user",
       "grayList": ["user_1", "user_2"],  // 白名单
       "grayPercent": 10  // 10% 流量
     }
   }

2. 基于流量百分比:

   // 主应用
   const getAppEntry = (appName) => {
     const random = Math.random() * 100
     const grayPercent = getGrayPercent(appName)  // 从配置中心获取

     if (random < grayPercent) {
       return `https://app1-v2.example.com`  // 10% 流量
     } else {
       return `https://app1-v1.example.com`  // 90% 流量
     }
   }

3. 基于特征的灰度(AB测试):

   const getAppEntry = (appName) => {
     const user = getUserInfo()

     // 灰度规则
     const rules = {
       // VIP 用户优先体验新版本
       vip: user.vip && random() < 0.5,
       // 特定城市灰度
       city: user.city === '北京',
       // 特定浏览器灰度
       browser: /Chrome/.test(navigator.userAgent)
     }

     if (Object.values(rules).some(v => v)) {
       return `https://app1-v2.example.com`
     }
     return `https://app1-v1.example.com`
   }

4. Nginx 层灰度(运维方案):

   # nginx.conf
   upstream app1_v1 {
     server app1-v1.example.com;
   }

   upstream app1_v2 {
     server app1-v2.example.com;
   }

   # 根据 cookie 分流
   map $cookie_version $app1_backend {
     "v2" app1_v2;
     default app1_v1;
   }

   location /app1/ {
     proxy_pass http://$app1_backend;
   }

5. CDN 灰度(阿里云 CDN):

   - 配置 CDN 灰度规则
   - 根据 IP、地域、比例分流
   - 无需修改代码

---

四、回滚方案

问题:
- 新版本有 Bug 需要快速回滚
- 不能影响用户体验

方案设计:

1. 配置中心秒级回滚(推荐):

   架构:
   ┌──────────────┐
   │ 配置中心      │  ← 修改配置
   │ app1: v2 → v1 │
   └───────┬──────┘
           ↓ 实时推送(WebSocket)
   ┌──────────────┐
   │ 主应用        │
   │ ├─ 监听配置   │
   │ └─ 动态切换   │
   └───────┬──────┘
           ↓
   重新加载子应用(使用 v1)

   实现:
   // 主应用
   let currentVersion = {}

   // 监听配置变化(WebSocket)
   ws.on('config-change', (config) => {
     const { app, version } = config

     if (currentVersion[app] !== version) {
       // 卸载旧版本
       unmountMicroApp(app)

       // 加载新版本
       const entry = `https://${app}-${version}.example.com`
       registerMicroApps([{
         name: app,
         entry,
         container: '#container'
       }])

       // 刷新当前页面
       if (isAppActive(app)) {
         location.reload()
       }

       currentVersion[app] = version
     }
   })

   // 配置中心(管理后台)
   回滚操作:
   1. 点击"回滚"按钮
   2. 配置中心修改配置:app1.version = "v1"
   3. WebSocket 推送到所有主应用
   4. 主应用自动切换到 v1

   耗时:< 5秒

2. CDN 回滚:

   方案1:切换CDN源
   // 修改 CDN 回源地址
   app1.example.com → app1-v1.example.com

   方案2:修改 DNS
   app1.example.com A 记录
   1.2.3.4 (v2) → 5.6.7.8 (v1)

   缺点:DNS 生效时间长(TTL)

3. 版本管理策略:

   策略1:保留历史版本(推荐)
   ├── app1-v1.0.0/
   ├── app1-v1.0.1/
   ├── app1-v1.1.0/  ← 当前版本
   └── app1-v1.2.0/  ← 灰度版本

   回滚:修改配置指向 v1.1.0

   策略2:蓝绿部署
   ├── app1-blue/  ← 当前生产版本
   └── app1-green/ ← 新版本

   发布:切换流量 blue → green
   回滚:切换流量 green → blue

   策略3:金丝雀发布
   10% 流量 → v2
   90% 流量 → v1

   如果 v2 有问题,停止灰度,100% 流量 → v1

4. 应用级回滚:

   // 主应用提供降级UI
   const FallbackApp = () => (
     <div>
       <h1>子应用加载失败</h1>
       <Button onClick={() => loadBackupVersion()}>
         加载备用版本
       </Button>
     </div>
   )

   registerMicroApps([{
     name: 'app1',
     entry: 'https://app1-v2.example.com',
     loader: (loading) => {
       if (error) {
         // 自动降级到 v1
         return loadApp('https://app1-v1.example.com')
       }
     }
   }])

5. 监控与自动回滚:

   // 监控错误率
   const errorRate = getErrorRate('app1')

   if (errorRate > 5%) {  // 错误率超过5%
     // 自动回滚
     rollback('app1', 'v1')

     // 告警通知
     notify('app1 错误率过高,已自动回滚')
   }

---

最佳实践总结:

资源加载:
✅ 使用 HTML Entry(qiankun)
✅ 配置 publicPath 避免 404
✅ 使用 CDN 加速
✅ 启用 HTTP/2

预加载:
✅ qiankun prefetch 开启
✅ 核心应用优先预加载
✅ 使用 Service Worker 缓存
✅ 弱网环境不预加载

灰度发布:
✅ 配置中心管理灰度规则
✅ 支持用户维度 + 流量百分比
✅ 小流量开始(1% → 10% → 50% → 100%)
✅ 实时监控灰度效果

回滚:
✅ 配置中心秒级回滚
✅ 保留历史版本
✅ 监控自动回滚
✅ 降级预案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476

4. 子应用性能治理

如果子应用性能差拖垮主应用,你会怎么隔离与治理?如何监控、降级、容错?

【作答】:

一、性能隔离方案

问题:
- 子应用 JS 执行慢,阻塞主应用
- 子应用内存泄漏
- 子应用频繁触发重渲染

隔离策略:

1. 时间片隔离(防止长任务阻塞):

   // 主应用:限制子应用 JS 执行时间
   const executeWithTimeLimit = (fn, timeout = 50) => {
     return new Promise((resolve, reject) => {
       const timer = setTimeout(() => {
         reject(new Error('子应用执行超时'))
       }, timeout)

       try {
         const result = fn()
         clearTimeout(timer)
         resolve(result)
       } catch (e) {
         clearTimeout(timer)
         reject(e)
       }
     })
   }

   // 监控子应用挂载时间
   const startTime = performance.now()
   await mount()
   const mountTime = performance.now() - startTime

   if (mountTime > 3000) {  // 超过3秒
     reportPerformance({
       app: 'app1',
       type: 'slow_mount',
       time: mountTime
     })
   }

2. CPU 隔离(Web Worker):

   // 将子应用耗时计算放到 Worker
   // main.js
   const worker = new Worker('heavy-compute.worker.js')

   worker.postMessage({ data: largeData })
   worker.onmessage = (e) => {
     console.log('计算结果:', e.data)
   }

   // heavy-compute.worker.js
   self.onmessage = (e) => {
     const result = expensiveCompute(e.data)
     self.postMessage(result)
   }

3. 内存隔离(iframe):

   // 对于性能特别差的子应用,降级使用 iframe
   const loadAppInIframe = (app) => {
     const iframe = document.createElement('iframe')
     iframe.src = app.entry
     iframe.style.width = '100%'
     iframe.style.height = '100%'
     document.getElementById('container').appendChild(iframe)
   }

   // 卸载时销毁 iframe(释放内存)
   const destroyIframe = () => {
     iframe.remove()
     iframe = null
   }

4. 资源隔离(限流):

   // 限制子应用同时请求数
   class RequestLimiter {
     constructor(maxConcurrent = 6) {
       this.maxConcurrent = maxConcurrent
       this.currentCount = 0
       this.queue = []
     }

     async request(url) {
       if (this.currentCount >= this.maxConcurrent) {
         await new Promise(resolve => this.queue.push(resolve))
       }

       this.currentCount++
       try {
         const response = await fetch(url)
         return response
       } finally {
         this.currentCount--
         if (this.queue.length > 0) {
           this.queue.shift()()
         }
       }
     }
   }

   const limiter = new RequestLimiter(3)  // 限制3个并发

---

二、性能监控方案

1. 加载性能监控:

   // qiankun 生命周期监控
   registerMicroApps([{
     name: 'app1',
     entry: '//localhost:8081',
     loader: (loading) => {
       if (loading) {
         window.__APP_LOAD_START__ = performance.now()
       } else {
         const loadTime = performance.now() - window.__APP_LOAD_START__

         // 上报加载时间
         reportMetric({
           app: 'app1',
           metric: 'load_time',
           value: loadTime,
           level: loadTime > 3000 ? 'error' : 'info'
         })
       }
     }
   }])

2. 运行时性能监控:

   // FPS 监控
   class FPSMonitor {
     constructor() {
       this.fps = 0
       this.lastTime = performance.now()
       this.frames = 0
     }

     tick() {
       this.frames++
       const now = performance.now()

       if (now >= this.lastTime + 1000) {
         this.fps = Math.round((this.frames * 1000) / (now - this.lastTime))
         this.frames = 0
         this.lastTime = now

         // FPS 过低告警
         if (this.fps < 30) {
           reportPerformance({
             type: 'low_fps',
             fps: this.fps,
             app: getCurrentApp()
           })
         }
       }

       requestAnimationFrame(() => this.tick())
     }
   }

   const fpsMonitor = new FPSMonitor()
   fpsMonitor.tick()

3. 内存监控:

   // 监控子应用内存占用
   const monitorMemory = () => {
     if (performance.memory) {
       const { usedJSHeapSize, totalJSHeapSize } = performance.memory
       const usage = (usedJSHeapSize / totalJSHeapSize * 100).toFixed(2)

       // 内存占用过高告警
       if (usage > 90) {
         reportPerformance({
           type: 'high_memory',
           usage,
           app: getCurrentApp()
         })
       }
     }
   }

   setInterval(monitorMemory, 10000)  // 每10秒检测

4. 长任务监控:

   // PerformanceObserver 监控长任务
   const observer = new PerformanceObserver((list) => {
     for (const entry of list.getEntries()) {
       // 超过50ms的任务被认为是长任务
       if (entry.duration > 50) {
         reportPerformance({
           type: 'long_task',
           duration: entry.duration,
           app: getCurrentApp(),
           startTime: entry.startTime
         })
       }
     }
   })

   observer.observe({ entryTypes: ['longtask'] })

5. 接口性能监控:

   // 拦截子应用请求,监控接口性能
   const originalFetch = window.fetch
   window.fetch = async (...args) => {
     const startTime = performance.now()
     const url = args[0]

     try {
       const response = await originalFetch(...args)
       const duration = performance.now() - startTime

       // 慢接口告警(> 3秒)
       if (duration > 3000) {
         reportPerformance({
           type: 'slow_api',
           url,
           duration,
           app: getCurrentApp()
         })
       }

       return response
     } catch (error) {
       reportPerformance({
         type: 'api_error',
         url,
         error: error.message,
         app: getCurrentApp()
       })
       throw error
     }
   }

6. 用户体验监控(Web Vitals):

   import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'

   // 监控核心性能指标
   getCLS(metric => reportMetric('CLS', metric.value))  // 累计布局偏移
   getFID(metric => reportMetric('FID', metric.value))  // 首次输入延迟
   getFCP(metric => reportMetric('FCP', metric.value))  // 首次内容绘制
   getLCP(metric => reportMetric('LCP', metric.value))  // 最大内容绘制
   getTTFB(metric => reportMetric('TTFB', metric.value)) // 首字节时间

---

三、降级方案

1. 超时降级:

   // 子应用加载超时,显示降级UI
   const loadAppWithTimeout = async (app, timeout = 10000) => {
     return Promise.race([
       loadMicroApp(app),
       new Promise((_, reject) => {
         setTimeout(() => reject(new Error('加载超时')), timeout)
       })
     ]).catch((error) => {
       // 降级:显示静态页面
       showFallbackUI(app.name)

       reportError({
         app: app.name,
         type: 'load_timeout',
         error
       })
     })
   }

   const showFallbackUI = (appName) => {
     const container = document.getElementById('container')
     container.innerHTML = `
       <div class="fallback">
         <h2>${appName} 加载失败</h2>
         <button onclick="retryLoad()">重试</button>
         <button onclick="useBasicVersion()">使用基础版本</button>
       </div>
     `
   }

2. 错误降级:

   // 子应用运行时错误,降级处理
   addGlobalUncaughtErrorHandler((event, app) => {
     const errorCount = getErrorCount(app.name)

     if (errorCount > 3) {  // 错误次数超过3次
       // 降级:卸载子应用
       unmountMicroApp(app.name)

       // 显示降级UI
       showDegradedUI(app.name)

       // 告警
       alertDevelopers({
         app: app.name,
         reason: '错误率过高,已自动降级'
       })
     }
   })

3. 性能降级:

   // 根据性能指标动态降级
   const performanceGrade = getPerformanceGrade()

   if (performanceGrade === 'poor') {
     // 降级策略
     - 禁用动画
     - 减少轮询频率
     - 延迟加载非核心模块
     - 降低渲染精度

     registerMicroApps([{
       name: 'app1',
       props: {
         enableAnimation: false,  // 禁用动画
         pollingInterval: 10000,  // 轮询间隔延长
         lazyLoad: true           // 懒加载
       }
     }])
   }

4. 设备降级:

   // 低端设备降级
   const isLowEndDevice = () => {
     // 内存小于4GB
     const memory = navigator.deviceMemory
     if (memory && memory < 4) return true

     // CPU核心数少于4
     const cores = navigator.hardwareConcurrency
     if (cores && cores < 4) return true

     return false
   }

   if (isLowEndDevice()) {
     // 降级:使用轻量版本
     registerMicroApps([{
       name: 'app1',
       entry: 'https://app1-lite.example.com'  // 轻量版
     }])
   }

---

四、容错方案

1. 错误边界(React):

   // 主应用错误边界
   class MicroAppErrorBoundary extends React.Component {
     constructor(props) {
       super(props)
       this.state = { hasError: false, error: null }
     }

     static getDerivedStateFromError(error) {
       return { hasError: true, error }
     }

     componentDidCatch(error, errorInfo) {
       // 上报错误
       reportError({
         app: this.props.appName,
         error: error.toString(),
         errorInfo
       })

       // 尝试重新加载
       this.retryCount = (this.retryCount || 0) + 1
       if (this.retryCount < 3) {
         setTimeout(() => {
           this.setState({ hasError: false })
           this.forceUpdate()
         }, 2000)
       }
     }

     render() {
       if (this.state.hasError) {
         return (
           <div>
             <h2>子应用出错了</h2>
             <button onClick={() => location.reload()}>刷新页面</button>
           </div>
         )
       }
       return this.props.children
     }
   }

2. 资源加载容错:

   // 子应用 JS 加载失败,尝试备用CDN
   const loadScriptWithFallback = (src, fallbackSrc) => {
     return new Promise((resolve, reject) => {
       const script = document.createElement('script')
       script.src = src

       script.onload = resolve
       script.onerror = () => {
         console.log('主CDN加载失败,尝试备用CDN')

         // 移除失败的script
         script.remove()

         // 尝试备用CDN
         const fallbackScript = document.createElement('script')
         fallbackScript.src = fallbackSrc
         fallbackScript.onload = resolve
         fallbackScript.onerror = reject
         document.head.appendChild(fallbackScript)
       }

       document.head.appendChild(script)
     })
   }

   // 使用
   loadScriptWithFallback(
     'https://cdn1.example.com/app1.js',
     'https://cdn2.example.com/app1.js'  // 备用CDN
   )

3. 接口容错:

   // 子应用接口失败,重试机制
   const fetchWithRetry = async (url, options = {}, retries = 3) => {
     for (let i = 0; i < retries; i++) {
       try {
         const response = await fetch(url, options)
         if (response.ok) {
           return response
         }
       } catch (error) {
         if (i === retries - 1) throw error

         // 指数退避
         await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000))
       }
     }
   }

4. 状态容错:

   // 子应用状态异常,自动恢复
   const recoverState = (app) => {
     try {
       // 尝试从 localStorage 恢复状态
       const savedState = localStorage.getItem(`${app}_state`)
       if (savedState) {
         return JSON.parse(savedState)
       }
     } catch (error) {
       // 恢复失败,使用默认状态
       return getDefaultState()
     }
   }

5. 自动重启:

   // 子应用崩溃自动重启
   let crashCount = 0
   const MAX_CRASH_COUNT = 3

   addGlobalUncaughtErrorHandler((event, app) => {
     crashCount++

     if (crashCount < MAX_CRASH_COUNT) {
       // 自动重启
       setTimeout(() => {
         unmountMicroApp(app.name)
         loadMicroApp({
           name: app.name,
           entry: app.entry,
           container: app.container
         })

         console.log(`子应用 ${app.name} 已自动重启`)
       }, 1000)
     } else {
       // 重启次数过多,放弃
       showFatalError(app.name)
     }
   })

---

五、性能优化建议

1. 子应用优化:

   ✅ 代码分割(React.lazy、Vue异步组件)
   ✅ 按需加载(路由懒加载)
   ✅ Tree Shaking(移除未使用代码)
   ✅ 压缩打包(Gzip、Brotli)
   ✅ CDN 加速
   ✅ 图片懒加载、压缩
   ✅ 减少依赖体积(使用 lodash-es、day.js)
   ✅ Service Worker 缓存

2. 主应用优化:

   ✅ 预加载核心子应用
   ✅ 虚拟滚动(大列表)
   ✅ 防抖节流(高频事件)
   ✅ Web Worker(耗时计算)
   ✅ CSS 隔离(避免样式冲突)

3. 通信优化:

   ✅ 减少跨应用通信频率
   ✅ 批量更新(合并多次setState)
   ✅ 使用 throttle/debounce

---

完整的性能治理体系:

┌─────────────────────────────────┐
│        监控层(Monitoring)       │
│  ├─ 加载性能(Load Time)         │
│  ├─ 运行时性能(FPS、Memory)     │
│  ├─ 接口性能(API Time)          │
│  └─ 用户体验(Web Vitals)        │
└────────────┬────────────────────┘
             ↓ 发现问题
┌─────────────────────────────────┐
│        告警层(Alerting)         │
│  ├─ 性能告警(超时、卡顿)         │
│  ├─ 错误告警(崩溃、404)          │
│  └─ 自动通知(钉钉、邮件)         │
└────────────┬────────────────────┘
             ↓ 触发策略
┌─────────────────────────────────┐
│        降级层(Degradation)      │
│  ├─ 超时降级(静态页面)           │
│  ├─ 错误降级(基础版本)           │
│  ├─ 性能降级(减少动画)           │
│  └─ 设备降级(轻量版)             │
└────────────┬────────────────────┘
             ↓ 保障可用性
┌─────────────────────────────────┐
│        容错层(Fault Tolerance)  │
│  ├─ 错误边界(Error Boundary)    │
│  ├─ 自动重试(Retry)             │
│  ├─ 备用方案(Fallback)          │
│  └─ 自动恢复(Recovery)          │
└─────────────────────────────────┘

最终目标:
✅ 子应用问题不影响主应用
✅ 用户无感知降级
✅ 问题自动发现、自动降级、自动恢复
✅ 开发者及时收到告警
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568

上次更新:
(adsbygoogle = window.adsbygoogle || []).push({});