通用中心路由基座式微前端实现

什么是微前端

微前端架构具备以下几个核心价值

  • 技术栈无关:主框架不限制接入应用的技术栈,子应用具备完全自主权
  • 独立开发、独立部署:子应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  • 独立运行时:每个子应用之间状态隔离,运行时状态不共享

现有解决方案

  • 路由分发(nginx)
  • npm 子包:基座负责打包构建发布,打包时集成;
  • iframe:应用之间完全独立;需要基座提供通信方案;
  • 通用中心路由基座式:完全独立;需要基座提供通信方案;使用 DOM 实现;(阿里乾坤)
  • 特定中心路由基座式:相同技术栈;复用基座公共基建内容;(美团广告业务)

方案对比

方案 技术栈是否能统一 单独打包 单独部署 打包部署速度 单页应用体验 子工程切换速度 工程间通信难度 现有工程侵入性 学习成本
NPM 式 是(不强制) 正常
iframe 式 是(不强制) 正常
通用中心路由基座式 是(不强制) 正常
特定中心路由基座式 是(强制) 正常

通用中心路由基座式实现

应用架构

graph TB
App[App]
App --> App1[App1]
App --> App2[App2]
App --> App3[App3]
App --> App4[...]

实现方案

graph LR
Start(Start)
Start --> Route(/subapp/xxx/index)
Route --> Match(根路由匹配到 /subapp)
Match --> DoubleMatch{二级路由xxx匹配到子路由注册信息}
DoubleMatch --> | 匹配失败 | MatchError(失败处理) --> End(End)
DoubleMatch --> | 匹配成功 | IsLoaded{检查是否已经加载}
IsLoaded --> | 是 | Loaded(直接获取模块) --> End
IsLoaded --> | 否 | LoadJs{异步获取JS}
LoadJs --> | 失败 | MatchJsError(失败处理) --> End
LoadJs --> | 成功 | MatchSuccess(获取子工程模块) --> LoadPage(显示子工程页面) --> End

核心流程

1、注册子应用

graph LR
Start(Start) --> GetList[拿到子应用列表] --> Init[将子应用初始状态设置为 NOT_LOADED] --> GlobalHook(设置全局生命周期) --> End(End)

2、运行

graph LR
Start(Start) --> Hijack[Hijack Route] --> Reroute[ReRoute] --> LoadOther(对其他子应用进行资源预加载) --> End(End)

3、页面路由切换

graph LR
Start(Start) --> ReRoute --> End(end)

核心代码逻辑

1、Hijack Route

  1. 设置全局变量 HistoryEvent
  2. 重写 pushState 方法,每次调用之后生成一个 PopStateEvent,赋值给 HistoryEvent
  3. 重写 replaceState 方法,每次调用之后生成一个 PopStateEvent ,赋值给 HistoryEvent
  4. 监听 hashChange ,调用 reroute
  5. 监听 popstate,调用reroute
  6. addEventListenerremoveEventListener注入,对hashChangepopstate进行拦截

2、ReRoute

  • actives:所有处于 active 状态的子应用列表
  • unmounted:所有处于 unmount 状态的子应用列表
graph LR
Start(Start) --> IfUrl{url !== lastUrl}
IfUrl --> | false | End(End)
IfUrl --> | true | AppList[拿到app的状态列表]
AppList --> | unmount | UnMount --> Finish[Finish]
AppList --> | active | BeforeLoad --> Assets --> Mounted --> Finish
Finish --> OpenListener[启动监听] --> End

生命周期函数

1、beforeLoad

graph LR
Start --> Loading[set app state to LOADING] --> GlobalHook --> LoadHTML --> Loaded[set app state to LOADED] --> End

LoadHTML:

graph LR
Start --> Init[get container ant entry] --> Dom[get dom node] --> parseHTML --> CSS[get external css] --> JS[get external js] --> RunCode --> Return[return application]

2、Assets

graph LR
Start --> IsLoaded{"status === LOADED"}
IsLoaded --> | false | End
IsLoaded --> | true | Assets[set app state to ASSETS_LOADING] --> RunApp[run app.assets] --> NotMounted[set app state to NOT_MOUNTED] --> End

3、Mounted

graph LR
Start --> Mounting[set app state to MOUNTING] --> RunApp[run app.mounted] --> GloalHook --> Mounted[set app state to MOUNTED] --> End

4、UnMounted

graph LR
Start --> UnMounting[set app state to UNMOUNTING] --> RunApp[run app.unmount] --> GlobalHook --> NotMounted[set app state to NOT_MOUNTED] --> End