前言
模块联邦也称为ModuleFedaration,是webpack5的新特性,能够很好的解决模块依赖复用问题。 多个独立构建的应用,他们之间的模块互不影响,可以独立开发、独立部署,也可以做为一个独立的子应用。
目前主流的微前端框架有:乾坤、microApp等,当在做技术栈迁移的时候能够很好的解决大量改动的问题,还无关技术栈、样式隔离好处就不多说了,大家可以自行查看微应用相关。但是如果每个应用中有重复的依赖,或者想要复用某个公共的脚本或者某个小组件等不是很友好,毕竟微应用的拆分也不能够过于细,vue的首屏加载什么的都很慢。
?问题:在主应用中要加载有好几个子应用,相同的依赖会被多次请求,这样就很费资源,怎么做到资源共享?
?解决:webpack5的(ModuleFederationPlugin)模块联邦插件就能够做到,但是发现在使用过程中还是有一些坑需要躺,花了几天时间研究了一下
一、实例场景说明
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中的代码原本的拷入
2)、main.js中改为动态引入
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
4、消费方安装moment并且使用了moment,且shared中也配置了moment, 则优先从消费方获取,并且只加载一次
5、消费方安装并且使用了moment,但是shared中未配置moment,则从app1中获取,且加载一次
6、app1/app2/consumer都使用并且安装了moment,获取方式同上,
- 消费方未安装或者消费方安装但未shared则从其他提供方获取
- 消费方安装且shared则就近获取
2、UI组件库的共享-比如tdesign-vue
问题:如果消费方未安装和注册该组件库,会导致控制台报错,且无法正常渲染
解决:消费方也需要安装并且注册,如果消费方也shared可以看到tdesign-vue被独立成bundle
总结:UI组件消费方必须安装
四、不同版本的shared共享
1、JS脚本版本不同
app1-moment@2.30.1
app2-moment@2.29.4
consumer-moment@2.29.4
shared中配置 singleton=true作用:当版本不同时,如果要以高版本为准,此时就算消费方配置了shared也是以高版本为准, 并且此时控制台会给出警告告知你当前从哪里获得的高版本,并且提示需要的低版本号为多少。
其他shared配置可查看官网说明
2、UI库版本不同
shared中配置 singleton=true也会按高版本为准
app1 - "@fzhx/znui": "0.0.94"
app2 - "@fzhx/znui": "1.0.20"
consumer - "@fzhx/znui": "1.0.19"
五、动态设置publicpath
前面的例子中,publicpath都是写死的绝对路径,但是实际项目开发中,肯定不能写死,这样部署时就不够灵活了.
问题: 如果我们部署到指定的路径下,比如app1应用需要部署到 /app1-ui路径下,app2需要部署到/app2-ui,那么消费方会遇到如下问题,请求提供方暴露[expose]的组件,会从自己本地请求,发现找不到就会报错
原因: 这是由于提供方的remoteEntry.js中有一段代码写的是相对路径
解决:提供方提供给一个方法修改publicPath,在消费方使用的时候修改这个值
1、提供新增src/setup-public-path.js
2、配置文件的publicPath改成相对路径比如 /app1-ui
也可以从环境变量中获取配置
publicPath: process.env.VUE_APP_PUBLIC_PATH