浅谈 Antd v5 的遗留bug:如何优化打包体积

419 阅读3分钟

642a8e8cd1126868e0875c38DUv5DSiv01.png

看到 Ant Design 核心成员 @zombieJ 昨天更新了一篇文章,提到一个遗留的 bug,用了 ConfigProvider 之后,即使没有用到 Form 组件,也会错误打包 rc-field-form

我看完这篇文章,特意分析了之前云课堂的业务工程,发现也有这个问题。如果你也在用 Vita-cli 构建前端工程,可以按下面的步骤排查:

1、首先在 vita.config.js 中添加如下配置,禁用 scope hoisting,方便分析打包产物:

config.optimization.concatenateModules(false);

2、运行下面的命令,可以在打包完成后分析打包产物:

$ npx vita build --analyze

打包完成后会自动打开下面的页面,可以看到 rc-field-form 确实是被错误打包了(这个工程没有用到 Form 组件)

Screenshot 2023-06-26 at 16.08.19.png

为啥会有这个问题?这是因为 ConfigProvider 提供了全局化配置能力,其中也包含了 Form 组件校验信息的自定义模板配置:

<ConfigProvider form={{ validateMessages }} />

从源码看出,ConfigProvider 底层依赖了从 rc-field-form 引入的 FormProvider,而 FormProvider 本身又对 rc-field-form 的 FormContext 进行了封装,导致引入 FormProvider 后将 rc-field-form 的更多内容给打包进来了:

Pasted image 20230626162030.png

有同学可能会想到,我们是不是可以做一个优化,如果没有配置 validateMessages 我们就不调用这个 FormProvider?

import { FormProvider } from 'rc-field-form';

const ConfigProvider = ({ validateMessages, children }) => {
  let node = children;

  if (validateMessages) {
    node = <FormProvider validateMessages={merge(...)}>{node}</FormProvider>;
  }

  return node;
};

很遗憾,这是不行的。Tree Shaking 只在编译阶段静态分析(Static Code Analysis),不能执行代码,而 validateMessages 是一个运行时的配置。所以在打包过程中,我们无法知道 validateMessages 是否存在,因而无法做到这样的优化。

如何优化?作者给出了一种解决方案,调整依赖,让 FormProvider 解耦,ConfigProvider 不再依赖于 FormProvider。实现也非常简单,既然这是 rc-field-form 所独有的,那么我们直接抽一个 Context 出来,让 ConfigProvider 不再感知 FormProvider 即可:

import { ValidateMessageContext } from '../form/context.ts';

const ConfigProvider = ({ validateMessages, children }) => {
  const mergedValidateMessages = ...

  return (
    // Just use the proxy context
    <ValidateMessageContext value={mergedValidateMessages}>
      <SomeOtherProvider>{children}</SomeOtherProvider>
    </ValidateMessageContext>
  );
};

而在 Form 中,同样消费代理的 Context:

import Form, { FormProvider } from 'rc-field-form';
import { ValidateMessageContext } from './context';

export default (props) => {
  const validateMessages = React.useContext(ValidateMessageContext);

  return (
    <FormProvider validateMessages={validateMessages}>
      <Form {...props} />
    </FormProvider>
  );
};

依赖就变成了这样,从而实现了解耦:

Pasted image 20230626162931.png

最后谈谈个人看法。这个 ConfigProvider 显然是 antd 设计之初留下的 bug。为啥之前没人发现,这是因为在 antd 里面,Table、Form 这些都是高频组件,开发中后台项目必然会用,这些也是 antd 逻辑最复杂、代码量最大的组件(应该不太有人只为了用一个 Button 就引入整个 antd)。因此 antd 作者考虑易用性,会把 Form 组件配置提升到 ConfigProvider,也是合理的做法。这样做会导致一些问题,ConfigProvider 是运行时配置,会导致 Tree-Shaking 失效。文章最后作者给出了一个比较简单直接的解决方案,虽然支持 Tree-Shaking,但是使用又变得有些复杂了。所以还是那句话,软件工程没有银弹,性能的提升必然是以牺牲易用性、可维护性为代价的。

参考

ant.design/docs/blog/t…