single-spa 应用与普通的单页面是一样的,只不过它没有HTML页面。在一个single-spa中,你的SPA包含许多被注册的应用,而各个应用可以使用不同的框架。被注册的这些应用维护自己的客户端路由,使用自己需要的框架或者类库。应用只要通过挂载,便可渲染自己的html页面,并自由实现功能。“挂载”(mounted)的概念指的是被注册的应用内容是否已展示在DOM上。我们可通过应用的activity function来判断其是否已被挂载。应用在未挂载之前,会一直保持休眠状态。
要添加一个应用,首先需要注册该应用。一旦应用被注册后,必须在其入口文件(entry point)实现下面提到的各个生命周期函数。
在一个 single-spa 页面,注册的应用会经过下载(loaded)、初始化(initialized)、被挂载(mounted)、卸载(unmounted)和unloaded(被移除)等过程。single-spa会通过“生命周期”为这些过程提供钩子函数。
生命周期函数是 single-spa 在注册的应用上调用的一系列函数,single-spa 会在各应用的主文件中,查找对应的函数名并进行调用。
注:
bootstrap
, mount
, and unmount
的实现是必须的,unload
则是可选的async
函数注
在single-spa 生态中有各个主流框架对于生命周期函数的实现,这些文档有助于理解这些helper执行的操作,也有助于你自己实现生命周期函数。
生命周期函数使用"props" 传参,这个对象包含single-spa相关信息和其他的自定义属性。
function bootstrap(props) {
const {
name, // 应用名称
singleSpa, // singleSpa实例
mountParcel, // 手动挂载的函数
customProps // 自定义属性
} = props; // Props 会传给每个生命周期函数
return Promise.resolve();
}
内置参数
每个生命周期函数的入参都会保证有如下参数:
name
: 注册到 single-spa 的应用名称singleSpa
: 对singleSpa 实例的引用, 方便各应用和类库调用singleSpa提供的API时不再导入它。 可以解决有多个webpack配置文件构建时无法保证只引用一个singleSpa实例的问题。mountParcel
: mountParcel 函数.#自定义参数
除single-spa提供的内置参数外,还可以指定自定义参数,在调用各个生命周期函数时传入。指定方法是在调用registerApplication
时,传入第4个参数。
singleSpa.registerApplication({
name: 'app1',
activeWhen,
app,
customProps: { authToken: "d83jD63UdZ6RS6f70D0" }
});
singleSpa.registerApplication({
name: 'app1',
activeWhen,
app,
customProps: (name, location) => {
return { authToken: "d83jD63UdZ6RS6f70D0" };
}
});
export function mount(props) {
console.log(props.authToken); // 可以在 app1 中获取到authToken参数
return reactLifecycles.mount(props);
}
可能使用到的场景:
注意如果没有提供自定义参数,则props.customProps
默认会返回一个空对象。
有一些帮助类库会对针对主流框架的生命周期函数进行实现以方便使用。具体可参见生态页面。
注册的应用会被懒加载,这指的是该应用的代码会从服务器端下载并执行。注册的应用在activity function 第一次返回真值(truthy value)时,下载动作会发生。在下载过程中,建议尽可能执行少的操作,可以在bootstrap
生命周期之后再执行各项操作。若确实有在下载时需要执行的操作,可将代码放入子应用入口文件中,但要放在各导出函数的外部。例如:
console.log("The registered application has been loaded!");
export async function bootstrap(props) {...}
export async function mount(props) {...}
export async function unmount(props) {...}
这个生命周期函数会在应用第一次挂载前执行一次。
export function bootstrap(props) {
return Promise
.resolve()
.then(() => {
// One-time initialization code goes here
console.log('bootstrapped!')
});
}
每当应用的activity function返回真值,但该应用处于未挂载状态时,挂载的生命周期函数就会被调用。调用时,函数会根据URL来确定当前被激活的路由,创建DOM元素、监听DOM事件等以向用户呈现渲染的内容。任何子路由的改变(如hashchange
或popstate
等)不会再次触发mount
,需要各应用自行处理。
export function unmount(props) {
return Promise
.resolve()
.then(() => {
// Do framework UI unrendering here
console.log('unmounted!');
});
}
“移除”生命周期函数的实现是可选的,它只有在unloadApplication被调用时才会触发。如果一个已注册的应用没有实现这个生命周期函数,则假设这个应用无需被移除。
移除的目的是各应用在移除之前执行部分逻辑,一旦应用被移除,它的状态将会变成NOT_LOADED,下次激活时会被重新初始化。
移除函数的设计动机是对所有注册的应用实现“热下载”,不过在其他场景中也非常有用,比如想要重新初始化一个应用,且在重新初始化之前执行一些逻辑操作时。
export function unload(props) {
return Promise
.resolve()
.then(() => {
// Hot-reloading implementation goes here
console.log('unloaded!');
});
}
默认情况下,所有注册的应用遵循全局超时配置,但对于每个应用,也可以通过在主入口文件导出一个timeouts
对象来重新定义超时时间。如:
export function bootstrap(props) {...}
export function mount(props) {...}
export function unmount(props) {...}
export const timeouts = {
bootstrap: {
millis: 5000,
dieOnTimeout: true,
warningMillis: 2500,
},
mount: {
millis: 5000,
dieOnTimeout: false,
warningMillis: 2500,
},
unmount: {
millis: 5000,
dieOnTimeout: true,
warningMillis: 2500,
},
unload: {
millis: 5000,
dieOnTimeout: true,
warningMillis: 2500,
},
};
注意millis
指的是最终控制台输出警告的毫秒数,warningMillis
指的是将警告打印到控制台(间隔)的毫秒数。
如果你想为应用在挂载和卸载时加一些过渡效果(动画效果等),则需要将其和bootstrap
, mount
, 和 unmount
等生命周期函数关联。这个single-spa 过渡仓库是个小demo,展示了生命周期之间切换时如何过渡。
对于已经挂载的应用,各个页面之间的过渡效果可由应用本身自行处理,如基于React创建的项目可使用using react-transition-group实现过渡效果。
在大型微服务系统中,你的single-spa基础配置和每个应用程序都应该有自己的git仓库。如何在JavaScript项目中实现这一点暂无定论,因此下面列出了一些建议。
由于single-spa是一个有助于组织扩展的框架,因此了解如何将应用程序彼此分离是很重要的,这样开发人员和团队就可以在不相互干扰的情况下开发子应用。
大多数的微服务体系都鼓励独立的代码仓库、构建和部署。虽然 single-spa不能解决如何托管、构建或部署 代码的问题,但是这些问题与许多single-spa用户相关,因此这里讨论了一些策略。
选择 1: 一个代码仓库, 一个build包
使用single-spa的最简单方法是拥有一个包含所有代码的仓库。通常,您只有一个package.json,一个的webpack配置,产生一个包,它在一个html文件中通过标签引用。
优势:
劣势:
选择 2: NPM包
创建一个父应用,npm安装每个single-spa应用。每个子应用在一个单独的代码仓库中,负责每次更新时发布一个新版本。当single-spa应用发生更改时,根应用程序应该重新安装、重新构建和重新部署。
通常,single-spa应用分别使用babel或者webpack来编译。
请注意,您还可以使用monorepo方法,该方法允许单独构建,而不需要单独的代码仓库。
优势:
劣势:
选择 3: 动态加载模块
创建一个父应用,允许子应用单独部署。为了实现这一点,创建一个manifest文件,当子应用部署更新时,它控制子应用的“上线”版本及加载的JavaScript文件。
改变每个子应用加载的JavaScript文件有很多的方法
Monorepo | NPM包 | 动态加载模块 | |
---|---|---|---|
搭建难度 | 简单 | 中等 | 困难 |
代码是否独立 | No | No | ✅ |
分开构建 | No | ✅ | ✅ |
分别部署 | No | ✅ | ✅ |
例子 |
|
|
|
如果你想将现有的 SPA 应用迁移到 single-spa 框架中,需要做三件事:
你现有的 SPA 应用,无论是基于 Angular,React 还是其他技术,可能都无法从 DOM 中将自身卸载。而且,可能都是控制整个 html 页面,而不是一个不对标签和
标签进行唯一控制的单纯 JavaScript 应用程序。因此,为了将它们转换为 single-spa 注册应用,需要在实现其生命周期功能时处理掉这些问题。
请参阅注册应用的生命周期文档,以了解需要做什么。最难的部分可能就是 unmount
这个生命周期,因为大多数 SPA 都不会休眠并从 DOM 卸载自身。在实现生命周期功能时,请先查看生态系统文档。如果文档中没有列出你所使用的技术栈,则必须确保在你自己实现的 unmount
中,能够清理其 DOM 节点,DOM 事件侦听(所有的事件侦听,尤其是 hashchange
和 popstate
)以及释放内存。
等依赖生效由于现有的 SPA 应用中, CSS、字体、第三方脚本等依赖都在 index.html 文件中,因此你可能必须做一些工作,以确保当你的 SPA 应用变成了无 HTML 应用程序之后,这些依赖可以正常工作。最好是将所有的资源都打包到 js 文件中,但是你也可以将所需要的资源列在single spa 配置中。