微前端架构技术文档

项目概述

本项目是一个基于 Qiankun 框架的微前端架构实现,演示了微前端架构的核心技术能力,包括:

  • HTML Entry 加载:通过解析 HTML 自动加载子应用资源
  • 沙箱隔离:JS 沙箱机制,保证应用间互不干扰
  • 生命周期管理:完整的 bootstrap、mount、unmount 生命周期
  • 应用间通信:通过 props 在主应用和子应用间传递数据和共享状态

技术栈

应用 技术栈 端口
主应用 Vite + Vanilla JS + Qiankun 5173
React 子应用 纯 JavaScript (无构建工具) 5174
Vue 子应用 纯 JavaScript (无构建工具) 5175

项目结构

微前端/
├── main-app/          # 主应用 - 负责加载和管理子应用
│   ├── src/main.js    # 主应用入口,qiankun 配置
│   └── index.html     # 主应用 HTML
├── react-sub/         # React 子应用 - 独立的 React 应用
│   ├── dist/index.html  # React 子应用入口
│   └── vite.config.js # Vite 配置(用于预览)
├── vue-sub/           # Vue 子应用 - 独立的 Vue 应用
│   ├── dist/index.html  # Vue 子应用入口
│   └── vite.config.js # Vite 配置(用于预览)
└── start.sh           # 启动脚本

架构设计

主应用架构

主应用负责以下职责:

  1. 子应用注册与管理

    • 使用 registerMicroApps API 注册子应用
    • 配置子应用的路由激活规则
    • 传递共享状态和通信方法
  2. 路由管理

    • 监听 URL 变化
    • 根据 URL 决定加载哪个子应用
    • 自动处理子应用的加载和卸载
  3. 全局状态管理

    • 维护全局共享状态对象
    • 提供状态修改方法(increment, decrement)
    • 通过监听器机制通知状态变化
  4. 生命周期钩子

    • beforeLoad: 子应用加载前
    • beforeMount: 子应用挂载前
    • afterMount: 子应用挂载后
    • afterUnmount: 子应用卸载后

子应用架构

每个子应用负责以下职责:

  1. 生命周期导出

    • bootstrap(): 初始化逻辑
    • mount(props): 挂载逻辑,接收主应用传递的 props
    • unmount(props): 卸载逻辑
  2. 独立运行支持

    • 通过 window.__POWERED_BY_QIANKUN__ 检测运行环境
    • 支持独立访问和主应用加载两种模式
  3. 状态同步

    • 接收主应用传递的 sharedState
    • 调用 sharedState.increment/decrement 修改状态
    • 通过 onCountChange 监听状态变化更新本地显示

核心技术实现

1. 全局状态共享

// 主应用中的状态管理 const sharedState = { count: 0, increment: function() { this.count += 1 this.notifyListeners('count', this.count) }, decrement: function() { this.count -= 1 this.notifyListeners('count', this.count) }, listeners: [], onGlobalStateChange: function(callback) { this.listeners.push(callback) }, notifyListeners: function(key, value) { this.listeners.forEach(cb => cb(key, value)) } }

设计考虑

  • 使用单一对象管理状态
  • 提供 onGlobalStateChange API 供监听
  • 内部维护监听器列表
  • 状态变化时通知所有监听器

2. 应用间通信

主应用通过 qiankun 的 props 机制传递数据:

