记qiankun微前端在搭建平台的实践
遇到问题
Buffalo是一个拖拽生成后台管理页面的平台,在开发期间遇到了一个较为麻烦的问题:
物料组件的在编辑器与运行时之间的共享问题。
可以想象到,一个搭建平台至少包括这两部分:
-
编辑器(画布)
-
运行时(产物页面)
出于打包,加载时长的考虑,分成了两个独立的项目,分别是 buffalo-client 和 buffalo-apps。举个具体的例子:
在初步实现中,现状是物料组件的源码(如 同时在编辑器(buffalo-client)项目和运行时(buffalo-apps)项目中分别储存,一式两份。
这种做法造成代码冗余,每次更新/新增物料组件都要同步改两个项目的源码。同时,随着物料的新增,也会不断的加大项目的体积,造成包体积增大,加载缓慢。
方案评定
为了解决这个问题,做了一些简单的调查,大概有几个方向:
- SystemJS (github.com/systemjs/sy…)
- Webpack module federation (webpack.docschina.org/concepts/mo…)
- qiankun (qiankun.umijs.org/zh)
方案 | 是否需要改动基座webpack配置 | 安装物料方式 | 兼容问题 | 按需加载 | 注册物料方式 |
---|---|---|---|---|---|
systemjs | 需要 | npm | 较多 | 支持 | importmap? |
webpack module federation | 需要 | npm | 较多 | 支持 | webpack.config? |
qiankun | 不需要 | url | 较少 | 支持(可以使用import动态加载所需) | bootstrap 或 mount 钩子 |
对于 systemjs ,业界已经有了一些较好的想法和实践,比如云凤蝶《ESM Bundleless 在低代码场景的实践》[mp.weixin.qq.com/s/GbZknXa_3…]。 虽然这个方案看起来比较美妙,但是可想目前靠自己实践起来的话成本较高,暂不接入。
预期效果
可见比较稳妥和成本较低的方案是接入qiankun,那么现在预想中的结构是这样的:
编辑器和运行时同时加载部署在 www.sample.com/datalist 中的代码,如此以来,以后有需要修改 的代码逻辑的话,可以只修改一次。
同时,可以新增物料组件也是比较清晰的。甚至我们可以使用多套物料库,比如 www.sample2.com
同时,我们可以在 sample.com/ 挂载 /datalist 的时候,利用 React 的 Code-Splitting 能力对每个使用到的组件采用按需加载的功能(Code-Splitting – React (reactjs.org))。
实践
开始动工:在编辑器项目安装qiankun:
$ yarn add qiankun
父应用(编辑器,运行时):
新建 LoadableApp 组件,这个组件可以在画布上渲染物料组件:
import { loadMicroApp, MicroApp } from "qiankun";
import React, { FC, PropsWithChildren, useEffect, useRef } from "react";
import { Material } from "../../types";
interface Props {
id: string;
material: Material;
[k: string]: any;
}
export const LoadableApp: FC<PropsWithChildren<Props>> = ({
children,
id,
material,
...props
}) => {
const microAppInstance = useRef<MicroApp | null>(null);
useEffect(() => {
microAppInstance.current = loadMicroApp({
name: id,
entry: material.src,
container: `#${id}`,
props: {
buffaloMaterial: material,
buffaloMaterialProps: props,
},
});
return () => {
microAppInstance.current = null;
};
// 只会初始化一次
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (microAppInstance.current?.getStatus() === "MOUNTED") {
console.log("new props", props);
microAppInstance.current?.update?.({ ...props });
}
}, [props]);
return <div id={id}></div>;
};
我们在 LoadableApp 组件做了:
- 指定物料id,一个不会重复的 nanojs 生产出来的随机串;同时我们用来这个id作为微应用的id;
- 传递物料信息给微应用(这步可以进一步优化成之后再 bootstrap 钩子 传回给父应用);
- 传递用户编辑的物料值(props)给微应用;
- 监听用户编辑数据,通过
microAppInstance.current?.update?.({ ...props })
更新最新编辑数据值给微应用;
父应用的修改只需要这么多,可见接入qiankun是较低成本的。
微应用(物料库):
通过 Create-React-App 生成物料库项目,我们指定项目名称是 buffalo-materials。
然而,我们还是需要稍微修改下 webpack 配置以达到qiankun的要求的,顺便安装 react-app-rewired。在根目录创建 config-overrides.js 文件与修改 npm scripts:
const packageName = require('./package.json').name;
module.exports = {
webpack: function (config, env) {
config.output = {
...config.output,
library: `${packageName}-[name]`,
libraryTarget: 'umd',
chunkLoadingGlobal: `webpackJsonp_${packageName}`,
};
return config;
},
};
接下来就可以修改 App.js 了,这步也很简便:
import './public-path';
import 'antd/dist/antd.css';
import './App.css';
import { Spin } from 'antd';
import { Suspense, lazy, useEffect } from 'react';
const Input = lazy(() => import('antd/es/input'));
const componentsMap = new Map([['/input', Input]]);
const App = (props) => {
const { buffaloMaterial, buffaloMaterialProps } = props;
console.log('buffaloMaterial', buffaloMaterial);
const hash = buffaloMaterial.src.split('#')[1];
const ComponentType = componentsMap.get(hash);
useEffect(() => {
console.log('buffaloMaterialProps', buffaloMaterialProps);
}, [buffaloMaterialProps]);
return (
<Suspense fallback={<Spin />}>
<ComponentType {...buffaloMaterialProps} />
</Suspense>
);
};
export default App;
我们在这里做了:
- 使用 React.lazy / Suspense 加载 antd 的input 组件;
- 通过分析物料 src 在注册表找到需要渲染的组件;
- 传递用户填写的props给需要渲染的组件
之后我们像qiankun文档所说的,在入口脚本增加 qiankun 的 bootstrap,mount 等钩子,
然后部署项目到cdn即可。
效果展示:
参考: