single-spa的原理其实很简单,它就是一个子应用加载器 + 状态机的结合体,而且具体怎么加载子应用还是基座应用提供的;框架里面维护了各个子应用的状态,以及在适当的时候负责更改子应用的状态、执行相应的生命周期函数
1、single-spa的状态
注册、加载、初始化、挂载、卸载、移除、错误
LOAD_ERROR,这通常是由于下载应用程序的js包时出现网络错误造成的。Single-spa将在用户从当前路由导航并返回后重试加载应
SKIP_BECAUSE_BROKEN,应用在加载、初始化、挂载或卸载过程中抛出错误,由于行为不当而被跳过,因此被隔离。其他应用将正常运行
2、single-spa 源码分析
从源码的 rollup.config.js 中可以看出,入口文件为 /src/single-spa.js,这个文件导出很多方法和状态值
registerApplication 注册子应用
single-spa/src/applications/apps.js
- sanitizeArguments(appNameOrConfig, appOrLoadApp, activeWhen,customProps),格式化用户传递的应用配置参数
- getAppNames() ,去重判断
- apps.push(app),将新的应用最加到apps中,并添加一些内置属性status、loadErrorTime、parcels、devtools
- ensureJQuerySupport() jQuery打补丁
- reroute(),更改app.status和执行生命周期函数
reroute 更改app.status和执行生命周期函数
single-spa/src/navigation/reroute.js
- 将apps分为4类,需要被移除的、需要被卸载的、需要被加载的、需要被挂载的
- 已经start(),将4类合在一起,执行performAppChanges()
- 没有start(),执行loadApps(),把需要被加载的执行toLoadPromise
performAppChanges()
- dispatch事件
- 取消导航,执行finishUpAndReturn(),并且导航到旧的地址上
- 没有取消导航,
需要被移除的,直接执行toUnloadPromise;
需要被卸载的,先执行toUnmountPromise,再执行toUnloadPromise;
需要被加载的,先执行toLoadPromise,执行toBootstrapPromise,等到移除和卸载完成再执行toMountPromise
需要被挂载的,先执行toBootstrapPromise,等到移除和卸载完成后再执行toMountPromise
最后把移除和卸载的完成后,把loadThenMountPromises + mountPromises合一起,完成后执行finishUpAndReturn()
toLoadPromise(app)
- 如果已经存在app.loadPromise,说明已经被加载过,直接返回
- 只有状态为NOT_LOADED和LOAD_ERROR的app才可以被加载,其他状态直接返回
- 设置app.status = LOADING_SOURCE_CODE;
- 执行app.loadApp(props),返回一个promise。这个promise.then得到用户定义的生命周期方法:bootstrap、mount、unmount、unload、timeouts
- 设置app.status = NOT_BOOTSTRAPPED;
- 把得到的生命周期方法挂到app中,然后删除app.loadPromise
toBootstrapPromise(appOrParcel, hardFail)
- 只有NOT_BOOTSTRAPPED状态可以被初始化,其他状态直接返回
- 设置appOrParcel.status = BOOTSTRAPPING
- 执行生命周期方法bootstrap
- 设置appOrParcel.status = NOT_MOUNTED
toMountPromise(appOrParcel, hardFail)
- 只有NOT_MOUNTED状态可以被挂载,其他状态直接返回
- 执行生命周期方法mount
- 设置appOrParcel.status = MOUNTED
toUnmountPromise(appOrParcel, hardFail)
- 只有MOUNTED状态可以被卸载
- 设置appOrParcel.status = UNMOUNTING
- 执行生命周期函数unmount
- appOrParcel.status = NOT_MOUNTED
toUnloadPromise(app)
- 维护自己的移除队列appsToUnload
- NOT_LOADED状态,直接执行finishUnloadingApp(app, unloadInfo),清除工作,设置app.status = NOT_LOADED
- UNLOADING状态,等待移除完成
- 非NOT_MOUNTED和非LOAD_ERROR,需要等到挂载完成,再进行移除
- 执行生命周期函数unload
- 设置app.status = UNLOADING
- 移除完成,执行finishUnloadingApp(app, unloadInfo),完成清除工作,设置app.status = NOT_LOADED
start(opts)
调用start之前,应用会被注册和加载,但不会被初始化、挂载、卸载
路由监听
- hashchange:当URL中的片段标识发生改变时,会触发此事件
- popstate:当活动历史条目更改时,触发popstate事件。需要注意的是调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在JS代码中调用history.back()或者history.forward()方法)
window.addEventListener("hashchange", funcRef, false);
window.addEventListener("popstate", funcRef, false);
- history.pushState(state, title, url) 向当前浏览器会话的历史堆栈中添加一个状态
- history.replaceState(state, title, url) 修改当前历史记录实体
history.pushState({foo: "bar"}, "", "bar.html");
history.replaceState({foo: "bar"}, "", "bar2.html");
3、single-spa的缺点
single-spa 就做了两件事,加载微应用(加载方法还是用户自己提供的)、维护微应用状态(初始化、挂载、卸载)。
single-spa 采用 JS Entry 的方式接入微应用。将整个微应用打包成一个JS文件,发布静态资源服务器,然后在主应用中配置该 JS 文件的地址告诉 single-spa 去这个地址加载微应用。
single-spa存在一些问题:
- 1、对微应用的侵入性太强
将整个微应用打包成一个 JS 文件,常见的打包优化基本上都没了,比如:按需加载、首屏资源加载优化、css 独立打包等优化措施 - 2、样式隔离问题
single-spa中没有做这部分的工作。如果保证一个大型项目中,主应用和微应用之间,微应用和微应用之间的样式隔离。比如应用样式以自己的应用名称开头 - 3、JS隔离问题
single-spa中没有做这部分的工作。JS全局对象污染,没有保证微应用A和微应用B的window互相没有影响 - 4、资源预加载
single-spa中没有做这部分的工作。 - 5、应用间通信
single-spa中没有做这部分的工作。它只在注册微应用时给微应用注入一些状态信息,后续就不管了,没有任何通信的手段,只能用户自己去实现