静态 Remote 的问题
在webpack高级应用篇(十三):模块联邦(Module Federation)- 未来组件包更新解决方案 最后的结语中有提到这样一句话:
试想一下,你有一个组件包通过npm发布后,你的10个业务项目引用这个组件包。当这个组件包更新了版本,你的10个项目想要使用最新功能就必须一一升级版本、编译打包、部署,这很繁琐。但是模块联邦让组件包利用CDN的方式共享给其他项目,这样一来,当你到组件包更新了,你的10个项目中的组件也自然更新了。是不是很香(*^▽^*)
事实上,我们在享受即时更新,减少繁琐的升级包版本、编译打包、部署的同时,也丢失了组件的版本化。这样会给项目带来风险,试想一下,如果你的一个组件有比较大的改动,当这个组件通过模块联邦暴露出去并被多个项目引用,那么这些项目都是存在风险的。因为你也许并没有进行对应项目线下测试,这些项目通过模块联邦得到的组件就被自动更新了,这很危险。
那么,有没有一个能解决版本化的方案呢?答案是肯定的。
基于 Promise 的动态 Remote
一般来说,remote 是使用 URL 配置的,示例如下:
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app1',
remotes: {
App2: 'app2@http://localhost:3001/App2RemoteEntry.js',
},
}),
],
};
我们可以向 remote
传递一个 promise
,其会在运行时被调用。使用任何符合 get/init
接口的模块来调用这个 promise。例如,如果你想传递你应该使用哪个版本的联邦模块,你可以通过一个查询参数做以下事情:
APP1
配置
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {
ModuleFederationPlugin } = require('webpack').container;
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
clean: true,
},
devtool: false,
devServer: {
port: '3000',
client: {
logging: 'none',
},
},
plugins: [
new HtmlWebpackPlugin(),
new ModuleFederationPlugin({
name: 'app1',
remotes: {
App3: `promise new Promise(resolve => {
// TODO
// 可以在这里设计和实现你的 模块联邦 版本化,这里简单的从URL获取version
const urlParams = new URLSearchParams(window.location.search)
const version = urlParams.get('app2VersionParam')
console.log('version', version);
const remoteUrlWithVersion = 'http://localhost:3001/' + version + '/App2RemoteEntry.js'
console.log('remoteUrlWithVersion', remoteUrlWithVersion);
const script = document.createElement('script')
script.src = remoteUrlWithVersion
script.onload = () => {
// 注入的脚本已经加载并在window上可用
// 我们现在可以解析这个Promise
const proxy = {
get: (request) => {
console.log('request', request);
return window.app2.get(request)
},
init: (arg) => {
try {
console.log('arg', arg);
return window.app2.init(arg)
} catch(e) {
console.log('remote container already initialized')
}
}
}
resolve(proxy)
}
// 将script的src设置为版本化的remoteEntry.js
document.head.appendChild(script);
})
`,
},
}),
],
};
请注意当使用该 API 时,你 必须 resolve 一个包含 get/init API 的对象。
打包后
执行webpack
打包编译后如下,是可以在运行时执行的,因此你可以考虑将获取version的方式改为通过接口获取
APP2
配置
webpack.config.js
在发布前修改 filename
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {
ModuleFederationPlugin } = require('webpack').container;
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
clean: true,
},
devtool: false,
devServer: {
port: '3001',
client: {
logging: 'none',
},
},
plugins: [
new HtmlWebpackPlugin(),
new ModuleFederationPlugin({
// 模块联邦名字,提供给其他模块使用
name: 'app2',
// 提供给外部访问的资源入口
filename: '1.0.0/App2RemoteEntry.js', // 在这里修改版本号
// 引用的外部资源列表
remotes: {
},
// 暴露给外部的资源列表
exposes: {
/**
* ./Header 是让外部应用的使用时基于这个路径拼接引用路径,如:nav/Header
* ./src/Header.js 是当前应用的要暴露给外部的资源模块路径
*/
'./Header': './src/Header.js',
},
// 共享模块,值当前被 exposes 的模块需要使用的共享模块,如lodash
shared: {
},
}),
],
};
打包后
执行webpack
,会得到新的文件
http://localhost:3000/?app2VersionParam=1.0.0
源码:https://gitee.com/yanhuakang/webpack-demos/tree/master/advanced/step_15-Module-Feder-Dynamic-Remote