前言
在现有前端应用日益复杂化的业务场景下,通常一条业务线下多个应用间会有些相同或者有共性的模块,会产生重复的工作量去维护多套代码。如何利用微前端的思路,来实现相同模块的共享,消除重复的工作量呢?让一个应用与其他应用在运行时互相提供或者消费各自的模块,另一个JS应用在进程中动态地去加载其他应用的代码,本文将详细介绍这种思路。
方案选型
1. 抽取公共模块为npm包
以npm包的形式引入的话也存在一些版本问题,每次公共代码的更新都得去所有依赖方应用升级这个版本,发布成本较高,而多应用间通常有些可以共用业务模块还须频繁迭代,鉴于这种版本维护的成本较大,果断舍弃。
2. webpack5中的模块联邦方案
该方案的实施成本极低,无需引入其他依赖,应用webpack5中的ModuleFederationPlugin插件,友好的实现共享模块的抽离,远程加载,在消费方的引入方式除路径规则有些许变化,其他同常规组件的引用方式。
2.1 实施步骤:
(1)将调用和被调用的应用脚手架升级到webpack5,如果是使用的vue-cli,则"@vue/cli-service": "~5.0.0";
(2)在vue.config.js配置文件中引入ModuleFederationPlugin插件 共享模块提供方A的配置代码如下:
const { ModuleFederationPlugin } = require('webpack').container;
plugins: [
new ModuleFederationPlugin({
// 2023.11.24 为在多个应用中共享处理模块,单独提取该模块打包成独立js,供其他应用远程调用
name: 'share_app',
filename: 'shareComponent.js',
// List of exposed modules
exposes: {
'./AddComplain': './src/components/AddComplain.vue',
'./AddProcess': './src/components/AddProcess.vue',
},
// 作为供给方,配置shared
shared: {
vue: {
requiredVersion: '^2.6.14',
eager: false, // 是否将vue模块设置为lazy模式加载
singleton: true,
},
'element-ui': {
requiredVersion: '^2.15.6',
},
},
}),
],
共享模块消费方B的配置代码如下:
const { ModuleFederationPlugin } = require('webpack').container;
plugins: [
new ModuleFederationPlugin({
// 远程拉取组件
name: 'main_app',
remotes: {
share_app: 'share_app@https://XXXX.oa.XXXXX.com/js/shareComponent.js',
},
}),
],
(3)消费方B引入提供方A的共享模块
import AddProcess from 'ticket_app/AddProcess';
除了引用路径和同一个应用引入方式有些许不同,其他使用方式同常规vue组件
2.2 模块联邦方案的优劣
该方案在运行时加载提供方共享的js模块,仅限于相同技术栈的应用间,如果技术栈有差异(比如:提供方Vue3,消费方Vue2),模块联邦方案未提供非常严格的沙箱环境,共享的模块在不同版本的 Vue 应用程序中需要处理兼容性问题,需要进行一些适配和调整。如果Vue大版本一致,兼容性问题的概率要小很多。
但如果你的应用主要关注模块共享和灵活性,技术栈差异不大,Webpack5 的模块联邦方案可能更适合,如果你的应用对安全性和隔离性要求较高,可能需要更强大的沙箱环境。
3. 基于微前端架构的框架加载共享模块
在上一篇# 微前端治理前端应用(一)- 聚合新旧应用详细介绍了使用qiankun框架聚合业务线技术栈混乱的新旧应用来重新整合业务,qiankun提供了非常严格的js沙箱环境,做到了技术栈无关,共享模块可通过这个属性来实现技术栈差异性的问题。
3.1 业务场景
笔者事件的案例场景如下:
有一个处理流程流转的业务模块,功能复杂,内容繁多,在多个应用间总共维护了4套,每期迭代重复工作量巨大,但是应用间框架版本差异大,急需多应用间共享该模块,提供代码复用性和可维护性。为了避免花费成本去处理兼容问题,且尽快推动项目上线,使用qiankun拉取方式实现实现业务代码的共享。
3.2 实施步骤
(1)提供方A共享模块提供方抽离业务模块
在vue.config.js中的配置如下
let pages = {};
if (process.env.VUE_APP_ENV.includes('customer')) {
pages = {
shareModule: {
entry: 'src/shareFiles/shareModule.js',
template: 'publicShareModule/index.html',
filename: 'index.html',
},
};
} else {
pages = {
main: {
entry: 'src/main.js',
template: 'public/index.html',
filename: 'index.html',
},
};
}
module.exports = {
outputDir: process.env.OUTPUT_DIR,
publicPath: process.env.PUBLIC_PATH,
pages,
...
(2)消费方B提供容器,加载共享模块
为保障远程拉取共享模块不影响性能体验,可以提前预加载静态资源,笔者利用了Qiankun框架的prefetchApps接口,通过window.requestIdleCallback() 这个函数实现,在浏览器空闲时间再去进行 prefetch子应用的静态,所以不用担心预加载会影响到首页的渲染性能。
预加载代码如下:
import { prefetchApps } from 'qiankun';
mounted() {
prefetchApps([
{
name: 'ticket',
entry: 'https://workorder.oa.fenqile.com/ticketdrawer/index.html',
},
]);
},
当共享模块需要出现在界面时,通过loadMicroApp去加载然后渲染,代码如下:
import { loadMicroApp } from 'qiankun';
loadTicketDrawer() {
const shareData = {
...
};
const props = { shareData, isShareModuleShow: true, permission: this.permission };
// this.actions = initGlobalState(props);
this.$globalState.onGlobalStateChange((state) => {
// state: 变更后的状态; prev 变更前的状态
...
});
const fetchOptions = {
prefetch: true, // 预加载方式
singular: false,
sandbox: { experimentalStyleIsolation: true }, // 可选,是否开启沙箱,默认为 true。// 从而确保微应用的样式不会对全局造成影响。
// qiankun 通过fetch 请求'oa.fenqile.com/res/static/common 有跨域问题, 所以使用 script 标签加载
excludeAssetFilter: (url) => url.includes('oa.XXXXX.com/res/static/common'),
fetch(url, ...args) {
return window.fetch(url, ...args);
},
};
const microApp = {
name: 'ticket',
container: '#remoteticketDrawer',
entry: 'https://XXX.oa.XXX.com/ticketdrawer/index.html',
// entry: 'https://customerservice.oa.fenqile.com/shareModule/index.html',
props,
};
this.microApp = loadMicroApp(microApp, fetchOptions);
},
这里需要注意的时,已经loadMicroApp,下次再次调用该模块时,需要判断该模块是否在浏览器dom中存在,否则再次多次加载,或者注销了未加载直接使用都会出现异常。
const shareModuleDiv = document.getElementById('__qiankun_microapp_wrapper_for_ticket__');
if (shareModuleDiv) {
this.$globalState.setGlobalState({
isShareModuleShow: true,
data,
});
} else {
this.loadTicketDrawer();
}
总结
基于qiankun框架去做技术栈的隔离,共享业务模块这种方案的实施成本很低,笔者该方案已上线一年多,无js兼容性和用户体验问题。同时将多应用间维护的4套“流程处理“模块,变更为维护1套,有效解决了该模块在各种场景下极易不同步的问题,极大程度了降低了开发维护以及测试成本。