Webpack 5模块联邦微前端架构搭建与共享依赖配置实战

模块联邦核心概念与工作原理

模块联邦(Module Federation)是Webpack 5引入的微前端架构能力,允许在运行时从远程加载另一个Webpack构建产物中的模块,实现多个独立构建之间的代码共享。传统微前端方案如iframe、single-spa存在样式隔离、通信复杂、部署耦合等问题,模块联邦通过Webpack构建时插件机制,在编译期生成远程模块的运行时加载器,实现了真正的动态模块加载和依赖共享。模块联邦涉及两个角色:Host(消费方,引用远程模块的应用)和Remote(提供方,暴露模块的应用)。一个应用可同时作为Host和Remote,实现双向共享。

Remote端暴露模块配置

作为模块提供方,需在webpack.config.js中配置ModuleFederationPlugin,声明暴露的模块和共享依赖:

// remote-app/webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;

module.exports = {
  output: {
    publicPath: "http://localhost:3001/",
    uniqueName: "remoteApp"
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "remoteApp",
      filename: "remoteEntry.js",
      exposes: {
        "./Button": "./src/components/Button",
        "./Dashboard": "./src/pages/Dashboard",
        "./utils": "./src/utils/shared"
      },
      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
        "react-router-dom": { singleton: true, requiredVersion: deps["react-router-dom"] }
      }
    })
  ]
};

配置说明:name是Remote应用在运行时的全局变量标识,filename是远程入口文件名,exposes声明对外暴露的模块路径映射,shared配置共享依赖及其版本要求。singleton: true确保React等单例库只加载一份实例,避免Hooks失效等问题。

Host端加载远程模块配置

Host应用通过ModuleFederationPlugin声明要消费的Remote应用和共享依赖:

// host-app/webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "hostApp",
      remotes: {
        remoteApp: "remoteApp@http://localhost:3001/remoteEntry.js",
        analyticsApp: "analyticsApp@http://localhost:3002/remoteEntry.js"
      },
      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        "react-dom": { singleton: true, requiredVersion: deps["react-dom"] }
      }
    })
  ]
};

在Host应用的React组件中,使用动态import加载远程模块:

// host-app/src/App.tsx
import React, { Suspense, lazy } from "react";

const RemoteButton = lazy(() => import("remoteApp/Button"));
const RemoteDashboard = lazy(() => import("remoteApp/Dashboard"));

function App() {
  return (
    <div>
      <h1>Host Application</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <RemoteButton onClick={() => console.log("clicked")} />
      </Suspense>
      <Suspense fallback={<div>Loading Dashboard...</div>}>
        <RemoteDashboard />
      </Suspense>
    </div>
  );
}

export default App;

React.lazy配合Suspense实现远程模块的懒加载,首次访问时才加载远程代码块,优化首屏性能。

共享依赖版本协调机制

共享依赖是模块联邦的关键特性,多个应用共享同一份依赖可减少重复加载和实例冲突。Webpack在运行时根据shared配置的requiredVersion协商使用哪个版本:若Host和Remote提供的版本都满足要求,优先使用Host的版本;若不满足则使用Remote自带的版本。singleton配置的依赖强制全局唯一实例。共享依赖初始化是异步的,Webpack 5.3+通过eager: true可改为同步初始化,但会增加首屏加载时间。

跨应用通信与状态共享方案

模块联邦本身不提供跨应用通信机制,需借助外部方案实现。常见做法包括:通过window对象挂载全局事件总线;使用共享的Redux Store(通过shared配置暴露store实例);基于CustomEvent的发布订阅模式。以下是一个基于事件总线的通信示例:

// shared/eventBus.ts(在shared配置中暴露)
type Handler = (payload?: any) => void;
const handlers = new Map<string, Set<Handler>>();

export const eventBus = {
  on(event: string, handler: Handler) {
    if (!handlers.has(event)) handlers.set(event, new Set());
    handlers.get(event)!.add(handler);
  },
  emit(event: string, payload?: any) {
    handlers.get(event)?.forEach(h => h(payload));
  },
  off(event: string, handler: Handler) {
    handlers.get(event)?.delete(handler);
  }
};

Host和Remote通过import(“remoteApp/eventBus”)获取同一实例,实现双向通信。

部署架构与生产环境注意事项

模块联邦的生产部署需注意:Remote应用的remoteEntry.js必须通过CDN或稳定URL提供,Host通过该URL加载远程模块;共享依赖的版本兼容性需提前规划,建议各应用使用相同的major版本;远程模块加载失败需有降级方案,通过Error Boundary捕获加载异常;生产环境建议启用Module Federation的TypeScript类型支持,使用@module-federation/typescript插件生成远程模块的.d.ts类型声明,保证开发体验。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/webpack5-mo-kuai-lian-bang-wei-qian-duan-jia-gou-da-jian-yu/

(0)
小编小编
上一篇 1小时前
下一篇 1小时前

相关推荐

Webpack 5模块联邦微前端架构搭建与共享依赖配置

Webpack 5引入的模块联邦(Module Federation)允许独立构建的应用在运行时共享模块,实现微前端架构的动态加载。各子应用可将组件、工具函数暴露为远程模块,其他应用无需本地安装即可直接引用。本文讲解模块联邦的配置方法和工程实践。

模块联邦核心概念与Host/Remote关系

模块联邦涉及两个角色:Host(消费方)和Remote(提供方)。一个应用可同时作为Host和Remote。Remote通过exposes字段暴露模块,Host通过remotes字段引用远程模块。运行时通过全局变量通信,不依赖构建时的代码合并。

典型微前端场景:一个主应用(Host)集成多个独立部署的子应用(Remote),子应用独立开发、构建和部署,运行时由主应用动态加载。

