代码拆分;splitChunk ;按需加载;按需打包;动态导入;tree-shaking;antd babel import plugin;

疑问

  • 为什么antd只设置了样式文件的tree shaking?
    • tree shaking在一下章,目前依然不理解
  • tree shaking在dev环境生效吗?
    • tree shaking在下一章,目前的感受是tree shaking只影响产物体积
  • babel插件对AST语法树的改写在dev环境生效吗?

    • 答:babel插件可以配置多种不同的环境,dev、test、production都是通用的。

      摘录&心得

  • 区分按需加载与按需打包

    • 按需加载表示代码模块在交互需要时,动态引入;
    • 按需打包针对第三方依赖库,及业务模块,只打包真正在运行时可能会需要的代码。
  • 按需打包的实现方案
    • 使用 ES Module 支持的 Tree Shaking 方案,在使用构建工具打包时,完成按需打包;
    • 使用以babel-plugin-import为主的 Babel 插件,实现自动按需打包 。

      一、按需打包

      Tree Shaking 实现按需打包

      我们来看一个场景,假设业务中使用 antd 的 Button 组件:
      1. import { Button } from 'antd';
      这样的引用,会使得最终打包的代码中包含所有 antd 导出来的内容。假设应用中并没有使用 antd 提供的 TimePicker 组件,那么对于打包结果来说,无疑增加了代码体积。在这种情况下,如果组件库提供了 ES Module 版本,并开启了 Tree Shaking,我们就可以通过“摇树”特性,将不会被使用的代码在构建阶段移除。
      antd源码中的sideEffects配置如下:
      1. "sideEffects": [
      2. "dist/*",
      3. "es/**/style/*",
      4. "lib/**/style/*",
      5. "*.less"
      6. ],
      上面这段配置代表antd这个包中的dist下所有文件、es/lib style目录下所有文件、所有less文件均有副作用,不能被tree-shaking删除,而其他文件均没有副作用,没引入直接删除即可。之所以会这样,是因为antd没有在任何一个js文件中引用了less文件,全靠babel插件进行样式文件导入,在webpack treeShaking的过程中会由于无法找到引入而删去这些样式文件,导致问题。

      https://webpack.js.org/guides/tree-shaking/image.png

Babel 插件实现按需打包

如果第三方库不支持 Tree Shaking,依然可以通过 Babel 插件,改变业务代码中对模块的引用路径来实现按需打包。比如 babel-plugin-import 这个插件,它是 antd 团队推出的一个 Babel 插件,比如:

  1. import { Button, Input, TimePicker, ConfigProvider } from 'antd'

这样的代码就可以被编译为:

  1. import _ConfigProvider from "antd/lib/config-provider";
  2. import _Button from "antd/lib/button";
  3. import _Input from "antd/lib/input";
  4. import _TimePicker from "antd/lib/time-picker";

这个插件被发布为单独的npm包,因此不但在antd内部可以生效,安装antd后,该包由antd host,可以进而在项目中继续生效,确保无论是dev环境还是生产环境,都可以按需打包。
所以想解决组件库解构引用时的全量文件加载,可以依靠babel插件:开发一个插件并发布,在组件库中引用这个插件,并由组件库host插件,从而实现组件库+项目的解构引用改写。
https://github.com/ant-design/antd-tools/blob/b68ad383bfa9ad234e1ec633b037d24799f8d8e2/lib/getWebpackConfig.js
image.png
然而该插件其实也是一种hack的写法,在webpack4推出以后,可以完全依靠treeShaking+sideEffects实现这个需求。无论哪种方案,在组件库中首先要做的是代码拆分。

实例

babel-helper-module-imports

二、按需加载

  • 静态加载使treeShaking有了应用空间
  • MDN 文档中给出了 dynamic import 更具体的使用场景:

    • 静态导入的模块很明显降低了代码的加载速度且被使用的可能性很低,或者并不需要马上使用它;
    • 静态导入的模块很明显占用了大量系统内存且被使用的可能性很低;
    • 被导入的模块在加载时并不存在,需要异步获取;
    • 导入模块的说明符,需要动态构建(静态导入只能使用静态说明符);
    • 被导入的模块有副作用(可以理解为模块中会直接运行的代码),这些副作用只有在触发某些条件时才被需要。

      动态导入

  • dynamic import 只是一个 function like 的语法形式

    • dynamic import 并非继承自 Function.prototype
    • dynamic import 并非继承自 Object.prototype
  • 原理:webpack_require.e

    • Webpack 对于业务中写到的 dynamic import 代码,会转换成了 Webpack 自己自定义的 webpack_require.e 函数
    • 这个函数返回了一个 promise 数组,最终模拟出了动态导入的效果。
    • 本质上是创建一个 script 标签,加载模块内容,并定义此 script 的 onload 和 onerror 回调

      如何在 Webpack 环境下支持代码拆分和按需加载

      1、通过入口配置手动分割代码
      2、动态导入支持
  • 调用 import(),被请求的模块和它引用的所有子模块,会分离到一个单独的 chunk 中。

  • 可以通过注释接收一些特殊的参数

3、通过 splitChunk 插件提取公共代码

  • 与动态导入是两个概念
    • 动态导入本质上是代码的懒加载——按需加载
    • splitChunk是一种代码拆包技术,与代码打包概念相逆
  • 代码分割的核心意义在于避免重复打包以及提升缓存利用率:
    • 比如拆分不常用的第三方库代码
    • 减少单个文件的size大小
    • 优化首屏加载速度(文件太大会加载时间长,白屏时间长)
  • splitChunk 插件的默认参数是经过“千挑万选”确定的,不建议开发者手动优化这些参数。
  • 可与动态导入配合使用,实现按需加载