# 情景提要
因我们的前端主工程选择用 **Vue **来进行对 **backbone **的重构,所以我们就需要在前端主工程中使用,微应用的方式对主工程中的功能渐进式的重构。由于主工程中应用部分的每个功能,都非常独立,更利于做成微应用接入到前端主工程中,所以我们这次就先从应用部分入手。(本文的主工程指的是:rishiqing-front)
# 需要提前了解的工具
# single-apa
这个框架是实现微应用的关键,但其实其原理是比较简单的,它主要做的就是将我们使用,vue/react/angular/ backbone ...等不同的框架,进行开发的应用,挂载到我们的主工程页面(应用页的body中)的body中。(single- spa 文档链接 (opens new window))
single-spa 的基本使用:
注册一个微应用管理中心:**(single-spa 所有的操作都是异步执行的)**
1.注册:
singleSpa.registerApplication(appName, loadingFunction, activityFunction);
以下是应用模块的首页做为一个单独的微应用,在这个项目中的 /src/registerApplication.js
进行应用首页中微应用的 single-spa 的注册,因为首页中需要挂载档案,云盘,等独立的应用,所以在应用首页中也是一个微应用管理中心,它隶属于主工程的应用模块的微应用管理中心。(应有首页的仓库名:rishiqing-application-index)
import * as singleSpa from "single-spa";
const lifecycleFn = ["bootstrap", "unmount", "mount"];
// 封装一下子应用暴露的方法,至少保证生命周期函数是有效的
function wrapLifecycle(life = {}) {
// eslint-disable-next-line no-param-reassign
if (!life) life = {};
lifecycleFn.forEach((method) => {
if (!life[method] || typeof life[method] !== "function") {
life[method] = () => Promise.resolve();
}
});
return life;
}
// 通过systemJs加载微应用
function loadApp(name) {
return () =>
new Promise((resolve) => {
System.import(name)
.then((app) => {
resolve(wrapLifecycle(app));
})
// js文件加载失败的时候,会执行这个报错
.catch((error) => {
resolve(wrapLifecycle(error));
});
});
}
// --- 注册功倍电商管理 ---
singleSpa.registerApplication(
// 1. '微应用name'
"commodityApp",
// 2. '加载微应用的js文件'
loadApp("commodity-manager"),
// 3. '激活微应用'
(location) => {
// 激活条件(通过判断路由的方式)
const active = location.pathname.startsWith(
"/app/application/commodity-manager"
);
// 如果microApplication是激活的,则隐藏 应用首页(#application-index-container)
const indexContainerDom = document.querySelector(
"#application-index-container"
);
if (active) {
if (indexContainerDom) indexContainerDom.style.display = "none";
} else if (indexContainerDom) {
indexContainerDom.style.display = "block";
}
return active;
}
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
2.启动:
singleSpa.start();
3.路由跳转:
singleSpa.navigateToUrl('/app/application/archive')
4.single-spa 执行挂载时的三个钩子函数:
如果是云盘/归档/回收站/番茄钟...等属于应用首页的微应用,只需要在入口文件 main.js 中将这三个钩子函数按照如下执行就可以了,不需要前面的注册环节。下面是 功倍商品管理应用的 注册钩子:
let VueContainer;
let container;
// single-spa 的三个钩子函数
// bootstrap:初始化 mount:挂载 unmount:卸载
export function bootstrap() {
return Promise.resolve();
}
export function mount(props) {
return new Promise((resolve) => {
container = document.createElement("div");
container.id = "application-commodity";
document.body.appendChild(container);
VueContainer = new Vue({
router,
store,
i18n,
render: (h) => h(App),
}).$mount("#application-commodity");
resolve();
});
}
export function unmount() {
return new Promise((resolve) => {
// 卸载应用首页
VueContainer.$destroy();
VueContainer.$el.parentNode.removeChild(VueContainer.$el);
resolve();
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# System.js
Systemjs 是一个可配置模块加载器,为浏览器和 NodeJs 启用动态的 Es 模板加载器。任何具有标准的 URL 都可被加载为一个模块,可以加载任何类型的模块格式,并由 SystemJS 自动检测。说了一堆官话,其实它的主要用途就是,将我们的微应用打包好的Js 文件加载成一个模块,让single-spa 将其挂载到主工程中。(github链接 (opens new window))
**System.js****基本使用:**
<script type="systemjs-importmap">
{
"imports": {
"application-index": "http://localhost:3002/application/application.js"
}
}
</script>
2
3
4
5
6
7
//如果是在使用Webpack打包的项目中使用System global构建代码。
//需要以下配置来避免重写:(主工程中有配置)
{
module: {
rules: [{ parser: { system: false } }];
}
}
// vue-cli3中 应注意要用push的方式 去添加该规则
2
3
4
5
6
7
8
# 开发时如何将独立的微应用接入到应用首页
前面的理论,都是我自己对这个技术点的一些理解。如有不足,还请各位老铁们多多指正。下面要讲得就是实战应用的主要步骤和一些要点。
注意点:
rishiqing-front 应用模块,是整个微应用的顶层微应用管理中心,所有的公用数据和资源由它来提供,(下面出现的一些操作如果不理解,请回过头来看下这句话。),所以在应用首页这个微应用管理中心中,不需再单独下载 single-spa 和 system 因为他们已经在顶层微应用管理中心作为全局的资源了,并且我们所有独立开发的微应用都是可以共享顶层微应用管理中心提供的数据。
- 从主工程项目远程仓库中拉取叫(autodeploy-workbei-v8)的分支(暂时是在这个分支上),然后新建一个
.applications-map.json
为名的 JSON 文件,来将自己的应用通过 System.js 进行模块化,以供 single-spa 将应用进行注册,
// 这个文件的东西将会映射到主工程应用的index.HTML中,通过System.js,进行模块化。
// 格式:"项目名称":"项目打包后的js文件地址"
{
"application-index": "http://192.168.3.74:3002/application/application.js",
"commodity-manager": "http://192.168.3.74:3009/commodity-manager/commodity-manager.js"
}
2
3
4
5
6
7
(如上面:使用 System 处理微应用的 js 文件,在本地开发时,请确保从本地服务器或启用了本地 XHR 请求的浏览器运行。如果不是,将会收到一条错误消息。别问为什么会这样,问了就是自己去 Google,Skr...)
在应用首页项目
/src/registerApplication.js
中将要挂载的应用先进行注册在要挂载的应用的
vue.config.js
中进行一些 webpack 的配置
因为在单独开发自己的应用的时候,一些配置是不需要的,而且如果使用了,会报错。所以可以使用环境变量来 控制 webpack 的配置,在项目中的 .env
文件中配置 NEED_SINGLE_SPA='need'。然后在 vue.config.js,进行环境变量注入
// 调整vue-cli中的webpack配置
// 配置define来注入 NEED_SINGLE_SPA 这个环境变量
pluginOptions: {
rishiqing: {
define: {
// 注入 NEED_SINGLE_SPA 这个环境变量
NEED_SINGLE_SPA: process.env.NEED_SINGLE_SPA === 'need',
},
},
},
// css的配置避免生产环境出现css代码分离成一个单独的文件
css: {
// 是否使用css分离插件
extract: false,
// 开启 CSS source maps
sourceMap: false,
// css预设器配置项
loaderOptions: {},
},
configureWebpack: (config) => {
// 项目中如果有用的kite-design 将别名这样配置一下
Object.assign(config.resolve.alias, {
'kite-basic': '@rishiqing/kite-design/dist/kite-basic.js',
'kite-business': '@rishiqing/kite-design/dist/kite-business.js',
'r-request': path.resolve(__dirname, 'src/utils/r-request.js'),
})
// 修改打包入口文件的名字(用你自己应用的名字)下面是以归档为例:
config.entry = {
archive: './src/main.js',
}
// 修改打包出口文件的名字 (这样应用的JS文件就是你的应用名字)
config.output.filename = '[name].js'
if (process.env.NEED_SINGLE_SPA === 'need') {
// 生产环境中不使用代码分割(原因是生产环境使用代码分割,会打出一个第三方包,
// 造成single-spa无法检测到应用的挂载钩子)
config.optimization = {
runtimeChunk: false,
splitChunks: false,
}
// 添加 @rishiqing/webpack-system-register 这个插件 并且如下配置,这样就可以,
// 通过import 的方式在你的项目中获取到主工程的微应用管理中心提供的共享数据了
config.plugins.push(new WebpackSystemRegister({
systemjsDeps: [/^share-data/,],
}))
}
},
// 将原本vue-cli中提供的 HtmlWebpackPlugin 这个插件删除 ,
// 因为你此时的项目是作为一个Dom节点插入到主工程应用页面的Body中,
// 所以自己的项目打包的时候不需要创建html入口文件了。
chainWebpack: (config) => {
if (process.env.NEED_SINGLE_SPA === 'need') {
config.plugins.delete('html')
config.plugins.delete('preload')
config.plugins.delete('prefetch')
}
},
// 解决本地开发时 system.js 在获取该微应用的js文件时会出现跨域的问题
// devServer.before 在服务内部的所有其他中间件之前, 提供执行自定义中间件的功能。
// 这可以用来配置自定义处理程序:
devServer: {
before: function before(app) {
app.use((req, res, next) => {
// 响应头设置 CORS
res.set('Access-Control-Allow-Origin', '*')
next()
})
},
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
- 在应用首页的 **Home.vue **中将要接入的应用的路由跳转配置一下(以档案应用为例)
- 可以使用全局的 single-spa 的方法 singleSpaNavigate('')
- 也可以自己在项目中引入 single-spa 使用他的 api singleSpa.navigateToUrl( )
- 路由的传参方式请使用 params 的格式 :
/app/application/commodity-manager/shopId/${shopId}
``
并且需在项目中使用** vue-router ** 需要将 base 配置成激活你的微应用的路由
注意点:
1.路由的组件的引入请暂时不要用 按需加载的方式 。
2.通过路由传递参数时请注意,由于微应用首页进行传参的时候使用的是 params 的格式,所以此时微应用里面的路由需要使用动态路由的方式接收 path: '/shopId/:shopId'
,在微应用中可以通 this.$route.parmas 来获取参数。
3.如果应用中有返回到应用首页的需求,请使用 singleSpaNavigate('/app/application/') 来进行跳转
- 被挂载的微应用 需要在 vue-cli 中 的 man.js 中 用single-spa的 挂载钩子 来进行挂载。(以档案应用为例)
- 解释
@rishiqing/webpack-system-register
这个 npm 包的作用
此包的作用:(这个插件没有发布的公共的 npm 包管理,而是在我们自己的 npm 包管理中,需要看下这篇笔记链接)
前面说过,主工程的应用页面会提供所有的公共数据,其中就包括所有微应用中要用到的数据(用户信息;组织架构信息...)这些数据都是通过 system.js 提供的,通过这个插件就可以通过 import 将共享的数据在自己的微应用进行引用 :
// 安装方式
// 通过npm下载
npm i @rishiqing/webpack-system-register -D
// 引用方式 (前提是有在vue.config.js文件中进行配置)
import shareData from 'share-data'
shareData中有暴露了两个方法:
shareData.getBasicData( ) 就能获取顶级应用管理中心暴露的共享数据
2
3
4
5
6
7
8
9
- 关于项目中(精灵图,下载模板...)等静态资源,在本地调试的时候获取的问题
由于我们在挂载的时候,只是将项目打包后的 JS 文件通过 System.js 模块化,提供给 single-spa。然而静态资源就需要通过设置 webpack 的 publicPath
属性来获取静态资源地址。但本地开发调试的时候,可以通过 Nginx 进行一下代理,以应用首页为例:
location /application/ { #应用首页的静态资源proxy
add_header 'Access-Control-Allow-Origin' '*';
proxy_pass http://192.168.3.74:3002/application/;
add_header Set-Cookie '$cookie_onlineCookie;'; # 为每个location加入这个,方便调试线上账号
}
2
3
4
5