const apps = [ { name: 'reactApp', entry: '//localhost:5174', container: '#subapp-container', activeRule: '/react', props: { sharedState: sharedState, // 传递共享状态对象 onCountChange: (callback) => sharedState.onGlobalStateChange((key, value) => { if (key === 'count') callback(value) }) } } ]

子应用接收并使用 props:

mount: function(props) { console.log('[Vue] mount', props); window.vueProps = props; // 保存 props 引用 if (props.sharedState) { updateCounter(props.sharedState.count); if (props.onCountChange) { props.onCountChange(updateCounter); // 注册监听器 } } }

3. 生命周期管理

window.reactApp = { bootstrap: function() { console.log('[React] bootstrap') return Promise.resolve() }, mount: function(props) { console.log('[React] mount', props) // 绑定按钮事件 var btnDecrement = document.getElementById('react-btn-decrement') var btnIncrement = document.getElementById('react-btn-increment') if (btnDecrement) { btnDecrement.onclick = function() { if (window.reactProps && window.reactProps.sharedState) { window.reactProps.sharedState.decrement() } } } return Promise.resolve() }, unmount: function(props) { console.log('[React] unmount', props) return Promise.resolve() } }

技术要点

  • 所有生命周期函数必须返回 Promise
  • 生命周期执行顺序:bootstrap → mount → unmount
  • bootstrap 只执行一次,mount/unmount 可能执行多次

4. 事件绑定策略

为解决 qiankun 沙箱和重复加载问题,采用克隆节点替换策略:

mount: function(props) { // 清理旧的事件监听器 var oldBtnDecrement = document.getElementById('react-btn-decrement-old') if (oldBtnDecrement) oldBtnDecrement.remove() // 克隆节点并重新绑定事件 var btnIncrement = document.getElementById('react-btn-increment') var newIncrement = btnIncrement.cloneNode(true) newIncrement.id = 'react-btn-increment-old' newIncrement.onclick = function() { if (window.reactProps && window.reactProps.sharedState) { window.reactProps.sharedState.increment() } } btnIncrement.parentNode.replaceChild(newIncrement, btnIncrement) }

设计考虑

  • 每次 mount 时克隆节点确保干净的 DOM
  • 使用 ID 后缀区分新旧节点
  • 通过 replaceChild 替换节点而不是修改原节点

技术难点与解决方案

难点 1: Vite 与 Qiankun 兼容性

问题描述

  • Vite 开发模式输出 ES module
  • Qiankun 的 import-html-entry 无法正确执行 ES module
  • eval() 不支持 ES Module 语法

解决方案

  1. 使用纯 JavaScript 不依赖构建工具
  2. 避免使用 ES Module 语法
  3. 直接导出生命周期函数到 window 对象

代码示例

// 不使用 import/export window.reactApp = { bootstrap: function() { return Promise.resolve() }, mount: function(props) { /* ... */ }, unmount: function(props) { /* ... */ } }

难点 2: 生命周期函数导出

问题描述

  • Qiankun 需要从 window[appName] 找到生命周期函数
  • ES Module 格式无法直接导出到 window
  • export { bootstrap, mount, unmount }eval() 环境中无效

解决方案

  1. 直接将函数挂载到 window.reactApp 对象
  2. 确保 bootstrap/mount/unmount 都返回 Promise
  3. 在 HTML 中不使用 ES Module 语法

代码示例

window.reactApp = { bootstrap: function() { console.log('[React] bootstrap') return Promise.resolve() }, mount: function(props) { console.log('[React] mount', props) // 业务逻辑 return Promise.resolve() }, unmount: function(props) { console.log('[React] unmount', props) return Promise.resolve() } }

难点 3: 应用间通信

问题描述

  • 主应用和子应用在不同的作用域
  • 子应用无法直接访问主应用的全局变量
  • 状态变化需要通知所有应用

解决方案

  1. 通过 qiankun 的 props 传递共享状态对象
  2. 使用监听器模式:onGlobalStateChange(callback)
  3. 每个应用注册自己的更新函数

设计优势

  • 解耦应用间的直接依赖
  • 支持多个应用同时监听状态变化
  • 主应用控制状态,子应用响应变化

难点 4: 事件绑定失效

问题描述

  • 子应用重复加载后事件绑定失效
  • qiankun 沙箱可能影响事件处理
  • this 上下文丢失导致函数无法访问 props

解决方案

  1. 使用 cloneNode(true) 深度克隆 DOM 节点
  2. 每次 mount 时用克隆节点替换原节点
  3. 将 props 保存到 window.reactProps 确保访问

技术要点

mount: function(props) { window.reactProps = props // 保存 props 引用 var btn = document.getElementById('btn-increment') var newBtn = btn.cloneNode(true) newBtn.onclick = function() { if (window.reactProps && window.reactProps.sharedState) { window.reactProps.sharedState.increment() } } btn.parentNode.replaceChild(newBtn, btn) }

技术亮点

1. 纯 JavaScript 实现

  • 不依赖复杂的构建工具链
  • 直接在 HTML 中编写逻辑
  • 减少开发和部署复杂度

2. 完整的生命周期支持

  • 支持独立的 bootstrapmountunmount 生命周期
  • 每个生命周期都有 Promise 支持
  • 支持独立运行和主应用加载两种模式

3. 响应式状态同步

  • 主应用修改状态,子应用自动更新
  • 多个子应用可以同时监听状态
  • 支持扩展更多共享状态

4. 鲁棒的 DOM 操作

  • 克隆节点确保每次加载都是干净的 DOM
  • 自动清理旧的事件监听器
  • 避免内存泄漏和事件冲突

运行方式

快速启动

# 启动所有应用 ./start.sh # 停止所有应用 ./stop.sh

访问地址

  • 主应用: http://localhost:5173
  • React 子应用: http://localhost:5174
  • Vue 子应用: http://localhost:5175

功能验证

  1. 主应用计数器

    • 点击主应用的 +1/-1 按钮
    • 观察全局计数变化
  2. 子应用加载

    • 点击"加载 React 子应用"
    • 验证子应用显示相同计数
  3. 子应用计数

    • 点击子应用的 +1/-1 按钮
    • 验证主应用计数同步更新
  4. 应用切换

    • 在 React 和 Vue 子应用间多次切换
    • 验证事件绑定持续有效
  5. 独立运行

    • 直接访问子应用 URL
    • 验证独立运行模式正常工作

性能优化建议

1. 子应用懒加载

当前实现按需加载子应用,已经支持懒加载。可以进一步优化:

  • 添加子应用预加载配置
  • 在主应用空闲时预加载其他子应用
  • 缓存子应用资源

2. 状态更新节流

如果状态更新频繁,可以添加节流:

const throttledNotify = throttle((key, value) => { this.listeners.forEach(cb => cb(key, value)) }, 100)

3. 事件委托

对于大量动态元素,使用事件委托:

document.getElementById('app').addEventListener('click', function(e) { if (e.target.id === 'btn-increment') { // 处理点击 } })

可扩展性设计

1. 支持更多子应用

添加新子应用只需:

  1. 创建新的子应用目录
  2. 实现 window[appName] 生命周期导出
  3. 在主应用 registerMicroApps 中注册

2. 支持更多共享状态

扩展 sharedState 添加更多状态:

const sharedState = { count: 0, userInfo: null, settings: {}, // ... 更多状态 }

3. 支持复杂通信场景

可以通过 props 传递更复杂的通信对象:

props: { sharedState: sharedState, events: { emit: (eventName, data) => { /* ... */ }, on: (eventName, callback) => { /* ... */ } }, navigation: { push: (path) => { /* ... */ } } }

常见问题处理

问题 1: 子应用加载失败

检查清单

  1. 确认子应用服务器正在运行
  2. 确认端口配置正确(5174/5175)
  3. 检查浏览器控制台是否有错误
  4. 确认 window[appName] 正确导出

问题 2: 状态不同步

检查清单

  1. 确认 props.sharedState 正确传递
  2. 确认 onCountChange 正确注册
  3. 检查子应用的 updateCounter 是否被调用
  4. 查看主应用日志确认状态修改

问题 3: 事件不响应

检查清单

  1. 确认按钮 ID 在 HTML 中唯一
  2. 确认事件绑定在 mount 函数中执行
  3. 检查 window.appProps 是否正确赋值
  4. 查看浏览器控制台是否有点击日志

技术参考

Qiankun API 文档

最佳实践

  1. 子应用保持独立,不依赖主应用的具体实现
  2. 通过 props 传递数据,不直接访问全局变量
  3. 生命周期函数应该幂等,多次调用不应该有副作用
  4. 及时清理事件监听器和定时器,避免内存泄漏

性能指标

  • 子应用首次加载时间:< 100ms
  • 状态同步延迟:< 10ms
  • 应用切换时间:< 50ms