如何使用 Webpack 构建微前端应用?

Module Federation

Module Federation 是这篇文章的主角,它是 Webpack 5 新引入的一种远程模块动态加载、运行技术。MF 允许我们将原本单个巨大应用按我们理想的方式拆分成多个小体积、职责内聚的小应用形式,各个应用能够实现独立部署、独立开发、团队自洽、从而降低系统与团队协作的复杂度------这正是所谓的微前端架构。

MF 的特性有:

  • 应用可按功能性导出若干模块,这些模块会被打包成模块包,功能上像 NPM 模块;
  • 应用在运行时基于 HTTP(S) 协议动态加载其他应用暴露的模块,且用法与动态加载普通 NPM 包一样简单;
  • 与其它微前端方案不同,MF 的应用之间关系平等,没有主应用/子应用之分,每个应用都能导出/导入任意模块。

导入与导出

MF 的最核心配置就是导出与导入,实际上两端都依赖于 ModuleFederationPlugin插件:

  • 导出方
    • 使用ModuleFederationPluginexpose参数声明需要导出的模块列表
    • 使用ModuleFederationPluginfilename参数声明需要导出的模块的入口文件名称
    • 使用devServer启动开发服务器能力
  • 导入方,需要使用ModuleFederationPluginremotes参数声明导入模块的链接
    • 使用ModuleFederationPluginRemoteApp声明远程模块的地址+模块名称
导出方配置 复制代码
const path = require("path");
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  mode: "development",
  devtool: false,
  entry: path.resolve(__dirname, "./src/main.js"),
  output: {
    path: path.resolve(__dirname, "./dist"),
    // 必须指定产物的完整路径,否则使用方无法正确加载产物资源
    publicPath: `http://localhost:8081/dist/`,
  },
  plugins: [
    new ModuleFederationPlugin({
      // MF 应用名称
      name: "app1",
      // MF 模块入口,可以理解为该应用的资源清单
      filename: `remoteEntry.js`,
      // 定义应用导出哪些模块
      exposes: {
        "./utils": "./src/utils",
        "./foo": "./src/foo",
      },
    }),
  ],
  // MF 应用资源提供方必须以 http(s) 形式提供服务
  // 所以这里需要使用 devServer 提供 http(s) server 能力
  devServer: {
    port: 8081,
    hot: true,
  },
};
导入方配置 复制代码
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  mode: "development",
  devtool: false,
  entry: path.resolve(__dirname, "./src/main.js"),
  output: {
    path: path.resolve(__dirname, "./dist"),
  },
  plugins: [
    // 模块使用方也依然使用 ModuleFederationPlugin 插件搭建 MF 环境
    new ModuleFederationPlugin({
      // 使用 remotes 属性声明远程模块列表
      remotes: {
        // 地址需要指向导出方生成的应用入口文件
        RemoteApp: "app1@http://localhost:8081/dist/remoteEntry.js",
      },
    }),
    new HtmlWebpackPlugin(),
  ],
  devServer: {
    port: 8082,
    hot: true,
    open: true,
  },
};
导入方使用位置 复制代码
// app-2/src/main.js
(async () => {
  const { sayHello } = await import("RemoteApp/utils");
  sayHello();
})();

依赖共享

当微服务应用中各个应用共同存在了一部分公共依赖------例如 Vue、React、Lodash 等,我们可以使用ModuleFederationPluginshared配置声明该应用可被共享的依赖模块,从而规避依赖被重复导报,造成产物冗余的问题。

注意,需要在模块导入导出双方都使用shared配置,同时通过requireVersion设置好相同的版本,:

css 复制代码
module.exports = {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      name: "app1",
      filename: `remoteEntry.js`,
      exposes: {
        "./utils": "./src/utils",
        "./foo": "./src/foo",
      }, 
      // 共享依赖及版本要求声明
+     shared: {
+       lodash: {
+         requiredVersion: "^4.17.0",
+       },
+     },
    }),
    }),
  ],
  // ...
};
java 复制代码
module.exports = {
  // ...
  plugins: [
    // 模块使用方也依然使用 ModuleFederationPlugin 插件搭建 MF 环境
    new ModuleFederationPlugin({
      // 使用 remotes 属性声明远程模块列表
      remotes: {
        // 地址需要指向导出方生成的应用入口文件
        RemoteApp: "app1@http://localhost:8081/dist/remoteEntry.js",
      },
      // 共享依赖及版本要求声明
+     shared: {
+       lodash: {
+         requiredVersion: "^4.17.0",
+       },
+     },
    }),
    new HtmlWebpackPlugin(),
  ],
  // ...
};
相关推荐
旧曲重听140 分钟前
最快实现的前端灰度方案
前端·程序人生·状态模式
默默coding的程序猿1 小时前
3.前端和后端参数不一致,后端接不到数据的解决方案
java·前端·spring·ssm·springboot·idea·springcloud
夏梦春蝉1 小时前
ES6从入门到精通:常用知识点
前端·javascript·es6
归于尽1 小时前
useEffect玩转React Hooks生命周期
前端·react.js
G等你下课1 小时前
React useEffect 详解与运用
前端·react.js
我想说一句1 小时前
当饼干遇上代码:一场HTTP与Cookie的奇幻漂流 🍪🌊
前端·javascript
funnycoffee1231 小时前
Huawei 6730 Switch software upgrade example版本升级
java·前端·华为
小鱼小鱼干1 小时前
【Tauri】Tauri中Channel的使用
前端
拾光拾趣录1 小时前
CSS全面指南:从基础布局到高级技巧与实践
前端·css
南屿im1 小时前
基于 Promise 封装 Ajax 请求:从 XMLHttpRequest 到现代化异步处理
前端·javascript