microapp 源码解析

457 阅读3分钟

@micro-zoe/micro-app 库的源码分析,主要围绕其核心功能、架构设计、关键模块实现展开。该库是一个轻量级微前端框架,旨在简化子应用的接入和管理。


一、核心功能与设计理念

  1. 核心功能:

    • 应用加载:动态加载子应用的 HTML、JS、CSS 资源。
    • 沙箱隔离:实现 JS 隔离(通过 Proxy)和样式隔离(通过 Shadow DOM 或 Scoped CSS)。
    • 路由管理:基于 URL 自动匹配子应用。
    • 通信机制:父子应用间通过 data 属性和事件通信。
    • 生命周期:提供 createdmountedunmount 等钩子。
  2. 设计理念:

    • 轻量化:无依赖,核心代码约 2000 行。
    • 侵入性低:子应用无需修改构建配置。
    • 类 Web Components:通过自定义标签 <micro-app> 加载子应用。

二、源码结构概览

src/
├── apis/                # 全局 API(如 start、prefetch)
├── sandbox/             # 沙箱实现(JS 隔离、样式隔离)
├── router/              # 路由处理逻辑
├── source/              # 资源加载与执行(HTML/JS/CSS)
├── interfaces/          # 通信机制(事件总线、数据传递)
├── scoped_css/          # 样式作用域处理
├── micro_app_element.ts # 自定义元素 `<micro-app>` 的实现
└── index.ts             # 入口文件

三、关键模块分析

1. 自定义元素 <micro-app>

  • 实现方式:基于 CustomElement 定义 Web Components。
  • 核心逻辑
    class MicroAppElement extends HTMLElement {
      connectedCallback() {
        // 创建微应用实例
        this.app = new MicroApp(this.name, this.getAttribute('url'), this)
        // 加载资源并渲染
        this.app.mount()
      }
    
      disconnectedCallback() {
        this.app.unmount() // 卸载应用,清理沙箱
      }
    }
    

2. 沙箱隔离

  • JS 沙箱

    • 通过 Proxy 代理全局对象(如 window),隔离全局变量。
    • 记录子应用对全局对象的修改,卸载时恢复。
    class ProxySandbox {
      constructor(name: string) {
        const rawWindow = window
        const fakeWindow = {}
        this.proxy = new Proxy(fakeWindow, {
          get(target, key) {
            return target[key] || rawWindow[key]
          },
          set(target, key, value) {
            target[key] = value // 修改仅作用于 fakeWindow
            return true
          }
        })
      }
    }
    
  • 样式隔离

    • Shadow DOM:默认将子应用包裹在 ShadowRoot 中,实现样式隔离。
    • Scoped CSS:通过添加 [micro-app-name] 属性选择器改写 CSS 规则。

3. 资源加载与执行

  • HTML 解析
    fetchSource(url).then(html => {
      const { scripts, styles, html: processedHtml } = extractScriptsAndStyles(html)
      this.processStyles(styles) // 插入样式
      this.processScripts(scripts) // 执行脚本
    })
    
  • 动态脚本执行
    • 通过 evalnew Function 执行 JS 代码,包裹在沙箱上下文中。
    • 支持模块化脚本(如 SystemJS)。

4. 通信机制

  • 基于 CustomEvent
    // 父应用发送数据
    microApp.setData('app-name', { data: 'value' })
    
    // 子应用监听
    window.addEventListener('micro-app-data', (e) => {
      console.log(e.detail.data)
    })
    
  • 数据序列化:通过 JSON.parse(JSON.stringify(data)) 深拷贝避免副作用。

四、路由管理

  • URL 监听:监听 popstatehashchange 事件。
  • 应用匹配
    function getActiveApps() {
      return apps.filter(app => {
        return isMatch(currentPath, app.activeRule) // 匹配路由规则
      })
    }
    

五、性能优化

  1. 资源缓存:通过 localStorageIndexedDB 缓存子应用资源。
  2. 预加载microApp.preload(['app-name']) 提前加载资源。
  3. 并行加载:使用 Promise.all 并行加载 JS 和 CSS。

六、错误处理

  • 全局错误捕获
    window.addEventListener('error', (e) => {
      if (e.target?.tagName === 'SCRIPT' && e.target.src.includes('micro-app')) {
        console.error('子应用脚本加载失败:', e.target.src)
      }
    })
    
  • 重试机制:资源加载失败时自动重试(默认 3 次)。

七、总结与评价

  • 优点
    • 轻量简洁,学习成本低。
    • 沙箱隔离实现较为彻底。
    • 对子应用侵入性极低。
  • 潜在问题
    • 样式隔离在非 Shadow DOM 模式下可能不彻底。
    • Proxy 兼容性(需注意 IE 支持)。
  • 适用场景:中小型项目快速接入微前端,无需复杂配置。

建议结合官方文档(micro-zoe.github.io/micro-app)进一步理解设计细节。