微前端治理前端应用(二)- 多应用间远程共享模块

366 阅读5分钟

前言

在现有前端应用日益复杂化的业务场景下,通常一条业务线下多个应用间会有些相同或者有共性的模块,会产生重复的工作量去维护多套代码。如何利用微前端的思路,来实现相同模块的共享,消除重复的工作量呢?让一个应用与其他应用在运行时互相提供或者消费各自的模块,另一个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套,有效解决了该模块在各种场景下极易不同步的问题,极大程度了降低了开发维护以及测试成本。