前言
阅读本文前,需要您对single-spa
有简单的了解。
single-spa
是一个非常棒的微前端框架,我们都知道另外一款非常优秀的前端框架qiankun
就是基于single-spa
实现的,在其基础上,增加了沙箱机制、css Model样式隔离。
本文分为三部分:
- single-spa源码解析
- 项目实战
- 手写简版single-spa
一、single-spa源码解析
single-spa
源码不多,但是写的非常好,感觉把异步用到了极致。非常推荐大家阅读一下~
源码地址
这是我拷贝下来源码,添加了注释,并用中文标注了错误提示,可以帮助理解源码:仓库地址
git clone xxx
npm install
npm run dev # 默认启动3000端口
复制代码
此时,就可以在本地debugger源码了。
目录结构
├─src
| ├─single-spa.js # 入口
| ├─start.js # start方法
| ├─utils # 工具
| | ├─assign.js
| | ├─find.js
| | └runtime-environment.js
| ├─navigation # 导航相关
| | ├─navigation-events.js # 导航事件
| | └reroute.js # 核心方法,重新路由函数
| ├─lifecycles # 生命周期
| | ├─bootstrap.js
| | ├─lifecycle.helpers.js
| | ├─load.js
| | ├─mount.js
| | ├─prop.helpers.js
| | ├─unload.js
| | ├─unmount.js
| | └update.js
| ├─applications # 应用程序相关
| | ├─app-errors.js
| | ├─app.helpers.js # 状态管理
| | ├─apps.js # 注册应用等
| | └timeouts.js
复制代码
我们主要关注avigation、lifecycles、applications
这三个文件夹即可,是整个源码的核心。
文字不如图示
single-spa
是一个状态机,维护着应用的状态,他的主要工作是路由劫持和应用加载: 主要分为四个过程:加载、激活启用、挂载、卸载销毁。
- 核心方法脑图
single-spa
核心方法就三个registerApplication,reroute,start
,脑图描述了这三个方法都具体做了什么:核心方法逻辑图示
- 初始化加载流程
- 应用启动和挂载流程
二、项目实战
项目启动方式:
git clone xxx
yarn install
npm run serve
复制代码
1.主应用做了什么
主应用的主要工作就是拦截路由,加载子应用。两个核心方法registerApplication
和start
。
npm install single-spa
复制代码
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router/index.js'
// 导入注册应用,和启动应用的方法
import { registerApplication, start } from 'single-spa';
// 手动加载子应用打包出来的文件
const loadScript = async (url) => {
await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script)
});
}
registerApplication(
'singleVue',
async () => {
// 加载模块
await loadScript('http://localhost:10000/js/chunk-vendors.js'); // vue打包出来的公共模块
await loadScript('http://localhost:10000/js/app.js'); // vue打包出来的应用核心
// 生命周期 singleVue上有bootstrap,mount,unmount方法,打包的时候打到singleVue上去的
return window.singleVue
},
location => location.pathname.startsWith('/vue') // 激活标识
)
start();
new Vue({
router,
render: h => h(App)
}).$mount('#app')
复制代码
vue
通过umd
规范打包完成后,有两个重要模块,chunk-vendors.js
是打包出来的公共模块,app.js
是打包出来的应用核心模块。把这两个模块手动加载进来,待应用加载时就可以显示了。
这也暴露了single-spa
的一个短板,有多少文件需要引入是不确定的,手动加载文件不方便,如果有很多文件,总不能用这种方式一个一个加载吧。
window.singleVue
是啥?是一个全局变量,子应用打包时用umd
格式进行打包,会将singleVue
挂载到全局。singleVue
上都有啥呢?有我们暴露的一些方法,如:bootstrap,mount,unmount
,这些恰巧是single-spa
需要的方法。
// app.vue
<template>
<div id="app">
<router-link to="/vue">加载子应用</router-link>
<!-- 子应用挂载在这里 -->
<div id="vue"></div>
</div>
</template>
复制代码
当我们加载子应用时,让子应用挂载在id
为vue
这里。
2.子应用需要做什么
- 暴露
single-spa
需要的bootstrap,mount,unmount
方法,single-spa
是协议接入,这几个方法都是必须要提供的,而且必须是异步的。 - 进行打包,并将
singleVue
挂载到window
上,以便父应用使用。 - 兼容不使用
single-spa
时,子应用也可以独立运行。
先安装依赖:
npm install single-spa-react
复制代码
子应用是vue应用用single-spa-vue
,是react应用用single-spa-react
。
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router/index.js'
Vue.config.productionTip = false
import singleSpaVue from 'single-spa-vue';
const appOptions = {
el: '#vue', // 挂在父应用id为vue的标签位置
router,
render: h => h(App)
}
// 在非子应用中正常挂载应用
if (!window.singleSpaNavigate) {
delete appOptions.el;
new Vue(appOptions).$mount('#app');
}
const vueLifeCycle = singleSpaVue({
Vue,
appOptions
});
// 协议接入
// 子应用必须导出 以下生命周期 bootstrap、mount、unmount
// 子应用还需要打包成一个个的lab给父应用使用
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
export default vueLifeCycle;
复制代码
// vue.config.js
const path = require('path');
const resolvePath = dir => path.resolve(__dirname, dir);
module.exports = {
configureWebpack: {
output: {
library: 'singleVue',
libraryTarget: 'umd' // umd打包,会把bootstrap,mount,unmount都打包到singleVue上,挂在window上
},
devServer:{
port:10000
}
},
chainWebpack: config => {
// 配置解析别名
config.resolve.alias
.set('@', resolvePath('src'))
.set('@components', resolvePath('src/components'))
.set('@views', resolvePath('src/views'))
}
}
复制代码
三、手写简版single-spa
项目地址:简版single-spa.
简版single-spa
麻雀虽小,但五脏俱全。基本还原了源码的核心逻辑,阅读上更友好~