webpack 摇树处理

3,691 阅读4分钟

webpack 摇树处理 Tree Shaking

GitHub 学习 Demo。

摇树处理 Tree Shaking 是一项在消除 Javascript 上下文中死代码的技术。 它依赖于ES2015模块语法的静态结构,即 importexport。这个专业名词被 ES2015 module bundler rollup 所汇总推广了。

webpack 2版本自带内置支持ES2015模块(alias harmony modules 别名和声模块)以及未使用的模块导出检测。

新的webpack 4版本扩展了这一功能,通过 package.jsonsideEffects 属性向编译器提供提示,表示项目中的哪些文件是“pure 纯粹的”,因此如果未使用则可以安全修剪。

修改项目

webpack-demo
|- package.json
|- webpack.config.js
|- /dist
  |- bundle.js
  |- index.html
|- /src
  |- index.js
+ |- math.js
|- /node_modules

# src/math.js

export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

# webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
- }
+ },
+ mode: 'development',
+ optimization: {
+   usedExports: true
+ }
};

# src/index.js

- import _ from 'lodash';
+ import { cube } from './math.js';

  function component() {
-   var element = document.createElement('div');
+   var element = document.createElement('pre');

-   // Lodash, now imported by this script
-   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+   element.innerHTML = [
+     'Hello webpack!',
+     '5 cubed is equal to ' + cube(5)
+   ].join('\n\n');

    return element;
  }

  document.body.appendChild(component());

记住,现在并没有 import square 方法。 这段代码就是死代码, 没有没使用的 export 代码应该被裁剪掉.现在运行 npm run build, 并检查 不能 bundle 文件:

# dist/bundle.js (around lines 90 - 100)

/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
  'use strict';
  /* unused harmony export square */
  /* harmony export (immutable) */ __webpack_exports__['a'] = cube;
  function square(x) {
    return x * x;
  }

  function cube(x) {
    return x * x * x;
  }
});

你会注意到没有 imprtsquare 方法,仍然被捆绑在bundle中,并标有 unused harmony export square 备注代码未被导入。我们将在下一节中解决这个问题。

设置 sideEffects 选项

在 100% 的 ESM 模块世界中,识别副作用很简单。 但是,我们还没有,所以在同时,有必要为 webpack 的编译器提供关于代码“pureness 纯度”的提示。

方法是,向 package.json 添加 sideEffects 属性:

# package.json

{
  "name": "your-project",
  "sideEffects": false
}

上面提到的所有代码都不包含副作用,因此我们可以简单地将该属性标记为 false ,以通知 webpack 它可以安全地修剪未使用的导出。

tips:
“副作用”的定义是,在导入时执行特殊行为的代码,而不是公开一个或多个导出。 一个例子是 polyfill ,它影响全局范围,通常不提供导出。

如果您的代码确实有一些副作用,可以提供一个数组:

# package.json

{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js"
  ]
}

这个参数接受相对,绝对路径,或全局变量的数字作为键值。使用的方式是 micromatch

请注意,任何导入的文件都会受到树抖动的影响。 这意味着如果在项目中使用类似css-loader的东西并导入 CSS 文件,则需要将其添加到副作用列表中,这样就不会在生产模式下无意中丢失代码:

# package.json

{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js",
    "*.css"
  ]
}

最后,"sideEffects" 也可以设置在 module.rules 的选项中。

最小化输出 Minify the Output

所以我们通过使用importexport语法来提取我们的“死代码”,但我们仍然需要从捆绑中删除它。 为此,请将模式配置选项设置为生产配置选项。

# webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
- mode: 'development',
- optimization: {
-   usedExports: true
- }
+ mode: 'production'
};

我们可以运行npm run build,看看是否有任何变化。

注意 dist/bundle.js 有什么不同吗?
很明显,整个捆绑包现在缩小并混淆了,但是,如果你仔细观察,你将看不到包 含 原 squre 方法,但会看到一个混淆版本的squre 方法(funtion r(e){return e * e * e}NA = R)。 随着缩小和树摇动,我们的捆绑现在变小了几个字节! 虽然在这个人为的例子中看起来似乎并不多,但是当处理具有复杂依赖树的较大应用程序时,效果将更加的明显。

tips:
树摇动工作需要 ModuleConcatenationPlugin,可以通过把 mode 切换为 production 生效。
也可以手动添加 ModuleConcatenationPlugin

总结

要想使用 tree shaking, 你必须:

  • 使用 ES2015 module 语法 importexport
  • 确保没有编译器将您的 ES2015 模块语法转换为 CommonJS 模块,(这是流行的 Babel 预设 @babel/preset-env 的默认行为, 有关更多详细信息,请参阅文档
  • 向 package.json 添加 sideEffects 属性。
  • 使用生产模式配置选项启用各种优化(mode: "production"),包括缩小和树抖动。

如果您对更多优化输出的方法感兴趣,请跳至下一个指南,了解有关生产的详细信息