Webpack Module Federation微前端架构搭建与远程模块动态加载实战

Webpack 5引入的Module Federation(模块联邦)提供了一种原生级别的微前端实现方案。不同独立构建的应用可以在运行时共享和加载彼此的模块,无需借助iframe或npm包发布流程。宿主应用(Host)可以动态加载远程应用(Remote)暴露的组件,多个应用间还能共享React、Vue等运行时依赖避免重复加载。本文从架构设计到代码实现,完整演示Module Federation的搭建过程。

Module Federation核心概念与架构设计

Module Federation涉及三个核心概念:Host(消费方)是运行时加载远程模块的应用;Remote(提供方)是暴露模块供其他应用消费的应用,一个应用可以同时是Host和Remote;Shared(共享依赖)是多个应用间公共的第三方库,如react、react-dom,Shared模块根据版本协商自动选择加载方。典型架构场景是一个主门户应用作为Host,多个业务子应用作为Remote,各应用独立开发、独立部署、独立运行。

Remote应用配置与模块暴露

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

module.exports = {
  mode: "development",
  entry: "./src/index.js",
  devServer: {
    port: 3001,
    headers: { "Access-Control-Allow-Origin": "*" },
  },
  output: { publicPath: "//localhost:3001/" },
  module: {
    rules: [{
      test: /\.jsx?$/,
      exclude: /node_modules/,
      use: { loader: "babel-loader", options: { presets: ["@babel/preset-react"] } },
    }],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "remoteApp",
      filename: "remoteEntry.js",
      exposes: {
        "./Widget": "./src/components/Widget",
        "./Dashboard": "./src/pages/Dashboard",
      },
      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
      },
    }),
  ],
};

filename指定远程入口文件名,宿主通过加载这个JS文件获取Remote暴露的模块清单。exposes的key是远程模块的访问路径,value是本地源码路径。shared中singleton:true确保React只有一个运行实例,避免多实例导致的Hooks报错。Remote应用需要异步边界入口文件确保共享依赖在模块加载前完成初始化:

// remote-app/src/index.js
import("./bootstrap");

// remote-app/src/bootstrap.js
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")).render(<App />);

Host应用配置与远程模块加载

// host-app/webpack.config.js
new ModuleFederationPlugin({
  name: "hostApp",
  remotes: {
    remoteApp: "remoteApp@//localhost:3001/remoteEntry.js",
  },
  shared: {
    react: { singleton: true, requiredVersion: deps.react },
    "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
  },
})

remotes配置中的key与Remote的name对应,value包含Remote名称和remoteEntry.js的URL。运行时Webpack通过动态script标签加载remoteEntry.js,然后通过全局变量remoteApp获取模块工厂函数。使用React.lazy和Suspense加载远程组件:

// host-app/src/bootstrap.js
import React, { Suspense } from "react";
import { createRoot } from "react-dom/client";

const RemoteWidget = React.lazy(() => import("remoteApp/Widget"));
const RemoteDashboard = React.lazy(() => import("remoteApp/Dashboard"));

function App() {
  return (
    <div>
      <h1>Host Application</h1>
      <Suspense fallback={<div>Loading Widget...</div>}>
        <RemoteWidget />
      </Suspense>
      <Suspense fallback={<div>Loading Dashboard...</div>}>
        <RemoteDashboard />
      </Suspense>
    </div>
  );
}
createRoot(document.getElementById("root")).render(<App />);

import(“remoteApp/Widget”)会被Webpack解析为对远程模块的加载请求。首次加载时浏览器先请求remoteEntry.js获取模块映射表,再按需加载对应chunk文件,后续加载相同模块直接命中缓存。

动态远程加载与运行时配置URL

固定URL的远程配置在编译时确定,生产环境常需根据环境动态指定Remote地址。使用Promise形式的动态远程加载:

