如何使用 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(),
  ],
  // ...
};
相关推荐
徐小夕6 分钟前
花了一周,3亿tokens,我开源了一款 Word 文档智能审查平台,文稿自动质检+可视化分析,告别低效人工审核
前端·算法·github
kyriewen1 小时前
别再乱用useEffect了——你写的10个里有8个不该存在
前端·javascript·react.js
Ivanqhz2 小时前
Rust &‘static str浅析
java·前端·javascript·rust
IT_陈寒2 小时前
SpringBoot这个分页坑,我踩了三天才爬出来
前端·人工智能·后端
颜酱2 小时前
05 | 召回前置准备:根据业务数据库生成各数据库(读取配置阶段)
前端·人工智能·后端
zandy10113 小时前
衡石 Agentic BI的ReAct 推理框架在 Agentic BI 中的工程化实践
前端·javascript·react.js
罗超驿3 小时前
JavaEE进阶之路:从Web架构原理到HTML标签全解析
前端·html·web·javaee
西安小哥3 小时前
从前端到AI工程师:一场跨越鸿沟的真实蜕变之旅
前端
Prince4183 小时前
侧边栏收起缩放适配方案
前端
用户7783366132113 小时前
serpbase + Cloudflare R2 边缘持久化实战
前端·人工智能