qiankun 应用 vue-cli 升级 webpack5 & Module Federation采坑记

3,140 阅读4分钟

想使用webpack 5Module 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

image.png 命令行会有一系列的提示,根据提示往下执行。升级完成后开始运行运行代码,果然一堆错。

image.png

  • 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";

    devserverbefore废弃,换成onBeforeSetupMiddleware

  • 还有一个本项目自己的原因,写死了core-jshtml-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 appwebpack.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.

image.png

但是Network中可以看到remoteEntry.js的请求。

image.png

看报错是找不到对应的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子应用会被基座调用。

接着我们通过基座去访问的时候,又出现了相同的错:

image.png

果然没有那么顺利,有了上次这个错的经验,猜想还是找不到加载的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

再次通过基座去访问的时候,我们的远程组件也实现了共享,至此搞定~~~