tree-shaking 理解

1,734 阅读2分钟

什么是tree-shaking

tree-shaking是一种Dead Code Elimination技术,它会在打包过程中静态分析模块之间的导入导出,确定哪些模块导出值没有被使用,并将其删除,从而实现了打包产物的优化。

tree-shaking可以实现的基础

在之前CommonJs、AMD、CMD的模块化方案中,导入导出是高度动态的,难以预测的,因此在打包阶段,是无法分析哪些模块被使用,例如:

if(someTrue){
  require('./bar');
  exports.foo = 'foo';
}

而ES module方案下,模块之间的依赖关系是高度确定的,与运行状态无关,因此可以做到在编译时候 分析ESM的模块,可以从代码字面量中推断出哪些模块没有被使用,这就是tree-shaking实现的必要条件。

tree-shaking的作用示例

可以看到示例中,bar.js中导出了bar``foo,而只有bar在index.js中使用过,foo从未被用过,经过tree-shaking处理后,foo变量会被视作无用代码删除。

// index.js
import {bar} from './bar';
console.log(bar);

// bar.js
export const bar = 'bar';
export const foo = 'foo';

webpack的tree-shaking实现步骤

webpack中实现tree-shaking总述: webpack 先标记出模块导出值中哪些没有被用过,然后使用Terser删掉这些没用的。 具体过程:

  • make阶段,收集模块导出,并记录到模块依赖关系图ModuleGraph 变量中
  • Seal阶段, 遍历ModuleGraph,标记没有被用到过的导出。
  • 生成产物阶段, 删除有标记的导出语句。

webpack给打了个什么样子的标记呢(unused harmony export XXX)?看图:

image.png

webpack中最佳实践

虽然webpack自2.x就开始原生支持tree-shaking,但是受限于js的动态特性,直至5.0依然没有解决需要代码副作用带来的问题,使得优化效果不如原本预想的那么完美,所以需要开发者有意识的优化代码结构,去帮助webpack更精准的检测无效代码。

  1. 避免无意义的赋值
  2. 使用#pure标注纯函数调用
  3. 禁止Bebal转译模块导入导出语句: Bebal 可以将 ESM风格模块,转成 CommonJs风格,Webpack无法对CommonJs风格tree-shaking
  4. 优化导出值粒度:export default的值会被完整保留,比如:
export default {
    bar: 'bar',
    foo: 'foo'
}

应改成:

const bar = 'bar'
const foo = 'foo'

export {
    bar,
    foo
}
  1. 使用支持tree-shaking的包,比如使用 lodash-es 替代 lodash

webpack开启tree-shaking的配置

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

  • 使用 ESM 规范编写模块代码
  • webpack配置optimization.usedExportstrue,启动标记功能。
  • 启动代码优化功能,可以通过如下方式之一即可实现:
    1. 配置 mode = production
    2. 配置 optimization.minimize = true
    3. 提供 optimization.minimizer 数组 例如:
    // webpack.config.js
      module.exports = {
        entry: "./src/index",
        mode: "production",
        devtool: false,
        optimization: {
          usedExports: true,
        },
      };