react 子应用
本节我们将详细介绍 react 框架的应用作为子应用的接入步骤。v16/17 demo、v18 demo
react 子应用接入步骤
1. bridge 依赖安装
提示
- 请注意,桥接函数的安装不是必须的,你可以自定义导出函数。
- 我们提供桥接函数是为了进一步降低用户接入成本并降低用户出错概率,桥接函数中将会内置一些默认行为,可以避免由于接入不规范导致的错误,所以这也是我们推荐的接入方式。
- npm
- Yarn
npm install @garfish/bridge-react --save
yarn add @garfish/bridge-react
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 />,
});
// src/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import RootComponent from "./components/root";
export const provider = () => {
return {
// 和子应用独立运行时一样,将子应用渲染至对应的容器节点,根据不同的框架使用不同的渲染方式
render({ dom, basename, props}) {
ReactDOM.render(<RootComponent {...props} />, root);
},
destroy({ dom, basename}) {
// 使用框架提供的销毁函数销毁整个应用,已达到销毁框架中可能存在得副作用,并触发应用中的一些组件销毁函数
// 需要注意的时一定要保证对应框架得销毁函数使用正确,否则可能导致子应用未正常卸载影响其他子应用
ReactDOM.unmountComponentAtNode(
dom ? dom.querySelector('#root') : document.querySelector('#root'),
);
},
};
};
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 />,
});
// src/index.tsx
import { createRoot } from 'react-dom/client';
import RootComponent from './root';
// 在首次加载和执行时会触发该函数
export const provider = () => {
let root = null;
return {
render({ basename, dom, store, props }) {
const container = dom.querySelector('#root');
root = createRoot(container!);
(root as any).render(<RootComponent basename={basename} />);
},
destroy({ dom }) {
(root as any).unmount();
},
};
};
3. 根组件设置路由的 basename
信息
// 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': '*',
},
},
};
【重要】注意:
- libraryTarget 需要配置成 umd 规范;
- globalObject 需要设置为 'window',以避免由于不规范的代码格式导致的逃逸沙箱;
- 如果你的 webpack 为 v4 版本,需要设置 jsonpFunction 并保证该值唯一(否则可能出现 webpack chunk 互相影响的可能)。若为 webpack5 将会直接使用 package.json name 作为唯一值,请确保应用间的 name 各不相同;
- publicPath 设置为子应用资源的绝对地址,避免由于子应用的相对资源导致资源变为了主应用上的相对资源。这是因为主、子应用处于同一个文档流中,相对路径是相对于主应用而言的
- 'Access-Control-Allow-Origin': '*' 允许开发环境跨域,保证子应用的资源支持跨域。另外也需要保证在上线后子应用的资源在主应用的环境中加载不会存在跨域问题(也需要限制范围注意安全问题); :::
5. 增加子应用独立运行兼容逻辑
提示
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"));
}
// src/index.tsx
if (!window.__GARFISH__) {
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(
<RootComponent
basename={
process.env.NODE_ENV === 'production' ? '/examples/subapp/react18' : '/'
}
/>
);
}