Upgrading React with micro-frontends

Alex O'Callaghan

使用微前端升级 React

原文由 Alex O'Callaghan 发布,订阅该博客

2021 年,我们基于 React 17 部署了微前端架构。我们按照建议将 reactreact-dom 设为共享的单例模块。这样整个应用在单一的 React 树中渲染。随着许多主流依赖要求使用 React 18,且 React 19 即将发布,我们必须在微前端中升级 React。由于涉及多个团队,这一升级必须分步进行。

引入桥接组件

2026 年更新我们随后开源了自己的微前端 React 桥接包,详情可参阅 《开源我们的微前端 React 桥接方案》。本文介绍的是我们最初使用 @module-federation/bridge-react 包的实现方案。

为了让微前端在独立的 React 树中渲染,我们添加了一个“桥接”组件包装层。这样就能在不影响外壳应用的情况下升级微前端中的 React。

@module-federation/bridge-react 包为这种方案提供了一些辅助工具。在微前端内部,我们新增了一个 export-app.tsx 文件,将入口 React 组件用桥接组件包裹起来:

// src/export-app.tsx
import { createBridgeComponent } from "@module-federation/bridge-react";
import App from "./App";

export default createBridgeComponent({
  rootComponent: App,
});

export const BRIDGE = true;

我们还需要更新 ModuleFederationPlugin,将这个新的入口文件暴露出去。为了让微前端能够使用比外壳应用更高的版本,我们必须不再将 reactreact-dom 作为单例共享。

// webpack.config.js
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "app",
      filename: "remoteEntry.js",
      exposes: {
        "./App": "./src/export-app",
      },
      shared: ["react", "react-dom"],
    }),
  ],
};

在外壳应用中,我们使用 import-remote 动态加载 remoteEntry 文件。为了向后兼容,我们需要调整这一逻辑,使其既能加载直接暴露的 React 组件,也能加载经过桥接的组件:

import { createRemoteComponent } from '@module-federation/bridge-react';

const SomeRemote = React.lazy(async () => {
  const remote = await importRemote({
    url: '/bundles/remote-app',
    scope: 'remote-app',
    module: 'App',
  });

  if (remote.BRIDGE) {
    return {
      default: createRemoteComponent({
        loader: () => remote,
        loading: <div>Loading...</div>,
        fallback: (info) =>
          <div>Failed to load {info.error.message}</div>,
      });
    }

    return remote;
  }
});

这里有几点需要说明:

  • 我们使用 importRemote 懒加载 remoteEntry.js 文件,从而避免加载不需要的微前端。
  • 我们检查导出的常量 BRIDGE,它用于判断该微前端是否已用桥接组件包裹。这让各团队可以自行选择是否使用桥接,同时也能保持对尚未迁移的微前端的兼容。
  • 我们将桥接组件放在一个带有 default 属性的对象中返回,以模拟 React.lazy 所期望的动态加载模块的结构。

通过这些改动,我们就可以将各个微前端单独升级到 React 18,而无需升级外壳应用。等到所有微前端都采用桥接方案后,我们便可以在不强制升级微前端的情况下单独升级外壳应用。

本文章由 muse-spark-1.2-contributor 进行翻译

评论