如何为 Vue 项目配置 `Webpack` 联邦模块(`Module Federation`),实现微前端架构下的代码共享?

各位同学,大家好!我是今天的主讲人,咱们今天来聊聊Vue项目里如何玩转Webpack联邦模块,打造一个灵活又强大的微前端架构。

开场白:微前端,你值得拥有!

想象一下,你负责一个大型电商网站。商品模块、订单模块、用户模块…每个模块都由不同的团队开发和维护。如果把所有代码都塞到一个大仓库里,那代码冲突、构建缓慢、发布风险等等问题会让你抓狂。

这时候,微前端就闪亮登场了!它允许我们将一个大型应用拆分成多个小型、自治的应用,每个应用都可以独立开发、独立部署。而Webpack联邦模块,就是实现微前端的一个利器。

第一部分:联邦模块是什么?能吃吗?

简单来说,联邦模块就是Webpack的一个插件,它允许一个Webpack构建的应用(“宿主”或“主机”)动态地加载另一个Webpack构建的应用(“远程”或“模块”)的部分代码。这样,我们就能在不同的应用之间共享代码,避免重复开发,提高代码复用率。

它就像一个共享的“代码仓库”,各个微应用可以将自己的组件、函数等“贡献”到这个仓库,也可以从仓库里“拿走”自己需要的代码。

第二部分:准备工作:安装和配置Webpack

首先,确保你的Vue项目已经使用了Webpack。如果还没有,你需要安装一些必要的依赖:

npm install webpack webpack-cli webpack-dev-server html-webpack-plugin vue-loader vue-template-compiler css-loader vue-style-loader --save-dev

然后,创建一个webpack.config.js文件,进行基本的Webpack配置。下面是一个简单的示例:

const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /.css$/,
        use: [
          'vue-style-loader',
          'css-loader',
        ],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
  devServer: {
    port: 8080, // 可选,根据需要修改端口号
    hot: true,
  },
};

这个配置做了以下几件事:

  • entry: 指定应用的入口文件。
  • output: 指定打包后的文件输出路径和文件名。
  • module.rules: 配置各种loader,用于处理不同类型的文件,例如.vue文件、.css文件等。
  • plugins: 使用插件,例如VueLoaderPlugin用于处理Vue组件,HtmlWebpackPlugin用于生成HTML文件。
  • devServer: 配置开发服务器,方便我们进行开发和调试。

第三部分:配置联邦模块:宿主应用(Host)

现在,我们开始配置宿主应用。假设我们的宿主应用是一个名为host-app的项目。

首先,安装@module-federation/webpack-plugin插件:

npm install @module-federation/webpack-plugin --save-dev

然后,修改webpack.config.js文件,添加联邦模块的配置:

const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  mode: 'development',
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /.css$/,
        use: [
          'vue-style-loader',
          'css-loader',
        ],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
    new ModuleFederationPlugin({
      name: 'host_app', // 必须唯一,作为模块的ID
      remotes: {
        'remote_app': 'remote_app@http://localhost:8081/remoteEntry.js', // 远程模块的名称和地址
      },
      shared: ['vue'], // 共享的依赖项,避免重复加载
    }),
  ],
  devServer: {
    port: 8080,
    hot: true,
  },
};
  • name: 宿主应用的名称,必须是唯一的,就像你的身份证号码一样。
  • remotes: 指定远程模块的信息。remote_app是远程模块的名称,remote_app@http://localhost:8081/remoteEntry.js是远程模块的入口文件地址。 remote_app@ 前面的 remote_app 是远程模块的 name (在远程模块的 webpack 配置中设置)。后面的URL是远程模块暴露出的 remoteEntry.js 文件的地址。
  • shared: 指定共享的依赖项。如果宿主应用和远程模块都使用了Vue,那么我们可以将Vue设置为共享依赖,避免重复加载,提高性能。

第四部分:配置联邦模块:远程模块(Remote)

接下来,我们配置远程模块。假设我们的远程模块是一个名为remote-app的项目,它提供了一个名为RemoteComponent的Vue组件。

首先,同样需要安装@module-federation/webpack-plugin插件:

npm install @module-federation/webpack-plugin --save-dev

然后,修改webpack.config.js文件,添加联邦模块的配置:

