logoAnt Design

⌘ K
  • Design
  • Development
  • Components
  • Blog
  • Resources
5.25.4
  • CSS in v6
  • 👀 Visual Regression Testing
  • Why is it so hard to disable the date?
  • HOC Aggregate FieldItem
  • Line Ellipsis Calculation
  • 📢 v4 surpassed maintenance period
  • Type Util
  • A build ghost
  • Ant Design meets CSS Variables
  • Historical Debt of API
  • Stacked Notification
  • Color Models and Color Picker
  • Extends Theme
  • Virtual Table is here!
  • Happy Work Theme
  • Where is the dynamic style?
  • Suspense breaks styles
  • Bundle Size Optimization
  • Hi, GitHub Actions
  • To be what you see
  • Pain of static methods
  • SSR Static style export
  • Dependency troubleshooting
  • Contributor development maintenance guide
  • Repost: How to submit a riddle
  • Tooltip align update
  • Unnecessary Rerender
  • How to Grow as a Collaborator
  • Funny Modal hook BUG
  • about antd test library migration
  • Tree's check conduction
  • Some change on getContainer
  • Component-level CSS-in-JS

Bundle Size Optimization

2023-06-25
@zombieJ

Articles are included in the column:

antd

Ant Design

Juejin logoAnt Design Open Source Column
Juejin logoGo to discuss
contributors
  • Suspense breaks stylesHi, GitHub Actions

    Resources

    Ant Design X
    Ant Design Charts
    Ant Design Pro
    Pro Components
    Ant Design Mobile
    Ant Design Mini
    Ant Design Web3
    Ant Design Landing-Landing Templates
    Scaffolds-Scaffold Market
    Umi-React Application Framework
    dumi-Component doc generator
    qiankun-Micro-Frontends Framework
    Ant Motion-Motion Solution
    China Mirror 🇨🇳

    Community

    Awesome Ant Design
    Medium
    Twitter
    yuque logoAnt Design in YuQue
    Ant Design in Zhihu
    Experience Cloud Blog
    seeconf logoSEE Conf-Experience Tech Conference

    Help

    GitHub
    Change Log
    FAQ
    Bug Report
    Issues
    Discussions
    StackOverflow
    SegmentFault

    Ant XTech logoMore Products

    yuque logoYuQue-Document Collaboration Platform
    AntV logoAntV-Data Visualization
    Egg logoEgg-Enterprise Node.js Framework
    Kitchen logoKitchen-Sketch Toolkit
    Galacean logoGalacean-Interactive Graphics Solution
    xtech logoAnt Financial Experience Tech
    Theme Editor
    Made with ❤ by
    Ant Group and Ant Design Community

    In modern JS applications, unused module can be automatically removed by modular packaging tools. This process is called Tree Shaking. However, if you are already very familiar with it, you will find that it is not so perfect in reality. We still need some extra operations to achieve the best size optimization effect. Today, let's talk about a problem that ConfigProvider causes Tree Shaking to fail.

    ConfigProvider and rc-field-form

    In daily maintenance, we encountered some problems that using ConfigProvider would cause bundle size to increase:

    • https://github.com/ant-design/ant-design/issues/41607
    • https://github.com/ant-design/ant-design/issues/43019
    • https://github.com/ant-design/ant-design/issues/42499

    The community also found the package that was incorrectly packaged while giving feedback rc-field-form. Here we directly borrow the illustration in the issue:

    bundle size

    ConfigProvider provides global configuration capabilities, which also includes the custom template configuration of Form component verification information:

    tsx
    <ConfigProvider form={{ validateMessages }} />
    Customize

    Since this feature dependents with the verification of the form, it is implemented by the FormProvider provided by the underlying rc-field-form. In antd, it will be aggregated with its own localized validateMessages:

    tsx
    // Sample only. Not real world code.
    import { FormProvider } from 'rc-field-form';
    const ConfigProvider = ({ validateMessages, children }) => {
    const mergedValidateMessages = React.useMemo(
    () => merge(antdDefaultValidateMessages, validateMessages),
    [validateMessages],
    );
    return (
    <FormProvider validateMessages={mergedValidateMessages}>
    <SomeOtherProvider>{children}</SomeOtherProvider>
    </FormProvider>
    );
    };

    Meanwhile, FormProvider itself encapsulates the FormContext of rc-field-form, which causes more content of rc-field-form to be packaged after introducing FormProvider:

    Deps

    You may think, can we optimize it? If validateMessages is not configured, we will not call this FormProvider?

    tsx
    // Sample only. Not real world code.
    import { FormProvider } from 'rc-field-form';
    const ConfigProvider = ({ validateMessages, children }) => {
    let node = children;
    if (validateMessages) {
    node = <FormProvider validateMessages={merge(...)}>{node}</FormProvider>;
    }
    return node;
    };

    Unfortunately, this is not possible. Tree Shaking is a static compilation process, and validateMessages is a runtime configuration. So in the packaging process, we cannot know whether validateMessages exists, so we cannot achieve this optimization.

    Decompose Dependencies

    We can adjust rc-field-form dependencies, so that FormProvider can be decoupled. But obviously, we should not rely on the adjustment of third-party libraries though rc-field-form is also maintained by us. We should solve this problem fundamentally, so that ConfigProvider no longer depends on FormProvider. The implementation is also very simple. Since this is unique to rc-field-form, we directly extract a Context, so that ConfigProvider no longer perceives FormProvider:

    tsx
    // Sample only. Not real world code.
    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 also consumes the proxy Context:

    tsx
    // Sample only. Not real world code.
    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>
    );
    };

    Decomposing the dependencies in this way:

    New Deps

    Final

    Tree Shaking provides an automated way to optimize bundle size, but we need to pay attention to some details. Otherwise, some dependencies may be incorrectly introduced. Thanks.