new ModuleFederationPlugin({
  name: "hostApp",
  remotes: {
    remoteApp: `promise new Promise((resolve, reject) => {
      const script = document.createElement("script");
      script.src = window.__REMOTE_URLS__.remoteApp;
      script.onload = () => {
        const container = window.remoteApp;
        container.init(__webpack_share_scopes__.default);
        resolve(container);
      };
      script.onerror = reject;
      document.head.appendChild(script);
    })`,
  },
  shared: {
    react: { singleton: true },
    "react-dom": { singleton: true },
  },
})

配合全局变量或配置中心,运行时读取各Remote的CDN地址实现按环境灵活切换。

共享依赖版本协商与降级策略

shared: {
  react: {
    singleton: true,
    requiredVersion: "18.2.0",
    eager: false,
    import: false,
  },
  antd: {
    singleton: false,
    requiredVersion: "^5.0.0",
  },
}

版本协商规则:Host和Remote都声明了shared依赖时,运行时比较版本号,选择满足双方requiredVersion的版本加载。若版本不兼容且singleton为true,使用先加载的版本并发出控制台警告。singleton为false时各自加载自己的版本,适用于UI组件库等允许多版本并存的场景。eager:true会将共享依赖打包进初始bundle而非异步chunk,适合Host自身也直接使用的依赖但会增加初始加载体积。

生产环境部署与CDN静态资源路径处理

生产环境部署在CDN时output.publicPath必须正确指向CDN地址:

// src/index.js - 动态设置publicPath
__webpack_public_path__ = window.__PUBLIC_PATH__ || "//cdn.example.com/remote-app/";
import("./bootstrap");
# Nginx CORS配置
location / {
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods "GET, OPTIONS";
    try_files $uri $uri/ /index.html;
}

远程加载失败通过ErrorBoundary捕获,提供降级UI防止白屏:

class RemoteErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  componentDidCatch(error) {
    console.error("Remote module load failed:", error);
  }
  render() {
    if (this.state.hasError) {
      return <div>模块加载失败,请刷新重试</div>;
    }
    return this.props.children;
  }
}

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

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

相关推荐

Webpack Module Federation微前端架构搭建与远程模块动态加载实战

Webpack 5引入的Module Federation(模块联邦)提供了一种原生级别的微前端实现方案。不同独立构建的应用可以在运行时共享和加载彼此的模块,无需借助iframe或npm包发布流程。宿主应用(Host)可以动态加载远程应用(Remote)暴露的组件,多个应用间还能共享React、Vue等运行时依赖避免重复加载。本文从架构设计到代码实现,完整演示Module Federation的搭建过程。

Module Federation核心概念与架构设计

Module Federation涉及三个核心概念:Host(消费方)是运行时加载远程模块的应用;Remote(提供方)是暴露模块供其他应用消费的应用,一个应用可以同时是Host和Remote;Shared(共享依赖)是多个应用间公共的第三方库,如react、react-dom,Shared模块根据版本协商自动选择加载方。典型架构场景是一个主门户应用作为Host,多个业务子应用作为Remote,各应用独立开发、独立部署、独立运行。

Remote应用配置与模块暴露

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

module.exports = {
  mode: "development",
  entry: "./src/index.js",
  devServer: {
    port: 3001,
    headers: { "Access-Control-Allow-Origin": "*" },
  },
  output: { publicPath: "//localhost:3001/" },
  module: {
    rules: [{
      test: /\.jsx?$/,
      exclude: /node_modules/,
      use: { loader: "babel-loader", options: { presets: ["@babel/preset-react"] } },
    }],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "remoteApp",
      filename: "remoteEntry.js",
      exposes: {
        "./Widget": "./src/components/Widget",
        "./Dashboard": "./src/pages/Dashboard",
      },
      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
      },
    }),
  ],
};

filename指定远程入口文件名,宿主通过加载这个JS文件获取Remote暴露的模块清单。exposes的key是远程模块的访问路径,value是本地源码路径。shared中singleton:true确保React只有一个运行实例,避免多实例导致的Hooks报错。Remote应用需要异步边界入口文件确保共享依赖在模块加载前完成初始化:

// remote-app/src/index.js
import("./bootstrap");

// remote-app/src/bootstrap.js
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")).render(<App />);

Host应用配置与远程模块加载

// host-app/webpack.config.js
new ModuleFederationPlugin({
  name: "hostApp",
  remotes: {
    remoteApp: "remoteApp@//localhost:3001/remoteEntry.js",
  },
  shared: {
    react: { singleton: true, requiredVersion: deps.react },
    "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
  },
})

remotes配置中的key与Remote的name对应,value包含Remote名称和remoteEntry.js的URL。运行时Webpack通过动态script标签加载remoteEntry.js,然后通过全局变量remoteApp获取模块工厂函数。使用React.lazy和Suspense加载远程组件:

// host-app/src/bootstrap.js
import React, { Suspense } from "react";
import { createRoot } from "react-dom/client";

const RemoteWidget = React.lazy(() => import("remoteApp/Widget"));
const RemoteDashboard = React.lazy(() => import("remoteApp/Dashboard"));

function App() {
  return (
    <div>
      <h1>Host Application</h1>
      <Suspense fallback={<div>Loading Widget...</div>}>
        <RemoteWidget />
      </Suspense>
      <Suspense fallback={<div>Loading Dashboard...</div>}>
        <RemoteDashboard />
      </Suspense>
    </div>
  );
}
createRoot(document.getElementById("root")).render(<App />);

import(“remoteApp/Widget”)会被Webpack解析为对远程模块的加载请求。首次加载时浏览器先请求remoteEntry.js获取模块映射表,再按需加载对应chunk文件,后续加载相同模块直接命中缓存。

动态远程加载与运行时配置URL

固定URL的远程配置在编译时确定,生产环境常需根据环境动态指定Remote地址。使用Promise形式的动态远程加载:

new ModuleFederationPlugin({
  name: "hostApp",
  remotes: {
    remoteApp: `promise new Promise((resolve, reject) => {
      const script = document.createElement("script");
      script.src = window.__REMOTE_URLS__.remoteApp;
      script.onload = () => {
        const container = window.remoteApp;
        container.init(__webpack_share_scopes__.default);
        resolve(container);
      };
      script.onerror = reject;
      document.head.appendChild(script);
    })`,
  },
  shared: {
    react: { singleton: true },
    "react-dom": { singleton: true },
  },
})

配合全局变量或配置中心,运行时读取各Remote的CDN地址实现按环境灵活切换。

共享依赖版本协商与降级策略

shared: {
  react: {
    singleton: true,
    requiredVersion: "18.2.0",
    eager: false,
    import: false,
  },
  antd: {
    singleton: false,
    requiredVersion: "^5.0.0",
  },
}

版本协商规则:Host和Remote都声明了shared依赖时,运行时比较版本号,选择满足双方requiredVersion的版本加载。若版本不兼容且singleton为true,使用先加载的版本并发出控制台警告。singleton为false时各自加载自己的版本,适用于UI组件库等允许多版本并存的场景。eager:true会将共享依赖打包进初始bundle而非异步chunk,适合Host自身也直接使用的依赖但会增加初始加载体积。

生产环境部署与CDN静态资源路径处理

生产环境部署在CDN时output.publicPath必须正确指向CDN地址:

// src/index.js - 动态设置publicPath
__webpack_public_path__ = window.__PUBLIC_PATH__ || "//cdn.example.com/remote-app/";
import("./bootstrap");
# Nginx CORS配置
location / {
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods "GET, OPTIONS";
    try_files $uri $uri/ /index.html;
}

远程加载失败通过ErrorBoundary捕获,提供降级UI防止白屏:

class RemoteErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  componentDidCatch(error) {
    console.error("Remote module load failed:", error);
  }
  render() {
    if (this.state.hasError) {
      return <div>模块加载失败,请刷新重试</div>;
    }
    return this.props.children;
  }
}

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

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

相关推荐