const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  mode: 'development',
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    publicPath: 'auto', // 非常重要!
  },
  module: {
    rules: [
      {
        test: /.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /.css$/,
        use: [
          'vue-style-loader',
          'css-loader',
        ],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
    new ModuleFederationPlugin({
      name: 'remote_app', // 必须唯一,作为模块的ID
      exposes: {
        './RemoteComponent': './src/components/RemoteComponent.vue', // 暴露的模块
      },
      shared: ['vue'], // 共享的依赖项,避免重复加载
    }),
  ],
  devServer: {
    port: 8081,
    hot: true,
  },
};
  • name: 远程模块的名称,必须是唯一的。
  • exposes: 指定要暴露的模块。./RemoteComponent是宿主应用中使用的模块名称,./src/components/RemoteComponent.vue是远程模块中实际的组件路径。
  • shared: 指定共享的依赖项。
  • publicPath: 这个属性非常重要!必须设置为 auto,这样Webpack才能正确地找到远程模块的资源。

第五部分:使用远程模块:在宿主应用中使用RemoteComponent

现在,我们可以在宿主应用中使用远程模块提供的RemoteComponent了。

首先,在宿主应用的src/components目录下创建一个RemoteWrapper.vue组件:

<template>
  <div>
    <h2>Remote Component:</h2>
    <RemoteComponent />
  </div>
</template>

<script>
import { defineAsyncComponent } from 'vue';

export default {
  components: {
    RemoteComponent: defineAsyncComponent(() => import('remote_app/RemoteComponent')),
  },
};
</script>
  • defineAsyncComponent: 使用defineAsyncComponent来异步加载远程组件。'remote_app/RemoteComponent'是远程组件的名称,它对应于远程模块webpack.config.jsexposes配置的./RemoteComponent

然后,在宿主应用的src/App.vue组件中使用RemoteWrapper组件:

<template>
  <div id="app">
    <h1>Host App</h1>
    <RemoteWrapper />
  </div>
</template>

<script>
import RemoteWrapper from './components/RemoteWrapper.vue';

export default {
  components: {
    RemoteWrapper,
  },
};
</script>

第六部分:启动应用:验证联邦模块是否生效

现在,我们可以分别启动宿主应用和远程模块了。

首先,启动远程模块:

cd remote-app
npm run serve  # 或者 yarn serve,取决于你使用的包管理器

然后,启动宿主应用:

cd host-app
npm run serve  # 或者 yarn serve

如果一切顺利,你应该能在宿主应用的页面上看到远程模块提供的RemoteComponent组件!

第七部分:常见问题及解决方案

在使用联邦模块的过程中,你可能会遇到一些问题。下面是一些常见问题及解决方案:

问题 解决方案
远程模块加载失败 1. 检查远程模块的webpack.config.js中的publicPath是否设置为auto。 2. 检查宿主应用的webpack.config.js中的remotes配置是否正确,包括远程模块的名称和地址。 3. 检查远程模块是否已经启动。 4. 检查网络连接是否正常。
共享依赖项冲突 1. 确保宿主应用和远程模块使用的共享依赖项的版本一致。 2. 尝试使用eager: true选项来强制加载共享依赖项。
类型定义问题(TypeScript) 1. 在宿主应用中声明远程模块的类型定义。例如,创建一个typings/remote-app.d.ts文件,添加以下内容: typescript declare module 'remote_app/RemoteComponent' { import { DefineComponent } from 'vue'; const RemoteComponent: DefineComponent; export default RemoteComponent; } 2. 在tsconfig.json文件中包含类型定义文件。
远程模块样式丢失 1. 确保远程模块的样式文件已经正确地打包到remoteEntry.js中。 2. 尝试使用style-loadercss-loader来处理样式文件。
远程模块路由问题 1. 使用vue-routercreateWebHistorycreateWebHashHistory来创建路由实例。 2. 确保宿主应用和远程模块的路由配置不会冲突。 3. 考虑使用history模式,并配置服务器端路由来处理路由请求。
多个远程模块共享同一个组件库的不同版本 1. 避免这种情况发生。尽量保持所有模块使用的组件库版本一致。 2. 如果必须使用不同的版本,可以考虑使用别名来区分不同的版本。

第八部分:高级技巧:动态加载远程模块

除了静态配置远程模块,我们还可以动态地加载远程模块。这可以让我们根据需要加载不同的模块,提高应用的灵活性。

例如,我们可以根据用户的角色动态加载不同的模块。

首先,创建一个函数来加载远程模块:

async function loadRemoteModule(remoteName, modulePath) {
  return new Promise((resolve, reject) => {
    const elementId = `remote-module-${remoteName}-${modulePath.replace(/[^a-zA-Z0-9]/g, '_')}`;
    if (document.getElementById(elementId)) {
      resolve(window[remoteName].get(modulePath).then((factory) => factory()));
      return;
    }

    const script = document.createElement('script');
    script.id = elementId;
    script.src = `http://localhost:8081/remoteEntry.js`; // 替换为你的远程模块地址
    script.onerror = reject;

    script.onload = () => {
      script.onload = null;
      const module = window[remoteName].get(modulePath).then((factory) => factory());
      resolve(module);
    };

    document.head.appendChild(script);
  });
}

然后,在Vue组件中使用这个函数来加载远程模块:

<template>
  <div>
    <component :is="remoteComponent" />
  </div>
</template>

<script>
import { ref, onMounted } from 'vue';

export default {
  setup() {
    const remoteComponent = ref(null);

    onMounted(async () => {
      try {
        const component = await loadRemoteModule('remote_app', './RemoteComponent');
        remoteComponent.value = component.default; // 注意:这里需要使用 component.default
      } catch (error) {
        console.error('Failed to load remote module:', error);
      }
    });

    return {
      remoteComponent,
    };
  },
};
</script>

第九部分:总结与展望

Webpack联邦模块是一个强大的工具,它可以帮助我们构建灵活的微前端架构,实现代码共享,提高开发效率。

当然,联邦模块也有一些缺点,例如配置复杂、调试困难等。但是,随着Webpack的不断发展,相信这些问题会得到解决。

未来,微前端架构将会越来越流行,联邦模块也将会发挥更大的作用。希望通过今天的讲解,大家能够对联邦模块有一个更深入的了解,并在实际项目中灵活运用。

今天的讲座就到这里,谢谢大家!如果有什么问题,欢迎提问。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注