You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Webpack 5 Module Federation本地单测微前端加载错误处理咨询

问题描述

我在基于NX CLI的单仓库中采用微前端架构,宿主应用通过Webpack 5的Module Federation加载各微前端的remoteEntry.js,并按路由作为组件渲染。已实现的Error Boundary在开发/预发布/生产环境中表现正常——当remoteEntry.js获取失败或微前端崩溃时,会显示404页面,符合预期。

但单独运行宿主应用单元测试或**本地单独启动宿主(未启动微前端)**时,Webpack会直接抛出错误:

Loading script failed.
(error: http://localhost:3107/remoteEntry.js)

希望在这些场景下,能触发Error Boundary显示404页面,而非直接抛出错误中断应用。

宿主应用Webpack配置如下:

const nrwlConfig = require('@nrwl/react/plugins/webpack.js');
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('../../package.json').dependencies;
const { remoteEntries } = require('./remoteEntries');

const EnvType = {
  LOCAL: 'local',
  PLAYGROUND: 'playground',
  DEVELOPMENT: 'development',
  STAGING: 'staging',
  PRODUCTION: 'production',
};

module.exports = (config) => {
  nrwlConfig(config);

  config.optimization.runtimeChunk = 'single';

  return {
    ...config,
    plugins: [
      ...config.plugins,
      new ModuleFederationPlugin({
        name: 'hostApp',
        filename: 'remoteEntry.js',
        remotes: {
          app1: `app1@${getRemoteEntryUrl(remoteEntries.APP1)}`,
          app2: `app2@${getRemoteEntryUrl(remoteEntries.APP2)}`,
          app3: `app3@${getRemoteEntryUrl(
            remoteEntries.APP3
          )}`,
          app4: `app4@${getRemoteEntryUrl(
            remoteEntries.APP4
          )}`,
          app5: `app5@${getRemoteEntryUrl(remoteEntries.APP5)}`,
          app6: `app6@${getRemoteEntryUrl(
            remoteEntries.APP6
          )}`,
          app7: `app7@${getRemoteEntryUrl(
            remoteEntries.APP7
          )}`,
        },
        shared: {
          ...deps,
          react: {
            singleton: true,
            eager: true,
            requiredVersion: deps.react,
          },
          'react-dom': {
            singleton: true,
            eager: true,
            requiredVersion: deps['react-dom'],
          },
        },
      }),
    ],
  };
};

const getRemoteEntryUrl = (entry) => {
  const { NODE_ENV } = process.env || EnvType.DEVELOPMENT;

  if (NODE_ENV !== EnvType.LOCAL) {
    return entry.remoteEntries[NODE_ENV];
  }

  return `http://localhost:${entry.localPort}/remoteEntry.js`;
};
解决方案

以下是几种可行的处理方案,可根据场景组合使用:

1. 动态加载微前端组件时捕获异常

Module Federation的静态remotes配置会在应用启动时尝试加载remoteEntry.js,绕过Error Boundary。改为动态加载,将加载逻辑放在组件内部,即可被Error Boundary捕获。

修改步骤:

  • 可保留Webpack中remotes静态配置,但不直接使用
  • 在路由组件中封装动态加载函数,包裹try/catch抛出错误

示例代码:

import React, { Suspense, lazy } from 'react';
import ErrorBoundary from './ErrorBoundary';

// 动态加载微前端的函数
const loadRemoteComponent = async (remoteName, modulePath) => {
  try {
    // 初始化remote链接
    const remote = await import(`webpack/container/reference/${remoteName}`);
    await remote.init(__webpack_share_scopes__.default);
    const module = await import(`webpack/container/remote/${remoteName}/${modulePath}`);
    return module.default;
  } catch (error) {
    // 抛出错误让Error Boundary捕获
    throw new Error(`微前端加载失败: ${error.message}`);
  }
};

// 封装为lazy组件
const App1Component = lazy(() => loadRemoteComponent('app1', './App'));

// 路由中使用
const AppRoutes = () => (
  <ErrorBoundary fallback={<div>404 - 微前端服务不可用</div>}>
    <Suspense fallback={<div>加载中...</div>}>
      <Route path="/app1" element={<App1Component />} />
      {/* 其他微前端路由同理 */}
    </Suspense>
  </ErrorBoundary>
);

export default AppRoutes;

2. 为测试/本地场景配置Mock Remote

通过环境变量判断场景,将remotes指向本地Mock模块,避免请求不存在的remoteEntry.js。

修改Webpack配置的getRemoteEntryUrl函数:

const getRemoteEntryUrl = (entry) => {
  const { NODE_ENV, TEST_ENV } = process.env || {};

  // 测试环境或本地单独启动时,返回mock文件路径
  if (TEST_ENV || (NODE_ENV === EnvType.LOCAL && !process.env.MICRO_FRONTEND_ENABLED)) {
    return 'mock/remoteEntry.js';
  }

  if (NODE_ENV !== EnvType.LOCAL) {
    return entry.remoteEntries[NODE_ENV];
  }

  return `http://localhost:${entry.localPort}/remoteEntry.js`;
};

在项目中创建mock/remoteEntry.js,返回错误提示组件:

// mock/remoteEntry.js
module.exports = {
  get: () => Promise.resolve({ default: () => <div>404 - 微前端未启动</div> })
};

3. 自定义Remote加载逻辑捕获错误

将静态remotes改为函数形式,自定义加载逻辑,捕获失败后返回Mock容器。

修改Webpack的ModuleFederationPlugin配置:

new ModuleFederationPlugin({
  // ...其他配置
  remotes: {
    app1: async () => {
      try {
        const remoteUrl = getRemoteEntryUrl(remoteEntries.APP1);
        const container = await import(/* webpackIgnore: true */ remoteUrl);
        return container;
      } catch (error) {
        // 返回Mock容器,渲染错误组件
        return {
          get: () => Promise.resolve({ default: () => <div>404 - 微前端服务不可用</div> })
        };
      }
    },
    // 其他微前端同理配置
  },
})

4. 单元测试中Mock微前端模块

在测试文件中直接Mock微前端的导入,避免实际加载远程资源:

// 测试文件顶部
jest.mock('webpack/container/reference/app1', () => ({
  init: jest.fn().mockResolvedValue(true),
}));

jest.mock('webpack/container/remote/app1/App', () => ({
  default: () => <div>Mock App1 组件</div>,
}));

// 或直接Mock整个加载函数
jest.mock('./loadRemoteComponent', () => ({
  __esModule: true,
  default: jest.fn().mockResolvedValue(() => <div>Mock 组件</div>),
}));

内容的提问来源于stack exchange,提问作者Raz Levy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 17:31:00