微前端:single-spa是什么

背景

我们都知道 qiankun 是基于 single-spa 开发的,今天我们来扒一扒 single-spa 到底做了哪些事。
在这里插入图片描述

微前端架构

一般来说,微前端需要解决的问题分为两大类:

  • 应用的加载与切换:路由问题、应用入口、应用加载
  • 应用的隔离与通信:js隔离、css样式隔离、应用间通信

single-spa 则很好地解决了 路由问题、应用入口 两个问题,但并没有解决应用加载问题,而是将该问题暴露出来由使用者实现(一般可以用 system.js 或原生 script 标签来实现);qiankun在此基础上封装了一个应用加载方案,即 import-html-entry (可以看我写的这篇 微前端:qiankun的依赖import-html-entry的作用),并给出了js隔离、css样式隔离和应用间通信三个问题的解决方案,同时提供了预加载功能。

原理

上面我们知道了,single-spa 则很好地解决了 路由问题、应用入口 两个问题,那么接下来我们就详细的看看其中的原理。

路由问题

single-spa是通过监听hashChange和popState这两个原生事件来检测路由变化的,它会根据路由的变化来加载对应的应用,相关的代码可以在single-spa的 src/navigation/navigation-events.js 中找到:

...
// 139行
if (isInBrowser) {// We will trigger an app change for any routing events.window.addEventListener("hashchange", urlReroute);window.addEventListener("popstate", urlReroute);
...
// 174行,劫持pushState和replaceStatewindow.history.pushState = patchedUpdateState(window.history.pushState,"pushState");window.history.replaceState = patchedUpdateState(window.history.replaceState,"replaceState");
function urlReroute() {reroute([], arguments);
}
export function reroute(pendingPromises = [], eventArguments) {...// getAppChanges会根据路由改变应用的状态,状态包含4类// 待清除、待卸载、待加载、待挂载const {appsToUnload,appsToUnmount,appsToLoad,appsToMount,} = getAppChanges();...// 如果应用已启动,则调用performAppChanges加载和挂载应用// 否则,只加载未加载的应用if (isStarted()) {appChangeUnderway = true;appsThatChanged = appsToUnload.concat(appsToLoad,appsToUnmount,appsToMount);return performAppChanges();} else {appsThatChanged = appsToLoad;return loadApps();}...function performAppChanges() {return Promise.resolve().then(() => {// 1. 派发应用更新前的自定义事件// 2. 执行应用暴露出的生命周期函数// appsToUnload -> unload生命周期钩子// appsToLoad -> 执行加载方法// appsToUnmount -> 卸载应用,并执行对应生命周期钩子// appsToMount -> 尝试引导和挂载应用})}...
}
export function start(opts) {started = true;if (opts && opts.urlRerouteOnly) {setUrlRerouteOnly(opts.urlRerouteOnly);}if (isInBrowser) {reroute();}
}
  1. 根据传入的参数activeWhen判断哪个应用需要加载,哪个应用需要卸载或清除,并将其push到对应的数组
  2. 如果应用已经启动,则进行应用加载或切换。针对应用的不同状态,直接执行应用自身暴露出的生命周期钩子函数即可。
  3. 如果应用未启动,则只去下载appsToLoad中的应用。

总的来看,当路由发生变化时,hashChange或popState会触发,这时single-spa会监听到,并触发urlReroute;接着它会调用reroute,该函数正确设置各个应用的状态后,直接通过调用应用所暴露出的生命周期钩子函数即可。当某个应用被推送到appsToMount后,它的mount函数会被调用,该应用就会被挂载;而推送到appsToUnmount中的应用则会调用其unmount钩子进行卸载。

应用入口

single-spa采用的是协议入口,即只要实现了single-spa的入口协议规范,它就是可加载的应用。single-spa的规范要求应用入口必须暴露出以下三个生命周期钩子函数,且必须返回Promise,以保证single-spa可以注册回调函数:

  1. bootstrap
  2. mount
  3. unmount

bootstrap用于应用引导,基座应用会在子应用挂载前调用它。
mount用于应用挂载,就是一般应用中用于渲染的逻辑,即上述的new Vue语句。
unmount用于应用卸载,我们可以在这里调用实例的destroy方法手动卸载应用,或清除某些内存占用等。

let instance = null;
let router = null;/*** 渲染函数* 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行*/
function render() {// 在 render 中创建 VueRouter,可以保证在卸载微应用时,移除 location 事件监听,防止事件污染router = new VueRouter({// 运行在主应用中时,添加路由命名空间 /vuebase: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/",mode: "history",routes,});// 挂载应用instance = new Vue({router,render: (h) => h(App),}).$mount("#app");
}// 独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {render()
}/*** bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。*/
export async function bootstrap() {console.log("VueMicroApp bootstraped");
}/*** 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法*/
export async function mount(props) {console.log("VueMicroApp mount", props);render(props);
}/*** 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例*/
export async function unmount() {console.log("VueMicroApp unmount");instance.$destroy();instance = null;router = null;
}

应用加载

那么,我们传一下上面两部分内容,解决了路由问题、应用入口(什么样的应用可以被加载,没有说如何加载),应用是如何进行加载的。single-spa并没有提供自己的解决方案,而是将它开放出来,由开发者提供。

刚刚说了,qiankun 提供了 import-html-entry 来进行加载,那么脱离 qiankun 普通的方式是怎么样的,这里举例用 system.js 来加载

<script type="systemjs-importmap">{"imports": {"app1": "http://localhost:8080/app1.js","app2": "http://localhost:8081/app2.js","single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js"}}
</script>
... // system.js的相关依赖文件
<script>
(function(){// 加载single-spaSystem.import('single-spa').then((res)=>{var singleSpa = res;// 注册子应用singleSpa.registerApplication('app1',() => System.import('app1'),location => location.hash.startsWith(`#/app1`););singleSpa.registerApplication('app2',() => System.import('app2'),location => location.hash.startsWith(`#/app2`););// 启动single-spasingleSpa.start();})
})()
</script>

我们在调用singleSpa.registerApplication注册应用时提供的第二个参数就是加载这个子应用的方法。single-spa会调用这个函数,下载子应用代码并分别调用其bootstrap和mount方法进行引导和挂载。

总结

以上就是single-spa的核心原理,从上面的介绍中不难看出,single-spa只是负责把应用加载到一个页面中,至于应用能否协同工作,是很难保证的。而qiankun所要解决的,就是协同工作的问题。

参考资料

官网
https://zhuanlan.zhihu.com/p/378346507
https://blog.csdn.net/qq_41694291/article/details/113842872


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部