微前端框架
2020-09-21 21:46:30 0 举报
AI智能生成
微前端框架 - single-spa、qiankun 源码架构解析
作者其他创作
大纲/内容
single-spa
registerApplication,注册应用
sanitizeArguments,处理用户提供的应用参数
validateRegisterWithConfig / validateRegisterWithArguments,验证用户提供的配置参数是否合法
返回 registration 对象,处理过后的应用配置参数
loadApp,一个返回promise的函数
customProps,一个对象
activeWhen,返回一个boolean值的函数
检测应用是否重名
apps.push(appConfig),将所有应用的配置信息都存放到apps数组中
ensureJQuerySupport
reroute
reroute,更改app.status和执行生命周期函数
将应用分为4大类
appsToUnload
appsToUnmount
appsToLoad
appsToMount
根据是否已经执行过starts函数
是 => 四大类app
performAppChanges,执行改变
dispatch自定义事件,在应用状态改变之前,给用户提供搞事情的机会
移除应用 appsToUnload.map(toUnloadPromise)
一般情况下不会执行移除操作,这里什么都不做,除非手动调用unloadApplication方法
更改应用状态
执行清理动作
从appsToUnload对象中移除该应用
从app对象上删除4个生命周期函数
完了app.status = NOT_LOADED
执行生命周期函数unload
卸载应用 => 移除应用
更改状态app.status = UNMOUNTING
执行unmount生命周期函数
卸载结束然后执行移除操作
卸载完成后触发一个自定义事件
加载应用 appstoLoad,当然这里其实只有手动执行过unloadAppliction方法,才会有二次加载
toLoadPromise(app)
tryToBootstrapAndMount(app),两次判断,目的是为了防止用户中途切换路由以后程序还在执行后续没必要的事情
第一次结果为true,执行初始化
第二次结果也为true,执行挂载
初始化和挂载应用
appToMount,初始化和挂载app,更改app.status和执行生命周期函数
而具体是怎么挂载的,就需要去看执行的生命周期函数的实现了,这个是子应用来决定的,比如single-spa-vue
这一切完成以后的一些收尾工作,比如其中有一个dispatch自定义事件
否 => appsToLoad
loadApps,通过微任务加载应用
toLoadPromise(app)
判断是否已经在被加载和状态是否符合加载的条件
app.loadPromise
app.stats === NOT_LOADED || app.status === LOAD_ERROR
app.status = LOADING_SOURCE_CODE
const loadPromise = app.loadApp(getProps(app)),加载函数必须返回一个promise
验证promise的结果,也就是加载函数中return出来的子应用打包后的对象
必须是个对象
导出bootstrap、mount、unmount生命周期函数
app.status = NOT_BOOTSTRAPPED
在app对象上挂载接收props作为参数的方法,方法内部执行子应用导出的生命周期函数,并且包生命周期函数返回一个promise
delete app.loadPromise
如果加载过程中出错
用户错误
app.status = SKIP_BECAUSE_BROKEN
其它错误
app.status = LOAD_ERROR
200ms 后重新加载
start(opts)
调用start之前,应用会被加载,但不会初始化、挂载和卸载,有了start可以更好的控制应用性能
reroute()
/src/navigation/navigation-events.js,监听路由变化
当触发hashchange和popstate事件时,执行reroute
扩展了原生的addEventListener方法,增加了对两个事件对应处理方法的缓存
增强pushstate和popstate的功能,除了本职工作外,还会触发reroute
暴露全局属性 => window.singleSpaNavigage,可识别子应用是独立运行还是在基座上运行
qiankun
registerMicroApps,注册微应用的基础配置信息
过滤出未注册的微应用,防止应用重复注册
循环注册微应用,registerApplication
loadAdd
从传递的参数中解析出微应用的基本信息
importEntry,得到微应用的html模版(index.html)、js脚本执行器,publicPath
importHTML
通过fetch请求微应用的入口地址,得到完整的html
processTpl,处理上一步得到的html模版
移除html中初始内容 <!-- -->
匹配 link 标签 <link rel = "stylesheet | prefetch | preload" href = "xxx" />
匹配 <link rel = "stylesheet" href = "xxx" />,外链的样式文件
解析出标签中href属性值,即css文件的地址
如果标签内有 ignore 属性,将该 link 标签替换为一段注释并直接 return,<!-- ignore asset ${href} replaced by import-html-entry -->
将 css 的绝对地址存入 styles 数组中
将 link 标签替换为一段注释, <!-- prefetch/preload link ${linkHref} replaced by import-html-entry -->
匹配 <link rel = "prefetch | preload" href = "xxx" as = "不是font(字体)" />,预加载或者预取的非字体文件,一般是js文件
将该 link 标签替换为一段注释, <!-- prefetch/preload link ${linkHref} replaced by import-html-entry -->
匹配 style 标签,如果标签内 ignore 属性,则将其替换为一段注释, <!-- ignore asset style file replaced by import-html-entry -->
匹配 script 标签
校验 script 标签的 type 属性值是否有效
text/javascript
module
application/javascript
text/ecmascript
application/ecmascript
匹配 <script type = "非 text/ng-template" src = "xxx" />
匹配 <script type = "xx" src = "xx" entry />,如果存在 entry 属性,则将脚本的绝对地址给 entry 变量,作为微应用的入口地址
匹配到 <script ... ignore />,则替换该标签为:<!-- ignore asset ${url} replaced by import-html-entry -->
模块属性匹配成功,则也替换为响应的注释
如果存在 src 属性,则将脚本地址存在 scripts 数组,如果是异步加载 则存一个对象 { async: true, src: xxx } 到 scripts 数组,然后将标签替换为一个注释:<!-- ${async ? 'async' : ''} script ${scriptSrc} replaced by import-html-entry -->
如果上面的匹配没成功则
存在 ignore 属性,则处理
存在 module 则处理
如果是行内脚本 <script>code content</script>,则获取脚本内容
将有效的内容(非纯注释块)存如 scripts 数组,然后将标签替换为注释:<!-- inline scripts replaced by import-html-entry -->
从 scripts 中去除无效的(空的)script
return { template, scripts, styls, entry: entry || scripts[scripts.length - 1] }
getEmbedHTML,将 template 模版中的外链 css 文件变成行内的 style 标签形式,减少 HTTP 请求优化性能
getExternalStyleSheets,通过 fetch 请求特定地址的 css 内容
将 template 模版中 <!-- prefetch/preload link ${linkHref} replaced by import-html-entry --> 替换为 <style>/* ${styleSrc } */ css 内容 </style>
return Promise.resolve().then(res)
样式隔离 =》shadow dom 方式或者 css scoped,两者不能共存(这点官网没说)
用一个特殊的 div 表气啊包裹微应用模版,appContent = <div id="__qiankun_microapp_wrapper_for_app_1_1599810928171_126__" data-name="app1">${template}</div>
createElement,通过 shadow dom 隔离微应用样式
创建容器元素 div
将容器元素子元素设置为 appContent
appElement = appContent
如果支持严格样式隔离,strictStyleIsolation = true
appElement 下创建一个shadow dom
将微应用模版内容放到 shadow dom 中,以达到隔离微应用样式的目的
return appElement = <div id = "qiankun_microapp_wrapper_for... " ...>#shadow-root (open)</div>
css scoped
styleNodes = element.querySelectorAll('style') || []
循环遍历styleNodes,处理每一个style => css.process
生成样式前缀 => ${tag}[data-qiankun]=${appName}
给样式加前缀,processor.process
获取style节点中的样式文本
rewrite方法负责更改样式,用前缀替换掉全局(根)选择器,在普通选择器中塞前缀进去
通过 MutationObserver 监听 styleNode 的变化,当样式内容发生变化时用 rewrite 处理
获取 container 元素,然后将微应用放到 container 下面
运行时沙箱,处理 JS 全局对象污染问题,sandboxInstance = createSandbox()
通过 proxy 来实现对 window 对象的增删改的记录,每次激活和取消激活时将 window 对象恢复到上一个状态,不支持 proxy 通过 diff 来实现,原理一样,都是记录、比对然后更新
生命周期
全局状态
返回一个配置对象
loadMicroApp,手动加载微应用
mountRootParcel
start,启动qiankun
0 条评论
下一页