微前端框架 Qiankun 源码解析
上一篇博客我们介绍了Qiankun的沙盒机制,也就是不同的子应用之间是如何做环境隔离的,这篇博客我们就基于上一篇博客讲一下如何利用沙盒去进行子应用的加载和切换。
qiankun是基于single-spa实现的,所有内部用到了部分single-spa的接口,简单说一下single-spa和qiankun的区别,single-spa只做了子应用的注册,切换,路由监听等,还没有达到一个商用的水准,而qiankun在此之上加了js和css的沙箱隔离,按照官方文档,qiankun是达到了商用的要求。
globalState 全局状态的保存和修改
下面的代码我会加上自己的注释,同时有两点需要注意:
- 这个全局状态,即globalState是唯一的,也就是说所有的子应用都用的同一个全局状态对象
- 但是每个子应用都可以为全局状态的改变注册自己的回调函数,以appInstanceId为key存储在deps数组中,虽然当状态改变的时候所有的deps都会触发,但是通过appInstanceId为key可以在子应用被销毁的时候正确地注销该应用的回调函数
1 | /** |
prefetch 预加载子应用资源
这里主要是提供预加载子应用的策略和方法
1 | /** |
loader 子应用加载器
loader内部工具函数
1 | function assertElementExist(element: Element | null | undefined, msg?: string) { |
createElement
创建新的元素,参数列表为:
- appContent:字符串类型,需要传入html文本
- strictStyleIsolation:是否使用独立的shadow dom,如果为真,并且当前浏览器支持shadow dom,那么appContent中的内容会在一个shadow dom内部
- scopedCSS:如果为真,则将本次创建的html中的所有css内容加上前缀,具体操作看我的另一篇博客,qiankun如何做css隔离的
- appInstanceId:字符串类型,子应用的id
1 | function createElement( |
getAppWrapperGetter
1 | function getAppWrapperGetter( |
getRender
1 | /** |
loadAPP
代码很长,简单分析下分别做了什么:
- 调用single-spa的
importEntry
接口,获取子应用的加载入口等信息 - 上一步解析出来的html entry会作为appContent传入createElement函数,创建一个新节点,并把html entry挂载到该节点之下,然后返回新的节点,赋值给
initialAppWrapperElement
- 调用
getRender
获取渲染函数,赋值给render
- 将
initialAppWrapperElement
传入render
函数 - 调用
getAppWrapperGetter
获取增强过的函数initialAppWrapperGetter
,内部会做一些判断,如果判断没有问题,返回的函数initialAppWrapperGetter
会返回initialAppWrapperElement
- 如果选择启动沙箱功能,则调用createSandboxContainer去返回sandboxContainer,并将全局对象赋值为sandboxContainer的proxy,启动沙箱功能
- 通过调用getAddOns来获取所有的生命周期函数
- 调用execHooksChain来执行上一步获得的生命周期中的beforeLoad函数
- 通过execScripts(第一步single-spa的解析结果)配合getLifecyclesFromExports来获取子应用的生命周期钩子函数
- 最终生成parcelConfigGetter函数,该函数会返回关于该子应用的 bootstrap,mount,和unmount方法,这些方法中会按顺序分别调用前面获取的不同的生命周期函数
1 | export async function loadApp<T extends ObjectType>( |
工具方法
error
这里就是声明了一个Error类而已
1 | export class QiankunError extends Error { |
errorHandler
暴露一个方法去添加全局的错误处理监听函数
1 | /** |
utils
这里的只列举函数签名及其作用,具体代码实现就不贴在这里了
- function toArray
(array: T | T[]): T[] - function sleep(ms: number)
- function nextTask(cb: () => void): void
- function isConstructable(fn: () => any | FunctionConstructor)
- function isCallable(fn: any)
- function isPropertyFrozen(target: any, p?: PropertyKey): boolean
- function isBoundedFunction(fn: CallableFunction)
- const qiankunHeadTagName = ‘qiankun-head’;
- function getDefaultTplWrapper(name: string)
- function getWrapperId(name: string)
- const nativeGlobal = new Function(‘return this’)()
- const genAppInstanceIdByName = (appName: string): string
- function validateExportLifecycle(exports: any)
- function performanceGetEntriesByName(markName: string, type?: string)
- function performanceMark(markName: string)
- function performanceMeasure(measureName: string, markName: string)
- function isEnableScopedCSS(sandbox: FrameworkConfiguration[‘sandbox’])
- function getXPathForElement(el: Node, document: Document): string | void
qiankun api
这一部分是qiankun对外暴露和使用的主要api
工具方法引入
1 | import { noop } from 'lodash'; |
registerMicroApps
利用single-spa的接口registerApplication
来注册子应用
1 | export function registerMicroApps<T extends ObjectType>( |
loadMicroApp
这个函数的功能就是调用loader的loadApp,然后使用single-spa的startSingleSpa去启动
1 | export function loadMicroApp<T extends ObjectType>( |
start
1 | export function start(opts: FrameworkConfiguration = {}) { |
总结
总的来说,内部的api分工如下:
- prefetch提供了预加载所有子应用资源的方法,
- loader提供了根据子应用的入口去加载资源,执行入口文件,然后将结果html挂载到一个新建的div上
- qiankun对外暴露的api主要是两个
- registerMicroApps主要是调用sigle-spa的register接口去注册子应用
- loadMicroApp会调用loader中的方法去加载子应用,loader内部其实最终还是调用了single-spa的load方法,只是做了一些沙箱的隔离,css的隔离等。