Remote端配置:暴露模块

子应用通过ModuleFederationPlugin暴露自身组件。以React组件库为例:

// webpack.config.js (remote应用)
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;

module.exports = {
  mode: "development",
  devServer: {
    port: 3001,
    headers: { "Access-Control-Allow-Origin": "*" },
  },
  output: { publicPath: "http://localhost:3001/" },
  plugins: [
    new ModuleFederationPlugin({
      name: "remoteApp",
      filename: "remoteEntry.js",
      exposes: {
        "./Button": "./src/components/Button",
        "./Dashboard": "./src/pages/Dashboard",
        "./utils": "./src/utils/shared",
      },
      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
      },
    }),
  ],
};

关键配置说明:name为远程应用标识,filename为入口文件名,exposes定义暴露的模块映射。shared配置共享依赖,singleton:true确保全局只加载一份React实例,避免React Hooks在多实例下失效。

暴露的组件本身不需要特殊处理,按正常方式编写:

// src/components/Button.jsx
import React from "react";

const Button = ({ label, onClick, type = "primary" }) => {
  return (
    <button className={type} onClick={onClick}>{label}</button>
  );
};

export default Button;

Host端配置:加载远程模块

主应用通过remotes字段引用远程模块入口:

// webpack.config.js (host应用)
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;

module.exports = {
  mode: "development",
  devServer: { port: 3000 },
  plugins: [
    new ModuleFederationPlugin({
      name: "hostApp",
      remotes: {
        remoteApp: "remoteApp@http://localhost:3001/remoteEntry.js",
      },
      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
      },
    }),
  ],
};

remotes配置格式为远程名称@远程入口URL。Webpack运行时会动态加载remoteEntry.js并初始化容器。

异步加载远程组件

React应用中使用React.lazy加载远程组件,配合Suspense处理加载状态:

// src/App.jsx
import React, { Suspense } from "react";

const RemoteButton = React.lazy(() => import("remoteApp/Button"));
const RemoteDashboard = React.lazy(() => import("remoteApp/Dashboard"));

const Loading = () => <div>Loading...</div>;

function App() {
  return (
    <div>
      <h1>Host Application</h1>
      <Suspense fallback={<Loading />}>
        <RemoteButton label="Click Me" onClick={() => console.log("clicked")} />
      </Suspense>
      <Suspense fallback={<Loading />}>
        <RemoteDashboard />
      </Suspense>
    </div>
  );
}

export default App;

Host应用的入口文件需要改为异步加载。Webpack要求联邦应用使用动态入口,确保共享依赖在应用代码执行前完成初始化:

// src/index.jsx (入口文件)
import React from "react";
import { createRoot } from "react-dom/client";

const root = createRoot(document.getElementById("root"));

import("./App").then(({ default: App }) => {
  root.render(<App />);
});

共享依赖版本协商机制

shared配置控制多应用间共享依赖的加载策略。当Host和Remote都声明了react为shared依赖时,Webpack按以下规则协商:

shared: {
  react: {
    singleton: true,
    requiredVersion: "^18.2.0",
    eager: false,
    import: false,
  },
  lodash: {
    singleton: false,
    requiredVersion: "^4.17.0",
  },
}

版本协商流程:Host初始化时注册可用依赖版本,Remote加载时查询是否已有满足requiredVersion的实例。若版本满足则复用,否则加载Remote自带的版本。singleton:true强制只使用第一个加载的实例。

生产环境部署与publicPath配置

生产环境中远程模块入口URL需要指向CDN或静态资源服务器。动态publicPath支持多环境部署:

// webpack.config.js
module.exports = {
  output: { publicPath: "auto" },
  plugins: [
    new ModuleFederationPlugin({
      name: "remoteApp",
      filename: "remoteEntry.js",
      exposes: { "./Dashboard": "./src/pages/Dashboard" },
      shared: ["react", "react-dom"],
    }),
  ],
};

publicPath:auto让Webpack根据remoteEntry.js的加载URL自动推断资源路径,无需硬编码域名。部署时将各子应用的remoteEntry.js上传到对应CDN路径即可。

Host端remotes配置使用运行时变量动态指定远程地址,支持不同环境切换:

const REMOTE_URL = process.env.REMOTE_APP_URL || "http://localhost:3001";

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "hostApp",
      remotes: { remoteApp: `remoteApp@${REMOTE_URL}/remoteEntry.js` },
    }),
  ],
};

跨应用通信方案

模块联邦不提供内置通信机制。常见方案是通过共享状态库或自定义EventBus:

// 共享utils模块(Remote暴露)
import { create } from "zustand";

const useSharedStore = create((set) => ({
  user: null,
  setUser: (user) => set({ user }),
  theme: "light",
  toggleTheme: () => set((s) => ({ theme: s.theme === "light" ? "dark" : "light" })),
}));

export { useSharedStore };

Host和Remote都引用同一个useSharedStore实例,实现跨应用状态共享。需确保zustand也在shared配置中声明为singleton:

shared: {
  react: { singleton: true },
  "react-dom": { singleton: true },
  zustand: { singleton: true, requiredVersion: "^4.5.0" },
}

模块联邦通过运行时动态加载实现了真正的微前端架构,各子应用独立开发部署的同时保持组件级别的复用能力。结合共享依赖的版本协商和singleton约束,避免了多实例问题,是目前Webpack生态下最成熟的微前端技术方案。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/webpack5-mo-kuai-lian-bang-wei-qian-duan-jia-gou-da-jian-yu/

(0)
小编小编
上一篇 7小时前
下一篇 7小时前

相关推荐