记qiankun微前端在搭建平台的实践

1,098 阅读4分钟

记qiankun微前端在搭建平台的实践

遇到问题

Buffalo是一个拖拽生成后台管理页面的平台,在开发期间遇到了一个较为麻烦的问题:

物料组件的在编辑器与运行时之间的共享问题

可以想象到,一个搭建平台至少包括这两部分:

  1. 编辑器(画布)

  2. 运行时(产物页面)

出于打包,加载时长的考虑,分成了两个独立的项目,分别是 buffalo-client 和 buffalo-apps。举个具体的例子:

Untitled.png

在初步实现中,现状是物料组件的源码(如 同时在编辑器(buffalo-client)项目和运行时(buffalo-apps)项目中分别储存,一式两份。

这种做法造成代码冗余,每次更新/新增物料组件都要同步改两个项目的源码。同时,随着物料的新增,也会不断的加大项目的体积,造成包体积增大,加载缓慢。

方案评定

为了解决这个问题,做了一些简单的调查,大概有几个方向:

方案是否需要改动基座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,那么现在预想中的结构是这样的:

Untitled 1.png

编辑器和运行时同时加载部署在 www.sample.com/datalist 中的代码,如此以来,以后有需要修改 的代码逻辑的话,可以只修改一次。

同时,可以新增物料组件也是比较清晰的。甚至我们可以使用多套物料库,比如 www.sample2.com

Untitled 2.png

同时,我们可以在 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即可。

效果展示:

Untitled 3.png

参考:

当企微侧边栏遇上微前端 | 微信开放社区 (qq.com)

【第2492期】ESM Bundleless 在低代码场景的实践