TreeShaking&SideEffects

425 阅读3分钟
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。
它依赖于 ES2015 模块语法的 [静态结构](https://exploringjs.com/es6/ch_modules.html#static-module-structure) 特性,例如 import 和 export。
这个术语和概念实际上是由 ES2015 模块打包工具 rollup 普及起来的。

It means that you can determine imports and exports at compile time (statically) – you only need to look at the source code, you don’t have to execute it. ES6 enforces this syntactically: You can only import and export at the top level (never nested inside a conditional statement). And import and export statements have no dynamic parts (no variables etc. are allowed).

这意味着您可以在编译时(静态地)确定导入和导出—您只需要查看源代码,而不必执行它。ES6在语法上强制执行这一点:

  1. 只能在顶层导入和导出(决不能嵌套在条件语句中)
  2. 导入和导出语句没有动态部分(不允许变量等)

tree shaking 和 sideEffects


sideEffects 和 usedExports(更多被认为是 tree shaking)是两种不同的优化方式。 sideEffects 更为有效 是因为它允许跳过整个模块/文件和整个文件子树。

⚠️ 注意:usedExports 依赖于 terser 去检测语句中的副作用。它是一个 JavaScript 任务而且没有像 sideEffects 一样简单直接。 当然,sideEffects要谨慎配置,否则可能导致某些有副作用的文件代码生产环境打包时被意外删除

配置tree shaking

// webpack.config.js
module.exports = {
  // ... 其他配置项
  optimization: {
    // 模块只导出被使用的成员,并标记
    usedExports: true,
    // 压缩输出结果(设置为true时,会将标记的unused harmony export xxx 移除掉)
    minimize: false,
  },
};

image.png

配置sideEffects-将文件标记为 side-effect-free(无副作用)

在一个纯粹的 ESM 模块世界中,很容易识别出哪些文件有副作用。然而,我们的项目无法达到这种纯度,所以,此时有必要提示 webpack compiler 哪些代码是“纯粹部分”。 通过 package.json 的 "sideEffects" 属性,来实现这种方式。

切记:设置sideEffects是整个文件模块级别的,换句话说,如果指定某个模块文件为无副作用,则当import该模块文件时,如果未用任意一个暴露出的方法或属性,则整个文件模块会从最终打包文件中移除;如果有任意一个方法或属性被引用且使用,则文件中的副作用代码仍然会出现在最终的打包文件中。

// 如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false .
// 告知 webpack 它可以安全地删除未用到的 export .
{
  "name": "project-name",
  "sideEffects": false
}

⚠️ 注意,所有导入文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader 并 import 一个 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除。

// 如果你的代码确实有一些副作用,可以改为提供一个数组
// 此数组支持简单的 glob 模式匹配相关文件。
// 其内部使用了 glob-to-regexp(支持:*,**,{a,b},[a-z])。
// 如果匹配模式为 *.css,且不包含 /,将被视为 **/*.css。
{
  "name": "project-name",
  "sideEffects": ["./src/some-side-effectful-file.js", "*.css"]
}

将函数调用标记为无副作用(颗粒度更小)

通过 /*#__PURE__*/ 注释,告诉 webpack 一个函数调用是无副作用的。 它可以被放到函数调用之前,用来标记它们是无副作用的(pure)。传到函数中的入参是无法被刚才的注释所标记,需要单独每一个标记才可以。如果一个没被使用的变量定义的初始值被认为是无副作用的(pure),它会被标记为死代码,不会被执行且会被压缩工具清除掉

// Top Level中的函数执行,除非手动 {/*#[防止被webpack识别]__PURE__*/} 进行标记
// 否则为保证tree shaking误删影响实际代码功能,不会被清除
/*#__PURE__*/ (function () {
  Array.prototype.sideEffectsFn = function () {
    console.log("sideEffectsFn");
  };
})();

image.png

参考文档