通用中心路由基座式微前端实现
什么是微前端
微前端架构具备以下几个核心价值
- 技术栈无关:主框架不限制接入应用的技术栈,子应用具备完全自主权
- 独立开发、独立部署:子应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
- 独立运行时:每个子应用之间状态隔离,运行时状态不共享
现有解决方案
- 路由分发(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
- 设置全局变量
HistoryEvent
- 重写
pushState
方法,每次调用之后生成一个PopStateEvent
,赋值给HistoryEvent
- 重写
replaceState
方法,每次调用之后生成一个PopStateEvent
,赋值给HistoryEvent
- 监听
hashChange
,调用reroute
- 监听
popstate
,调用reroute
- 对
addEventListener
和removeEventListener
注入,对hashChange
和popstate
进行拦截
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