前言
工作中,常常会有这样的需求,项目A与项目B是两个完全独立的项目工程,项目A需要在项目B中运行,例如,网页中的在线客服,或者其中一个项目的部分代码是可复用的,或者是可以继承的,那webpack 5中的模块联邦Module Federation就可以帮助我们达到这样的目的~
什么是 Module Federation?
官网上首先引出了“容器”的概念,那什么是容器呢,容器就是独立部署的应用,每个应用都可以是看作是提供远程加载模块的容器入口。模块加载分为本地模块与远程模块加载,本地模块是当前构建的一部分,远程模块不属于当前构建,在运行时从容器中加载。
容器之间是可以互相嵌套的,容器可以使用来自其他容器的模块。容器之间也可以循环依赖
从这句话可以理解,容器不单单是两个项目之间的依赖,其实是可以看作有一个“容器群”,容器之间可以共享资源、共享依赖。
项目实践
在我们的项目实践中,使用lerna管理多包应用,packages目录下的每个应用互相独立,子应用的根目录下都有webpack.config.js配置。
那让我们来整体看一下,如何配置Module Federation?
// webpack.config.js文件
const {ModuleFederationPlugin} = require("webpack").container;
const name = 'GCPWebFrameworkBill'
// mode是自定义的环境变量
const { version } = require('./package.json')
const port = 9091
const cdnBaseUrl = 'https://cdn.developer.xxxx.com'
const host = mode === 'development' ? `http://localhost:${port}` : cdnBaseUrl
const libVersion = mode === 'development' ? 'latest' : version
const publicPath = `${host}/gcp/web-framework-bill/${libVersion}/`
module.exports = {
entry: "./src/index",
mode: "development",
devServer: {
static: path.join(__dirname, "dist"),
port,
},
output: {
publicPath,
},
plugins: [
new ModuleFederationPlugin({
name, // 容器名称-这里是'GCPWebFrameworkBill'
exposes: {
'./startPage': './lib/app/startPage.js',
'./BaseWebPage': './lib/app/baseWebPage.js',
'./cores/CommonPage': './lib/cores/page.js',
'./cores/CommonModule': './lib/cores/module.js'
},
remotes: { // 远程容器
@gcbp/web-framework: `GCBPWebFramework@${cdnBaseUrl}/gcp/web-framework/2.1.0/gcp.js`,
},
shared: { // 共享组件库
vue: `Vue@${cdnBaseUrl}/vendors/vue/2.6.14/vue.min.js`,
vuex: `Vuex@${cdnBaseUrl}/vendors/vuex/3.6.2/vuex.min.js`,
'@geip/basic-components': [
`GCPDesignPro@${cdnBaseUrl}/gcp/gcp-design-pro/latest/index.js`,
`${cdnBaseUrl}/gcp/gcp-design-pro/latest/theme-default/index.css`
],
'@gcbp/gcp-forms': [
`GCPForms@${cdnBaseUrl}/gcp/gcp-forms/latest/umd/index.js`,
`${cdnBaseUrl}/gcp/gcp-forms/latest/umd/index.css`
]
},
})
],
};
上面的配置说明:
- 插件
ModuleFederationPlugin接收一个对象,里面需要关注的是name、exposes、shared、remotes属性配置,name是独一无二的容器名称,exposes是对外暴露的资源文件,shared是共享组件库、框架库,remotes是引用的远程仓库。 exposes接收一组资源映射,注意写法,官方强调是'./startPage': './lib/app/startPage.js',如果写成'/startPage',会报这样的错Uncaught Error: Module "./startPage" does not exist in container。这里提供官方的故障排除指南。remotes配置需要依赖的远程仓库,项目部署到cdn上,注意publicPath,相应的,资源更新后会重新在Jenkins上部署最新版本。shared配置共享组件库,不同于npm包,这些资源库不用放在package.json文件里,也就不会下载到本地目录的node_modules,它们会在运行时,会被优先下载依赖。
那在项目中使用import()异步加载暴露出来的模块
// await放在页面顶层,会等待异步资源加载完成,代码才会继续执行
const { default: BasePage } = await import('@gcbp/web-framework/BasePage')
总结
本文偏向对Module Federation的落地实践,对运行时本地模块资源与远程模块加载资源的顺序等、以及工作原理没有太多篇幅。可以参考其他博主的深入分析如 Shenfq的Webpack5 跨应用代码共享 - Module Federation