前言

single-spa是一个用于前端微服务化的JavaScript前端解决方案。single-spa的核心就是定义了一套协议。协议包含主应用的配置信息和子应用的生命周期,通过这套协议,主应用可以方便的知道在什么情况下激活哪个子应用。


配置信息

1. 主应用配置

single-spa中的配置信息也称为:Root Config,如下就是具体的配置项。需要配置子应用的名称,加载方式以及加载时机。

1
2
3
4
5
6
{
name: "subApp1", // 子应用的名称
app: () => // 告诉主应用如何加载子应用的代码,
System.import("/a/b/subAPP/code"),
activeWhen: "/subApp1", // 告诉主应用何时激活子应用
}

single-spa提供registerApplication将子应用的信息注册到主应用中。

1
2
3
4
5
6
singleSpa.registerApplication(
{
name: 'appName',
app: () => System.import('appName'),
activeWhen: '/appName',
})

在上面的代码中System.import让人比较在意,这是什么呢?

这个问题要从主应用如何加载子应用说起,在single-spa中子应用要实现生命周期函数,然后导出给主应用使用。关键就是这个“导出”的实现,这涉及到JavaScript的模块化问题,即需要把子应用打包成一个包含生命周期的模块,让主应用引入。

2. JavaScript的模块化,如何在页面中引入模块?

JavaScript的模块化就是将JavaScript程序拆分为可按需导入的单独模块的机制。Node.js已经提供这个能力很长时间了,还有很多的Javascript库和框架已经开始了模块的使用(例如CommonJS和基于AMD的其他模块系统 如RequireJS,以及最新的Webpack和Babel)。目前最新的浏览器也开始原生支持模块功能。

(1)在<script>标签上添加type=“module”来实现导入导出

1
2
3
4
5
<!doctype html>
<script type="module">
import {test} from './test.js';
document.body.innerHTML = test('1111');
</script>

(2)实现import axios from ‘axios’还需要借助于importmap

第一点虽然可以实现导入,但是每次import都要写入固定的地址,或者在不同的script中多次引入时就要重复书写,这样造成代码的冗余,所以这里可以使用importmap,使变量名和其相应的地址一一映射,允许控制js的import语句或import()表达式获取的库的url,并允许在非导入上下文中重用这个映射,这样就不用重复书写地址了。

1
2
3
4
5
6
7
8
9
10
11
<script type="importmap">
{
"imports": {
"lodash": "/node_modules/lodash/lodash.js"
}
}
</script>
<script type="module">
import {hello} from 'lodash';
document.body.innerHTML = hello('John');
</script>

(3)SystemJS

import maps的兼容性不好,所以想在生产环境下使用还是需要一些兼容实现方案,SystemJS就是解决这个问题的。

systemjs是一个模块加载器,和requirejs类似,systemjs参考import maps规范实现了自己的alias(类似requirejs-paths或者webpack alias)。具体用法在下面的demo中。

1
2
3
4
5
6
7
8
9
10
11
12
13
<script src="https://cdn.bootcss.com/systemjs/6.2.6/system.js"></script>
// 通过systemjs来引入别的文件
System.import('./test.js');
// systemjs也支持通过下面的方式定义资源 ,用来给资源定义一个key
<script type="systemjs-importmap">
{
"imports": {
"vue": "https://cdn.bootcss.com/vue/2.6.11/vue.js"
}
}
</script>
// 直接通过名称引用
System.import('vue');

3. 总结一下single-spa是如何通过以上方法加载子应用的:

在主应用中注册子应用的配置信息,主应用运行时根据配置信息去请求子应用的manifest.json配置文件,这个文件中是子应用打包出的入口js和js文件的依赖关系,主应用通过动态的构造script标签去加载这些js文件,这里就完成了其注册过程。

这样在主应用检测路由命中子应用的规则之后就会触发其渲染函数,把子应用挂载到相应的dom下。


生命周期

single-spa的另一个关键点就是生命周期,子应用生命周期包含bootstrap,mount,unmount三个回调函数。主应用在管理子应用的时候,通过子应用暴露的生命周期函数来实现子应用的启动和卸载。

  1. load:当应用匹配路由时就会加载脚本(非函数,只是一种状态)。

  2. bootstrap:应用内容首次挂载到页面前调用。

  3. Mount:当主应用判定需要激活这个子应用时会调用, 实现子应用的挂载、页面渲染等逻辑。

  4. unmount:当主应用判定需要卸载这个子应用时会调用, 实现组件卸载、清理事件监听等逻辑。

  5. unload:非必要函数,一般不使用。unload之后会重新启动bootstrap流程。


缺陷

通过以上两点的分析,大致了解了一下sing-spa的主要思想,但是single-spa毕竟是第一个微前端框架,他也有一定的缺点。

  1. single-spa的文档略显凌乱,概念也比较多,在初次学习时上手难度较高。

  2. single-spa是通过js文件去加载子应用,当文件名是乱码名时,每次子应用更新,父应用要更新引入配置文件,更新多项目时比较麻烦。

  3. single-spa本身缺少js隔离和css隔离,虽然现在已经可以引入其他的包去解决,但是并没有做到开箱即用的程度。

所以在基本了解其思路之后,我们可以不妨看一下其他的方案比如qiankun都是如何实现和优化的。


实战

  1. 官方文档

  2. single-spa 从入门到精通