Contents

简单理解微前端

参考文章 手把手教你写一个简易的微前端框架-谭光志

参考仓库 mini-single-spa

本篇文章为学习微前端有感,只为记录微前端简短理解只实现了v1&v2版本,深度以及广度完全不及大佬文章

微前端是什么

微前端是一种软件架构模式,旨在将前端应用程序拆分为更小的、可独立开发、部署和维护的微服务单元。与传统的单体前端应用不同,微前端允许团队独立开发和部署其特定的前端功能模块,这些模块可以独立运行,也可以与其他模块协同工作,形成一个完整的前端应用

微前端应用运行原理

微前端分主应用和子应用,在主应用中来注册子应用,通过监听路由的变化来挂载或者卸载子应用,从这里可知,子应用都有其对应的生命周期

子应用注册

我们需要暴露出一个方法来使主应用注册子应用,并通过这个方法来接收到子应用的部分信息,如子应用名称,对应的路由地址,以及子应用生命周期函数等等

在微前端内部,通过一个数组来接收主应用注册的子应用,当子应用被注册时会进行应用状态的更新为启动前,并将注册的应用收集起来

/**
 * 该方法用来注册新的应用程序,并将应用程序的状态改为BEFORE_BOOTSTRAP(初始化状态)
 * @param app 所注册的应用
 */
export default function registerApplication(app: Application) {
    console.log('app registerApplication', app)
  
    if (typeof app.activeRule === 'string') {
        const path = app.activeRule
        /**
     * 并且判断activeRule是否为字符串,如果 app.activeRule 是一个字符串,
     * 代码将它赋值给一个新的函数 app.activeRule,这个函数会接受一个 location 参数
     * (默认为 window.location),并检查当前页面的路径是否与传入的路径匹配。
     * 这是为了将路径规则转换为一个可执行的条件函数。
     * @param location 地址
     * @returns
     */
        app.activeRule = (location = window.location) => location.pathname === path
    }

    app.status = AppStatus.BEFORE_BOOTSTRAP
    apps.push(app)
}

子应用加载

在子应用的加载中会根据获取到的应用的状态来进行加载

/ 已注册应用集合
export const apps: Application[] = []

export async function loadApps() {
    // 获取状态为MOUNTED的应用
    const toUnMountApp = getAppsWithStatus(AppStatus.MOUNTED)
    // 对状态为MOUNTED的应用调用unMountApp方法 进行应用程序卸载
    await Promise.all(toUnMountApp.map(unMountApp))
    // 获取状态为BEFORE_BOOTSTRAP的应用
    const toLoadApp = getAppsWithStatus(AppStatus.BEFORE_BOOTSTRAP)
    // 对状态为BEFORE_BOOTSTRAP的应用调用bootstrop函数 进行初始化
    await Promise.all(toLoadApp.map(bootstrapApp))

    const toMountApp = [
        ...getAppsWithStatus(AppStatus.BOOTSTRAPPED),
        ...getAppsWithStatus(AppStatus.UNMOUNTED),
    ]
    await toMountApp.map(mountApp)
}
/**
 * 这个函数用于获取具有特定状态的应用程序的列表。它被多次调用,
 * 传入不同的 AppStatus 枚举值作为参数,从而获取不同状态的应用程序列表。
 * 这可能是一个用于筛选应用程序的辅助函数。
 * @param status 应用状态
 * @returns
 */
function getAppsWithStatus(status: AppStatus) {
    // 收集筛选后的应用
    const result: Application[] = []
    apps.forEach((app) => {
    // tobootstrap or tomount
        if (isActive(app) && app.status === status) {
            switch (app.status) {
                case AppStatus.BEFORE_BOOTSTRAP:
                case AppStatus.BOOTSTRAPPED:
                case AppStatus.UNMOUNTED:
                    result.push(app)
                    break
            }
        } else if (
            app.status === AppStatus.MOUNTED
      && status === AppStatus.MOUNTED
        ) {
            // tounmount
            result.push(app)
        }
    })

    return result
}

function isActive(app: Application) {
    return typeof app.activeRule === 'function' && app.activeRule(window.location)
}

路由监听

在路由监听这一层中,主要是对pushState,replaceState两个方法的重写,对这两个方法添加额外的行为,来进行子应用的加载或卸载,

const originPushState = window.history.pushState
const originReplaceState = window.history.replaceState

export default function overwriteEventAndHistory() {
  window.history.pushState = function (
    data: any,
    unused: string,
    url?: string | URL | null | undefined
  ): void {
    const result = originPushState.call(this, data, unused, url)
    loadApps() // 加载或卸载应用
    return result
  }
  window.history.replaceState = function (
    data: any,
    unused: string,
    url?: string | URL | null | undefined
  ) {
    const result = originReplaceState.call(this, data, unused, url)
    loadApps() // 加载或卸载应用
    return result
  }
  window.addEventListener('popstate', () => {
    console.log('popstate')
  })
  window.addEventListener('hashChange', () => {
    console.log('hashChange')
  })
}

生命周期

子应用的生命周期分为bootstrap,mount,unmount三个部分,启动,挂载和卸载

bootstrap

bootstrap是用于启动应用程序的核心函数,负责应用程序的初始化和启动过程。

在此会先从对应的微前端应用中加载其声明的生命周期函数和属性,并对这些核心的声明周期函数进行校验,校验通过后将加载的生命周期函数赋值给对应的属性

接下来初始化应用的props,并执行应用程序的bootstrap函数,并处理结果

执行后修改应用的状态

mount

在mount执行时首先会将应用程序的状态改为挂载前,接下来调用应用的mount函数执行生命周期内的函数

执行修改应用的状态为挂载中

unmount

将应用程序的状态改为卸载前,执行函数的卸载操作,执行完后将应用程序状态改为已卸载

简单注册使用

 registerApplication({
      name: 'vue',
      actionRule: '/vue',
      loadApp() {
        return Promise.resolve({
          bootstrap() {
            console.log('vue bootstrap');
          },
          mount() {
            console.log('vue mount');
          },
          unmount() {
            console.log('vue unmount');
          }
        })
      }
    })
    registerApplication({
      name: 'react',
      actionRule: '/react',
      loadApp() {
        return Promise.resolve({
          bootstrap() {
            console.log('react bootstrap');
          },
          mount() {
            console.log('react mount');
          },
          unmount() {
            console.log('react unmount');
          }
        })
      }
    })