webpack5的模块联邦ModuleFedaration最佳实践

678 阅读4分钟

前言

模块联邦也称为ModuleFedaration,是webpack5的新特性,能够很好的解决模块依赖复用问题。 多个独立构建的应用,他们之间的模块互不影响,可以独立开发、独立部署,也可以做为一个独立的子应用。

目前主流的微前端框架有:乾坤、microApp等,当在做技术栈迁移的时候能够很好的解决大量改动的问题,还无关技术栈、样式隔离好处就不多说了,大家可以自行查看微应用相关。但是如果每个应用中有重复的依赖,或者想要复用某个公共的脚本或者某个小组件等不是很友好,毕竟微应用的拆分也不能够过于细,vue的首屏加载什么的都很慢。

?问题:在主应用中要加载有好几个子应用,相同的依赖会被多次请求,这样就很费资源,怎么做到资源共享?

?解决:webpack5的(ModuleFederationPlugin)模块联邦插件就能够做到,但是发现在使用过程中还是有一些坑需要躺,花了几天时间研究了一下

ModuleFederation官方文档

一、实例场景说明

1、 app1和app2都为提供方

2、 consumer为消费方,消费app1和app2提供的组件

二、模块联邦的使用

1、app1提供方配置

const { defineConfig } = require('@vue/cli-service')
const webpack = require('webpack')
module.exports = defineConfig({
  transpileDependencies: true,
  publicPath: 'http://localhost:9000',
  devServer: {
    port: 9000,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
      'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
    },
  },
  configureWebpack:{
    optimization: { splitChunks: false, minimize:false},
    plugins:[
      new webpack.container.ModuleFederationPlugin({
        name: "app1",
        filename: "remoteEntry.js",
        library: { type: "var", name: "app1" },
        exposes: {
          "./HelloWorld": "./src/components/HelloWorld",
        },
        shared: {
          'vue':{
            singleton: true, 
          },
          'moment':{
            singleton: true,
          }
        },
      }),
    ]
  }
})


2、consumer消费方配置

const { defineConfig } = require('@vue/cli-service');
const webpack = require('webpack')
module.exports = defineConfig({
  transpileDependencies: true,
  publicPath: 'auto',
  configureWebpack: {
    optimization: {
      splitChunks: false,
    },
    plugins: [
      new webpack.container.ModuleFederationPlugin({
        name: 'consumer',
        filename: 'remoteEntry.js',
        remotes: {
          app1: 'app1@http://localhost:9000/remoteEntry.js',
          app2: 'app2@http://localhost:9000/remoteEntry.js',
        },
        shared: {
          'vue':{
            singleton: true, 
          },
          'moment':{
            singleton: true,
          }
        },
      }),
    ],
  },
})

3、消费方和配置方入口文件改为异步【重要】

1)、增加一个文件bootstrap.js【文件名称可自定义】,将main.js中的代码原本的拷入

image.png

2)、main.js中改为动态引入

image.png

4、slitChunks必须改为false

optimization: { splitChunks: false },

5、消费方Host使用提供方Remote暴露的组件

注意需要采用异步的方式引入

<template>
  <div id="app">
    <h1>消费方</h1>
    <SectionT/>
    <HelloWorld/>
  </div>
</template>

<script>
export default {
  components: {
    SectionT: () => import('app2/SectionT'),
    HelloWorld: () => import('app1/HelloWorld'),
  },
}
</script>

三、shared依赖共享

1、纯JS的第三方脚本库共享-比如moment

1、app1应用中安装依赖,并且模块联邦shared moment

2、消费方consumer未安装moment是不能使用该依赖的

3、消费方未安装moment但是引用app1中的组件【并且该组件中使用了moment】该组件正常,并且依赖来自app1

image.png

4、消费方安装moment并且使用了moment,且shared中也配置了moment, 则优先从消费方获取,并且只加载一次

image.png 5、消费方安装并且使用了moment,但是shared中未配置moment,则从app1中获取,且加载一次

image.png 6、app1/app2/consumer都使用并且安装了moment,获取方式同上,

  • 消费方未安装或者消费方安装但未shared则从其他提供方获取
  • 消费方安装且shared则就近获取

image.png

2、UI组件库的共享-比如tdesign-vue

问题:如果消费方未安装和注册该组件库,会导致控制台报错,且无法正常渲染

image.png

解决:消费方也需要安装并且注册,如果消费方也shared可以看到tdesign-vue被独立成bundle

总结:UI组件消费方必须安装

image.png

四、不同版本的shared共享

1、JS脚本版本不同

app1-moment@2.30.1

app2-moment@2.29.4

consumer-moment@2.29.4

shared中配置 singleton=true作用:当版本不同时,如果要以高版本为准,此时就算消费方配置了shared也是以高版本为准, 并且此时控制台会给出警告告知你当前从哪里获得的高版本,并且提示需要的低版本号为多少。 其他shared配置可查看官网说明

image.png

image.png

2、UI库版本不同

shared中配置 singleton=true也会按高版本为准

app1 - "@fzhx/znui": "0.0.94"

app2 - "@fzhx/znui": "1.0.20"

consumer - "@fzhx/znui": "1.0.19"

image.png

五、动态设置publicpath

前面的例子中,publicpath都是写死的绝对路径,但是实际项目开发中,肯定不能写死,这样部署时就不够灵活了.

问题: 如果我们部署到指定的路径下,比如app1应用需要部署到 /app1-ui路径下,app2需要部署到/app2-ui,那么消费方会遇到如下问题,请求提供方暴露[expose]的组件,会从自己本地请求,发现找不到就会报错

image.png

原因: 这是由于提供方的remoteEntry.js中有一段代码写的是相对路径

image.png

解决:提供方提供给一个方法修改publicPath,在消费方使用的时候修改这个值

1、提供新增src/setup-public-path.js

image.png

2、配置文件的publicPath改成相对路径比如 /app1-ui

也可以从环境变量中获取配置

publicPath: process.env.VUE_APP_PUBLIC_PATH

3、暴露public-path文件给消费方

image.png

4、消费方在入口文件引入之前修改路径

image.png

5、如果有多个消费方需要修改路径,可以通过promise.all

image.png