webpack之treeShaking

使用 Tree-Shaking 删除多余模块导出

Tree-Shaking 较早前由 Rich Harris 在 Rollup 中率先实现,Webpack 自 2.0 版本开始接入,是一种基于 ES Module 规范的 Dead Code Elimination 技术,它会在运行过程中静态分析模块之间的导入导出,判断哪些模块导出值没有被其它模块使用 —— 相当于模块层面的 Dead Code,并将其删除.

Webpack 中,Tree-shaking 的实现,一是需要先 「标记」 出模块导出值中哪些没有被用过;二是使用代码压缩插件 —— 如 Terser 删掉这些没被用到的导出变量。

在 Webpack 中,启动 Tree Shaking 功能必须同时满足三个条件:

  • 使用 ESM 规范编写模块代码;
  • 配置 optimization.usedExports 为 true,启动标记功能;
  • 启动代码优化功能,可以通过如下方式实现:
    • 配置 mode = production;
    • 配置 optimization.minimize = true ;
    • 提供 optimization.minimizer 数组。
// webpack.config.js
module.exports = {
  mode: "production", // 生产模式自动开启
  optimization: {
    usedExports: true,
  },
};

在 CommonJs、AMD、CMD 等旧版本的 JavaScript 模块化方案中,导入导出行为是高度动态,难以预测的;比如将 require() 语句写在判断逻辑中。而 ESM 方案要求所有的导入导出语句只能出现在模块顶层,模块之间的依赖关系是高度确定的,与运行状态无关。

最佳实践

  1. 始终使用ESM导入模块
  2. 使用 #pure 标注纯函数调用,JavaScript 中的函数调用语句也可能产生副作用,因此默认情况下 Webpack 并不会对函数调用做 Tree Shaking 操作。不过,开发者可以在调用语句前添加 /*#__PURE__*/ 备注,明确告诉 Webpack 该次函数调用并不会对上下文环境产生副作用。
  3. 禁止 Babel 转译模块导入导出语句,@babel/preset-env设置modules为commonjs是编译成commonjs风格的代码,webpack无法做模块静态分析.
  4. 使用支持 tree-shaking 的包,如使用lodash-es替代lodash

综上,Tree-Shaking 是一种只对 ESM 有效的 Dead Code Elimination 技术,它能够自动删除无效(没有被使用,且没有副作用)的模块导出变量,优化产物体积。不过,受限于 JavaScript 语言灵活性所带来的高度动态特性,Tree-Shaking 并不能完美删除所有无效的模块导出,需要我们在业务代码中遵循若干最佳实践规则,帮助 Tree-Shaking 更好地运行。

使用 Scope Hoisting 合并模块

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');

module.exports = {
    // 方法1: 将 `mode` 设置为 production,即可开启
    mode: "production",
    // 方法2: 将 `optimization.concatenateModules` 设置为 true
    optimization: {
        concatenateModules: true,
        usedExports: true,
        providedExports: true,
    },
    // 方法3: 直接使用 `ModuleConcatenationPlugin` 插件
    plugins: [new ModuleConcatenationPlugin()]
};