一、不同的方式共享模块
1. NPM 方式共享模块
正常的代码共享需要将依赖作为 Lib 安装到项目,进行 Webpack 打包构建再上线。
如下图,对于项目 Home 与 Search,需要共享一个模块时,最常见的办法就是将其抽成通用依赖,并分别安装在各自项目中。
2. UMD 方式共享模块
真正 Runtime 的方式可能是 UMD 方式共享代码模块,也就是将模块用 Webpack UMD 模式打包,并输出到其他项目中。
如下图,对于项目 Home 与 Search,直接利用 UMD 包复用了一个模块。
3. 微前端方式共享模块
微前端(MFE:micro-frontends)也是最近比较火的模块共享管理方式,微前端就是要解决多项目并存问题,多项目并存的最大问题就是模块共享,不能有冲突。
4. 模块联邦方式共享模块
Webpack5 模块联邦让 Webpack 达到了线上 Runtime 的效果,让代码直接在项目间利用 CDN 直接共享,不再需要本地安装 npm 包、构建再发布了!简单来说,就是可以实现跨应用共享模块。
如下图,这个方案是直接将一个项目的包应用于另一个项目,同时具备整体项目一起打包的公共依赖抽取能力。
二、模块联邦应用案例
案例中,先创建了三个独立的项目,后又用模块联邦的方式将三个项目联系到一起。
关键代码:
nav 项目:
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'nav', // 这个模块联邦的名字,将来其他应用访问这个组件时需要通过这个名字来访问
filename: 'remoteEntry.js', // 向外暴露的资源的名字,供其他应用使用
remotes: {}, // 引用其他应用暴露出来的组件
exposes: { // 暴露一些组件给其他应用使用
'./Header': './src/Header.js'
},
shared: {} // 共享的模块,比如第三方模块 lodash
})
]
}
home 项目:
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'home',
filename: 'remoteEntry.js',
remotes: { // 这里引用了 nav 项目中暴露出来的组件
// 格式:
// xxx: nav项目的模块联邦name @ 项目地址 / nav项目模块联邦向外暴露的资源
nav: 'nav@http://localhost:3003/remoteEntry.js'
},
exposes: {
'./HomeList': './src/HomeList.js'
},
shared: {}
})
]
}
// src/index.js
import HomeList from './HomeList'
// 格式:
// import('webpack配置中的模块联邦的remotes中的xxx / nav项目模块联邦中的exposes中的./Header')
import('nav/Header').then((Header) => { // 通过异步方式导入,因为网络共享或者模块载入是有延迟的
const body = document.createElement('div')
body.appendChild(Header.default())
document.body.appendChild(body)
document.body.innerHTML += HomeList(5)
})
search 项目:
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'search',
filename: 'remoteEntry.js',
remotes: {
nav: 'nav@http://localhost:3003/remoteEntry.js',
home: 'home@http://localhost:3001/remoteEntry.js'
}
})
]
}
// src/index.js
// 引入 nav项目暴露出来的Header组件 和 home项目暴露出来的HomeList组件
Promise.all([import('nav/Header'), import('home/HomeList')])
.then(([
{
default: Header
},
{
default: HomeList
}
]) => {
document.body.appendChild(Header())
document.body.innerHTML += HomeList(3)
})