在现代 JS 应用中,通过模块化打包工具可以自动将一些未使用的模块代码去除,这个过程叫做 Tree Shaking 。不过如果你已经对此非常熟悉了,你会发现现实中它并不是那么完美,我们还需要一些额外的操作才能达到最佳的体积优化效果。今天,我们就来讲讲一个 ConfigProvider 导致的 Tree Shaking 失效的问题。
在日常维护中,我们遇到了一些关于使用 ConfigProvider 会导致 BundleSize 变大的问题:
而社区在反馈的同时也把被错误打包进来的包找了出来 rc-field-form,这里我们就直接借用一下 issue 中的图示:
ConfigProvider 提供了全局化配置能力,其中也包含了 Form 组件校验信息的自定义模板配置:
< ConfigProvider   form = { {  validateMessages  } }   /> 
由于该功能对表单的校验有依赖关系,因而它由底层的 rc-field-form 提供的 FormProvider 实现。在 antd 中会先和自身的本地化后的 validateMessages 做聚合:
import   {   FormProvider   }   from   'rc-field-form' ; 
const   ConfigProvider   =   ( {  validateMessages ,  children  } )   =>   { 
   const  mergedValidateMessages  =   React . useMemo ( 
     ( )   =>   merge ( antdDefaultValidateMessages ,  validateMessages ) , 
     < FormProvider   validateMessages = { mergedValidateMessages } > 
       < SomeOtherProvider > { children } </ SomeOtherProvider > 
而 FormProvider 本身又对 rc-field-form 的 FormContext 进行了封装,导致引入 FormProvider 后将 rc-field-form 的更多内容给打包进来了:
你或许会想到,我们是不是可以做一个优化,如果没有配置 validateMessages 我们就不调用这个 FormProvider?
import   {   FormProvider   }   from   'rc-field-form' ; 
const   ConfigProvider   =   ( {  validateMessages ,  children  } )   =>   { 
    node  =   < FormProvider   validateMessages = { merge ( ... ) } > { node } </ FormProvider > ; 
很遗憾,这是不行的。Tree Shaking 是静态编译过程,而 validateMessages 是一个运行时的配置。所以在打包过程中,我们无法知道 validateMessages 是否存在,因而无法做到这样的优化。
rc-field-form 自然可以调整依赖,让 FormProvider 解耦。但是很明显,我们不应该依赖于第三方库的调整(虽然 rc-field-form 也由我们来维护)。我们应该从根本上解决这个问题,让 ConfigProvider 不再依赖于 FormProvider。实现也非常简单,既然这是 rc-field-form 所独有的,那么我们直接抽一个 Context 出来,让 ConfigProvider 不再感知 FormProvider 即可:
import   {   ValidateMessageContext   }   from   '../form/context.ts' ; 
const   ConfigProvider   =   ( {  validateMessages ,  children  } )   =>   { 
   const  mergedValidateMessages  =   ... 
     < 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 ) ; 
     < FormProvider   validateMessages = { validateMessages } > 
依赖就变成了这样,从而实现了解耦:
Tree Shaking 提供了一种自动化的方式来优化打包体积,但是我们需要注意一些细节。否则可能不慎导致一些依赖被错误的引入。以上。