react 子应用

本节我们将详细介绍 react 框架的应用作为子应用的接入步骤。v16/17 demov18 demo

react 子应用接入步骤

1. bridge 依赖安装

TIP
  1. 请注意,桥接函数的安装不是必须的,你可以自定义导出函数。
  2. 我们提供桥接函数是为了进一步降低用户接入成本并降低用户出错概率,桥接函数中将会内置一些默认行为,可以避免由于接入不规范导致的错误,所以这也是我们推荐的接入方式。
npm install @garfish/bridge-react --save

2. 入口文件处导出 provider 函数

更多 bridge 函数参数介绍请参考 这里

react v16、v17 导出

使用 @garfish/bridge-react
自定义导出函数
// src/index.tsx
import { reactBridge } from '@garfish/bridge-react';
import RootComponent from './components/root';
import Error from './components/ErrorBoundary';

export const provider = reactBridge({
  // 子应用挂载点,若子应用构建成 js ,则不需要传递该值
  el: '#root',
  // 根组件, bridge 会默认传递 basename、dom、props 等信息到根组件
  rootComponent: RootComponent,
  // 设置应用的 errorBoundary
  errorBoundary: () => <Error />,
});

react v18 导出

使用 @garfish/bridge-react-v18
自定义导出函数
// src/index.tsx
import { reactBridge } from '@garfish/bridge-react-v18';
import RootComponent from './root';
import ErrorBoundary from './ErrorBoundary';

export const provider = reactBridge({
  el: '#root',
  rootComponent: RootComponent,
  errorBoundary: (e: any) => <ErrorBoundary />,
});

3. 根组件设置路由的 basename

INFO
  1. 为什么要设置 basename?请参考 issue
  2. 我们强烈建议使用从主应用传递过来的 basename 作为子应用的 basename,而非主、子应用约定式,避免 basename 后期变更未同步带来的问题。
  3. 目前主应用仅支持 history 模式的子应用路由,why
// src/component/rootComponent
import React from "react";
import { BrowserRouter } from "react-router-dom";

const RootComponent = ({ basename }) => {
  return (
    <BrowserRouter basename={basename}>
      <Routes>
        <Route path="/" element={<App />}>
          <Route path="/home" element={<Home />} />
          <Route path="*" element={<PageNotFound />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}

4. 更改 webpack 配置

// webpack.config.js
const webpack = require('webpack');
const isDevelopment = process.env.NODE_ENV !== 'production';

module.exports = {
  output: {
    // 开发环境设置 true 将会导致热更新失效
    clean: isDevelopment ? false : true,
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js',
    // 需要配置成 umd 规范
    libraryTarget: 'umd',
    // 修改不规范的代码格式,避免逃逸沙箱
    globalObject: 'window',
    // webpack5 使用 chunkLoadingGlobal 代替,或不填保证 package.json name 唯一即可
    jsonpFunction: 'garfish-demo-react',
    // 保证子应用的资源路径变为绝对路径
    publicPath: 'http://localhost:8080',
  },
  plugin: [
    // 保证错误堆栈信息及 sourcemap 行列信息正确
    new webpack.BannerPlugin({
      banner: 'Micro front-end',
    }),
  ],
  devServer: {
    // 保证在开发模式下应用端口不一样
    port: '8000',
    headers: {
      // 保证子应用的资源支持跨域,在上线后需要保证子应用的资源在主应用的环境中加载不会存在跨域问题(**也需要限制范围注意安全问题**)
      'Access-Control-Allow-Origin': '*',
    },
  },
};

【重要】注意:

  1. libraryTarget 需要配置成 umd 规范;
  2. globalObject 需要设置为 'window',以避免由于不规范的代码格式导致的逃逸沙箱;
  3. 如果你的 webpack 为 v4 版本,需要设置 jsonpFunction 并保证该值唯一(否则可能出现 webpack chunk 互相影响的可能)。若为 webpack5 将会直接使用 package.json name 作为唯一值,请确保应用间的 name 各不相同;
  4. publicPath 设置为子应用资源的绝对地址,避免由于子应用的相对资源导致资源变为了主应用上的相对资源。这是因为主、子应用处于同一个文档流中,相对路径是相对于主应用而言的
  5. 'Access-Control-Allow-Origin': '*' 允许开发环境跨域,保证子应用的资源支持跨域。另外也需要保证在上线后子应用的资源在主应用的环境中加载不会存在跨域问题(也需要限制范围注意安全问题);

5. 增加子应用独立运行兼容逻辑

TIP

last but not least, 别忘了添加子应用独立运行逻辑,这能够让你的子应用脱离主应用独立运行,便于后续开发和部署。

react v16/v17
react v18
// src/index.tsx
if (!window.__GARFISH__) {
  ReactDOM.render(
    <RootComponent
      basename={
        process.env.NODE_ENV === 'production' ? '/examples/subapp/react18' : '/'
      }
    />, document.getElementById("root"));
}