如何使用 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(),
  ],
  // ...
};
相关推荐
软件工程师文艺1 小时前
使用HTML5 Canvas 实现呼吸粒子球动画效果的原理
前端·javascript·html·html5
不在··2 小时前
Axios HTTP库基础教程:从安装到GET与POST请求的实现
前端·javascript·vue.js
Want5956 小时前
HTML新春烟花
前端·html
2401_897916847 小时前
Android 解析蓝牙广播数据
android·java·前端
罗_三金9 小时前
(4)Vue 3 + Vite + Axios + Pinia + Tailwind CSS搭建一个基础框架
前端·css·vue.js·axios·pinia·tailwind
zqwang88810 小时前
IOS 安全机制拦截 window.open
前端·javascript
Saltwater_leo10 小时前
【无标题】
java·服务器·前端
疯狂小料10 小时前
React 表单处理与网络请求封装详解[特殊字符][特殊字符]
前端·react.js·php
小李飞飞砖11 小时前
kotlin的协程的基础概念
开发语言·前端·kotlin
百事老饼干11 小时前
Vue3实现表格搜索内容高亮
前端·javascript·vue.js