TreeShaking 必知必会的理论
Tree Shaking 为什么要依赖 ESM 规范?
Tree Shaking 是在编译时进行未引用代码消除的。因此它需要在编译时确定依赖关系,进而确定哪些代码可以被“摇掉”,ESM 规范具有以下特点。
import 模块只能是字符串常量。
import 一般只能在模块的顶层出现。
import 依赖的内容是不可变的。
ESM 规范具有静态分析能力,而 CommonJS 定义的模块化规范,只有在执行代码后才能确定依赖模块,因此不具备 TreeShaking 的先天条件。
什么是副作用模块,如何对副作用模块进行 Tree Shaking 操作?
其实 Tree Shaking 无法摇掉副作用模块。为了解决这个问题,可以利用 package.json 的 sideEffects 属性来告诉工程化工具,哪些模块有副作用,哪些模块没有副作用可以被优化。
1 | { |
以上代码表示全部模块均没有副作用,告知 webpack 可以安全的删除没有用的模块。所以在业务项目中,设置最小化副作用范围,同时通过合理的配置,给工程化工具最多的副作用信息。
TreeShaking 友好的导出模式
1 | export default { |
对于上述两段代码,webpack 会趋向于保留整个默认导出对象或者类,因此以下情况都不利于 TreeShaking 处理:
导出一个包含多个属性和方法的对象。
导出一个包含多个属性和方法的类。
使用 export default 方法导出。
更加推荐的做法是遵循原子化和颗粒化规则导出。下面是一个很好的实践:
1 | export function add(a, b) { |
前端工程化生态和 Tree Shaking 实践
Babel 和 Tree Shaking
Babel 默认会将 ESM 规范代码编译为 CommonJS 规范代码。而我们从前面的理论知识可以知道,Tree Shaking 必须依托于 ESM 规范。所以我们需要配置 Babel 对模块化代码的编译降级,具体配置可以在 babel-preset-env#modules 可以找到。
可是这个时候又会出现新的问题,因为有些工具链中的工具要求模块符合 CommonJS 规范,否则就要罢工啦,比如 Jest。因为 Jest 是基于 Node.js 开发的,那么如何处理这种“模块死锁”呢?
思路之一是,根据环境的不同采用不同的 Babel 配置,在 production 编译环境下,我们进行如下配置:
1 | production: { |
在测试环境中,我们进行如下配置:
1 | test: { |
除此之外呢,我们还需要配置 jest,transformIgnorePatterns 是 Jest 的一个配置项,默认值为 node_modules,它表示 node_modules 中的第三方模块代码都不需要经过 babel-jest 编译。
Webpack 和 TreeShaking
webpack4.0 以上版本在 mode 为 Production 时,会自动开启 Tree Shaking 能力。其实 webpack 真正执行 Tree Shaking 时依赖了 TerserPlugin、UgifyJS 等压缩插件。webpack 负责对模块进行标记,而这些压缩插件负责根据标记结果进行代码删除。webpack 在分析时有三类相关标记。
used export: 被使用过的 export 会被标记为 used export。
unused harmony export: 没有被使用过的 export 会被标记为 unused harmony export。
harmony export: 所有 import 会被标记为 harmony export。
在编译分析阶段,webpack 将每一个模块放进 ModuleGraph 中维护。依靠 HarmonyExportSpecifierDependency 和 HarmonyImportSpecifierDependency 分别识别和处理 export 及 import 操作。依靠 HarmonyExportSpecifierDependency 进行 used export 和 unused harmony export 标记。
Vue.js 和 Tree Shaking
1 | import Vue from 'vue' |
在 vue.js2.0 版本中,如果我们没有使用 Vue.nextTick 方法,那么 nextTick 这样的全局 API 就成了未引用代码,不容易被 Tree Shaking 处理。而在 vue.js3.0 版本中,全局 API 需要通过原生 ES Module 方式进行导入。
1 | import { nextTick } from 'vue' |
设计一个兼顾 Tree Shaking 和易用性的公共库
npm script 来解决:
1 | { |
我们通过 main 来暴露 CommonJS 规范代码 dist/index.cjs.js。webpack 等构建工具又支持 module 这个新的入口字段,module 并非是 package.json 的标准字段,而是打包工具专用的字段。