acongm

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

笔试题(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万+ 行代码才考虑
- 独立部署:如果不需要独立部署,不要用微前端

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

面试题(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、代码分割),问题严重时再引入微前端。

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)

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%)
✅ 实时监控灰度效果

回滚:
✅ 配置中心秒级回滚
✅ 保留历史版本
✅ 监控自动回滚
✅ 降级预案

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)          │
└─────────────────────────────────┘

最终目标:
✅ 子应用问题不影响主应用
✅ 用户无感知降级
✅ 问题自动发现、自动降级、自动恢复
✅ 开发者及时收到告警

On this page