想使用webpack 5的Module Federation功能,所以决定对已有的vue-cli 4.x项目进行升级。
关于Module Federation可在官网具体查看。我想使用的主要是将组件库作为容器。
remote app 中导出某个组件,host app想使用这个组件。这里需要做说明的是host app是个qiankun子应用,线上会被相应的基座项目去调用。
升级步骤:(使用vue-cli去整体升级)
npm install -g @vue/cli@next
# OR
yarn global add @vue/cli@next
vue upgrade --next
命令行会有一系列的提示,根据提示往下执行。升级完成后开始运行运行代码,果然一堆错。
-
Node模块的Polyfill
首先显示的就是
path模块的错误,提示的也很明显,通过查阅webpack5的升级文档,webpack5不再自动添加node 模块的polyfill,如果我们的代码中用到了,需要通过resolve.fallback去手动添加。关于
resolve.fallback可以参考fallback,更新。去做相应的修改。这里我们用到了path,所以修改webpack.config.js如下:module.exports = { // ... resolve: { fallback: { path: require.resolve('path-browserify'), buffer: require.resolve('') } } // ... } -
PreloadPLugin 废弃
vue-cli 5.x不再有
PreloadPLugin插件的配置,去掉这块的配置。 -
不再有
url-loader/raw-loader/file-loader,统一修改为资源模块asset modules。 -
output不再有jsonpFunction,换成chunkLoadingGlobal -
wenpack-dev-server的升级修改可以参考changLog去做相应字段的变化。
disableHostCheck弃用,更换为:allowedHosts: "all";devserver中before废弃,换成onBeforeSetupMiddleware -
还有一个本项目自己的原因,写死了
core-js,html-webpack-plugin的版本号,导致控制台报错,移除这俩包,并重新安装,至此,所有错误都解决了,项目完美运行起来。 -
Module Federation的使用
接着进入我们的正题,我们想要使用
Module Federation的配置,修改webpack.config.js如下// remote app config const { ModuleFederationPlugin } = require('webpack').container module.exports = { // ... configureWebpack(config) { return { resolve: { alias: { '@': resolve('src'), }, fallback: { "path": require.resolve("path-browserify"), "buffer": require.resolve("buffer"), } }, plugins: [ new ModuleFederationPlugin({ // name 全局唯一 name: 'remote_sjms_customer', filename: 'remoteEntry.js', exposes: { // 需要暴露的组件 './customerDialog': './src/views/customer/components/dialog-form.vue' }, }), ] }; }, // ... }运行后,我们访问
https://localhost:9091/remoteEntry.js就可以看到这个文件。接着我们开始了配置
host app的webpack.config.js// host app config const = __publicPath__ = process.env.NODE_ENV === 'development' ? '' : '/xxx/' module.exports = { publicPath: __publicPath__, configureWebpack: (config) => { return { resolve: { alias: { '@': resolve('src'), }, // webpack5 不再自动添加Node 的Polyfill, 手动添加 fallback: { path: require.resolve('path-browserify'), buffer: require.resolve('buffer'), } }, output: { libraryTarget: 'window', library: packageObj.name, // jsonpFunction 废弃,换成这个 chunkLoadingGlobal: `webpackJsonp_${packageObj.name}`, }, plugins: [ new ModuleFederationPlugin({ name: 'host_customer', filename: 'host_customer.js', remotes: { // 和远程remote的name保持一致 'remote_sjms_customer': 'remote_sjms_customer@//localhost.longfor.com:9091/remoteEntry.js', }, }) ] } } } // 应用 import AddCompany from 'remote_sjms_customer/customerDialog'
一切准备就绪,准备使用,当我们单独打开qiankun子应用(host应用)时,控制台出现报错
ScriptExternalLoadError: Loading script failed.
但是Network中可以看到remoteEntry.js的请求。
看报错是找不到对应的script,去看了官网的例子,找到了vue-cli3-demo的demo(本来我是看的vue-cli),后来意识到cli被升级到了5.x,该去看 vue3的demo。看到了对应的配置将splitChunks配置为了false,所以我们修改我们的remote app 的配置
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
// ...
configureWebpack(config) {
return {
// vue-cli5需要增加此配置
optimization: {
splitChunks: false,
},
resolve: {
alias: {
'@': resolve('src'),
},
fallback: {
"path": require.resolve("path-browserify"),
"buffer": require.resolve("buffer"),
}
},
plugins: [
new ModuleFederationPlugin({
// name 全局唯一
name: 'remote_sjms_customer',
filename: 'remoteEntry.js',
exposes: {
// 需要暴露的组件
'./customerDialog': './src/views/customer/components/dialog-form.vue'
},
}),
]
};
},
// ...
重新运行我们的host项目,至此,我们成功的看到了我们需要的远程组件正确的加载进来。走到这里以为万事大吉,但是我们的host app是一个qiankun子应用会被基座调用。
接着我们通过基座去访问的时候,又出现了相同的错:
果然没有那么顺利,有了上次这个错的经验,猜想还是找不到加载的JS 文件,qiankun无非多了沙箱机制,本来想动态去修改__webpack_public_path__ 这个值,但是手动的修改并未生效,后来查阅了各种资料,终于在这篇文章里找到了答案,qiankun的沙箱机制导致找不到remote_sjms_customer 这个模块,需要我们为ModuleFederationPlugin增加library的配置,修改 remote app 的配置如下:
// remote app config
module.exports = {
// ...
configureWebpack(config) {
return {
optimization: {
splitChunks: false,
},
// provide the app's title in webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
name: APP_NAME,
resolve: {
alias: {
'@': resolve('src'),
},
fallback: {
"path": require.resolve("path-browserify"),
"buffer": require.resolve("buffer"),
}
},
plugins: [
new ModuleFederationPlugin({
name: 'remote_sjms_customer',
filename: 'remoteEntry.js',
// 增加library,一定注意type 是 window
library: { type: "window", name: "remote_sjms_customer" },
exposes: {
'./customerDialog': './src/views/customer/components/dialog-form.vue'
},
}),
]
};
},
// ...
}
后来去看了ModuleFederationPlugin的源码,即使我们不写library的参数,也会默认为
{ type: 'var', name: name },但这里我们需要将 remote_sjms_customer 这个模块去暴露到全局,所以type修改为window。
再次通过基座去访问的时候,我们的远程组件也实现了共享,至此搞定~~~