# 微前端演进(二)
2020年11月,进阶改造
- 微前端对我们当前的业务的实际价值是什么?
- 微前端从构建到运行,过程中还有哪些可以优化的点?
上文微前端演进(一)中,讲解了微前端第一阶段落地的演进,验证了微前端的可行性。微前端也在产品的开发中稳定运行。不过在业务的实践过程中,我们也发现了许多可以优化的点。
# 对微前端的思考
微前端对我们实际产生的价值是什么?
为什么会有这样的疑问,因为微前端的实际应用场景更适合中后台系统。中后台系统的子系统特别多且系统独立,能够最大限度发挥微前端应用隔离和独立发布的优势,但是对于有格来说,我们发现有格是一个业务形态统一的产品,应用之间并不是完全独立,有格的需求是模块之间高内聚低耦合。
有格的子应用更接近于子模块,模块之间保持一定的边界,但又开放一定程度的跨模块沟通的能力;代码在业务上保证独立,但是在底层上保证统一且复用。于是我们开始对qiankun框架进行更深入的研究。经过研究讨论之后,我们明确了对微前端的一些优化方向。
首先,HTML Entry会首先解析html内容,再拉取资源,在请求上会浪费一次请求的时间。这样我们对有格子应用的定位,从“应用”转化为了“资源”,类似于webpack的异步chunk。所以,优化方法就是提前获取资源路径,在浏览器运行时减少一次请求的时间,从而提升子应用的加载速度。
其次,是对微前端子应用的代码复用方案,一些公共库可以最大程度的复用。
# 子应用信息的采集
上文提到的减少html请求时间, 直接加载资源,就是通过qiankun的api,entry字段支持以{ scripts?: string[]; styles?: string[]; html?: string }
的格式配置。
于是子应用的配置就变成了类似这样的形式:
{
name: 'foo',
// 旧的格式
// entry: '//localhost:8080',
entry: {
scripts: [
'//localhost:8080/foo/vendor.js',
'//localhost:8080/foo/app.js',
],
styles: [
'//localhost:8080/foo/vendor.css',
'//localhost:8080/foo/app.css',
],
},
container: '#yourContainer',
activeRule: '/foo',
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 通过webpack-manifest-plugin采集子应用构建信息
那么如何获取上文提到的scritps 和 styles信息呢?
最合适的解决办法是在构建时获取,使用webpack-manifest-plugin在构建完成后生成一个manifest.json
文件。
子应用经过webpack-manifest-plugin
构建之后生成的subapp_manifest.json
文件格式如下:
{
"app.css": "/tableview/v966577ec-2103310646-1384/css/app.5ba0472c.css",
"app.js": "/tableview/v966577ec-2103310646-1384/js/app.b6ffbbe2.js",
"vendors.css": "/tableview/v966577ec-2103310646-1384/css/vendors.e62a84ef.css",
"vendors.js": "/tableview/v966577ec-2103310646-1384/js/vendors.1e9fedd2.js",
"index.html": "/tableview/index.html"
}
2
3
4
5
6
7
在构建时,将生成的dist
目录下的资源和subapp_manifest.json
文件,上传到OSS资源服务器中,以版本号作为标识。
请看jenkins的构建执行脚本:
再结合s2i中的镜像构建脚本一起看:
TODO
此处可优化,将oss上传的脚本命令从镜像中抽离出来。理论上,子应用构建可以只生产资源,不生产镜像。
# nodejs中间层
nodejs中间层,是指在网关和浏览器资源之间添加的一层服务端,就是我们用来实现服务端渲染的中间层。
我们可以在这一层来实现上述的优化。
参考这篇文章了解有格的服务端渲染。在nodejs启动时,会根据子应用列表,去对应的子应用服务中采集subapp_manifest.json
文件信息。
获取到manifest之后,再通过服务端渲染挂载到window上。可以通过window.__H3_SUBAPPS_MANIFEST__
来获取。
# 聚合服务
微前端将产品划分为了多个子应用,如果每个子应用在发版时都独立发版的话,就一定需要考虑服务之间的依赖问题和版本兼容问题,这样会导致前端的发版会非常复杂。
聚合发版系统就是解决这样的问题,保证前端可以一次性发版,而不需要考虑发版顺序;保证子应用的独立和解耦,又兼顾整个产品的完整性。
# 公共依赖的抽离
得力于我们对HTMLEntry的改造,我们对子应用的entry配置将更灵活。我们甚至可以在代码中自定义加载的脚本。我们可以通过这个能力来对沙箱做修改。
JS 沙箱的相关知识了解:解密qiankun沙箱 - CSDN
首先是对第三方的依赖的抽离,将第三方依赖包打包好放到资源服务器中。
其次是在子应用的初始化脚本中,引入'<script>window[Symbol.for("_SANDBOX_INJECT_")](window);</script>',
这样一段脚本可以修改沙箱。
{
name: 'foo',
entry: {
scripts: [
'<script>window[Symbol.for("_SANDBOX_INJECT_")](window);</script>', // 沙箱初始化脚本
'//assets.devoa.h3yun.net/common-library/0.1.0/vue-bundle/v1.0.0/vue-bundle.js', // 第三方静态资源
'//localhost:8080/foo/app.js',
],
styles: [
'//localhost:8080/foo/app.css',
],
},
container: '#yourContainer',
activeRule: '/foo',
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
提前在主应用中,声明好沙箱的初始化函数。可以将一些库,从主框架获取到,注入到沙箱中。之后,只需要在子应用中配置webpack的external属性即可。如下图:
export default () => {
const _SANDBOX_INJECT_ = Symbol.for('_SANDBOX_INJECT_');
window[_SANDBOX_INJECT_] = function (global: any) {
try {
// 从主框架中复用的第三方库
global._H3Icons_ = H3Icons;
global._H3IconsBiz_ = H3IconsBiz;
global._H3YunOpensdk_ = H3YunOpensdk;
global._H3YunRequest_ = H3YunRequest;
global._H3YunGlobalProvider_ = H3YunGlobalProvider;
global._UmiRequest_ = UmiRequest;
global._Vuedraggable_ = Vuedraggable;
// global.Vue = Vue;
// global.Vuex = Vuex;
// global.VueRouter = VueRouter;
// global.VuePropertyDecorator = VuePropertyDecorator;
// global.VueCompositionAPI = VueCompositionAPI;
// const extendedVue = Vue.extend();
// extendedVue.version = Vue.version;
// extendedVue.config = Vue.config;
// (extendedVue as any).util = (Vue as any).util;
// extendedVue.observable = Vue.observable;
// extendedVue.set = Vue.set;
// (extendedVue as any).__SUB_VUE__ = true;
// global.Vue = extendedVue;
// global.VuePropertyDecorator = Object.assign({}, VuePropertyDecorator, {
// Vue: extendedVue,
// });
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
};
};
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
在子应用的webpack配置中, 使用externals将依赖排除:
{
externals: {
'@h3-icons/basic': '_H3Icons_',
'@h3-icons/basic-biz': '_H3IconsBiz_',
'@h3yun/opensdk': '_H3YunOpensdk_',
'@h3yun/request': '_H3YunRequest_',
'@h3yun/global-provider': '_H3YunGlobalProvider_',
'umi-request': '_UmiRequest_',
lodash: '_',
moment: 'moment',
'@h3/antd-vue': 'antd',
'@ant-design/icons/lib/dist': 'AntDesignIcons',
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
'vue-class-component': 'VueClassComponent',
'vue-property-decorator': 'VuePropertyDecorator',
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19