前言
众所周知,微前端方案中的qiankun是基于single-spa的,而single-spa是基于systemJs的,上文我们已经聊过systemJS了,并且简单实现了下核心的加载功能。那么现在就该到single-spa了,可以说只要了解了single-spa,那么微前端思想就基本通了,而qiankun只是在single-spa上层做了些功能的拓展,比如预加载,沙箱等。
那么好,我们开始进入正题吧。
single-spa
single-spa是什么,这个不用过多介绍,一个微前端库。
那single-spa如何使用呢,以及它如何运行的呢?我们直接着手体验一下就好了。
使用
- 全局安装
lua
npm install create-single-spa -g
- 创建基座
lua
create-single-spa xxx
- 打开基座项目
入口代码:
看过上一篇关于systemJS的jym应该会觉得非常熟悉,这部分的代码和systemJS的入口文件长得非常像了,毕竟single-spa是基于systemJS的。
应用代码:
结合入口和应用的代码,整个single-spa跑起来逻辑如下("start": "webpack serve --port 9000 --env isLocal"):
执行start命令,访问localhost:9000
根应用 -> index.ejs -> @rippi/root-config -> rippi-root-config.js -> 匹配activeWhen
现在我们就能看到基座页面了。接下来我们创建子应用。
- 创建子应用
直接采用single-spa提供的命令方式去创建,也可以自己创建项目,按照文档进行配置,最后接入协议即可。
- 打开子应用
从目录就能看出来基座和子应用的区别,少了一个index.ejs但多了自己项目的内容文件,如root.component和root.component.test。
root.component:
root.component.test:
从文件内容上来看,他们的入口文件也是很不同的。
rippi-reactProject(这个文件名是基于创建子应用时填写的组织名和项目名的):
我们在图中,多次看到了export const { bootstrap, mount, unmount } = lifecycles;
。
这就是single-spa的接入协议,子应用必须向基座暴露出这bootstrap、mount和unmount这三个钩子,所以如果不通过single-spa提供的方式创建子应用,就要自行去实现这三个钩子,并暴露出去。
这三个钩子的具体作用就和他们的名字一样:
bootstrap:提供加载目标子应用的方法。
mount:提供挂载目标子应用的方法。
unmount:提供卸载目标子应用的方法。
- 子应用的命令
命令也大不相同,子应用有这两种玩法,一种是独立起起来,执行start:standalone,另一种是作为子应用让基座加载,那就执行start。
那么我们就来分别执行这两种命令。
1、npm run start:standalone
独立起来就比较普通,就是项目本身的内容,没有什么特别的地方。我们重点关注start命令。
2、npm run start
如图所示,跑起来的这个页面给出了我们很多提示,一个是说明了本项目的访问地址,另一个是让我们去基座那里注册我们这个子应用。
我们先来访问下这个地址。
熟悉的配方了,完全符合systemJS规范的代码。
- 在基座中注册子应用
在rippi-root-config.js(这个文件名是single-spa依据大家的组织名)中添加一个registerApplication。
在index.ejs中加入@rippi/reactProject的地址。
由于基座中没装react和reactdom,但single-spa默认认为这些为公共资源,所以需要删一下子应用的配置。
在子应用的webpack.config中删掉externals配置。
这里选择了最省事的方法进行删除。
- 效果
最后的效果是,子应用起在了8080端口,但访问9000端口(基座)的/react能看到子应用,这就是注册成功了。
基于以上步骤,大家可以自行创建更多的子应用进行更多的体验,篇幅原因,这里就不进行第二个子应用的创建了。
简化
上面我们体验了下single-spa的基本使用,不过框架、打包等部分使得整个例子有点臃肿了,不好看出太多的东西,我们这里就来简化下,去掉框架和打包等东西。
- 以一个简单的html作为基座,注册两个没有dom结构的子应用,极致的简化例子。
html
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/single-spa/5.9.3/umd/single-spa.min.js"></script>
<script>
const { registerApplication, start } = singleSpa;
console.log(singleSpa);
const appA = {
bootstrap: async () => {
console.log('app1 bootstrap');
},
mount: async () => {
console.log('app1 mount');
},
unmount: async () => {
console.log('app1 unmount');
}
};
const appB = {
bootstrap: async () => {
console.log('app2 bootstrap');
},
mount: async () => {
console.log('app2 mount');
},
unmount: async () => {
console.log('app2 unmount');
}
};
registerApplication('a', async () => appA, (location) => location.hash.startsWith('#/a'));
registerApplication('b', async () => appB, (location) => location.hash.startsWith('#/b'));
start();
</script>
</body>
我们从single-spa那里引入了两个核心的api ------ registerApplication 和 start。
然后创建了两个子应用 ------ appA 和 appB。
这两个子应用分别暴露了bootstrap、mount和unmount钩子。
然后调用registerApplication进行注册。
最后将基座启动。
- 效果
打印了single-spa,了解下single-spa提供的方法。
访问a应用:
访问b应用:
再次访问a应用:
经过这个简化的例子,更清楚了解single-spa了。
一般来说通过基座来加载子应用,子应用要接入协议,在mount中进行挂载逻辑(如reactDOM的createRoot),在unmount中进行卸载逻辑。
基座基本就只是一个管理的作用,根据路由和子应用的匹配规则(registerApplication的第三个参数)进行子应用的切换(也就是执行相应的bootstrap、mount、unmount)。
那么就可以洞悉出来这两个主要的方法都大概做了什么。
registerApplication:注册子应用,记录匹配规则和子应用的接入协议以及子应用名。
start:通过当前路由和已注册的子应用的匹配规则进行匹配,然后调用相应的方法。
结尾
到此,本文的所有内容就结束了,不过single-spa的内容并没有结束,本文只是体验了下single-spa,并通过一个极简的例子说明single-spa核心的registerApplication和start都做了些什么,让我们对single-spa有了更深一点的理解,基于这些理解或者说基础,我们将在下一篇文章中实现一个简单的single-spa,敬请期待。