首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

摇树优化 | Tree Shaking

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 importexport。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup

webpack 2发行版内置了对ES2015模块(别名 harmony 模块)的支持以及未使用的模块导出检测。

本指南的其余部分将来自入门。如果您尚未阅读该指南,请现在就这样做。

添加一个通用模块

在我们的项目中添加一个新的通用模块文件 src/math.js,此文件导出两个函数:

项目

代码语言:javascript
复制
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
  |- bundle.js
  |- index.html
|- /src
  |- index.js
  |- math.js
|- /node_modules

src/math.js

代码语言:javascript
复制
export function square(x) {
  return x * x;
}

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

接着,更新入口脚本,使用其中一个新方法,并且为了简单,将 lodash 删除:

src/index.js

代码语言:javascript
复制
- 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());

注意,我们__并未从 src/math.js 模块中 import 导入 square 方法__。这个功能是所谓的“未引用代码(dead code)”,也就是说,应该删除掉未被引用的 export。现在让我们运行我们的npm 脚本 npm run build,并检查输出的 bundle:

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

代码语言:javascript
复制
/* 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;
}

注意,上面的 unused harmony export square 注释。如果你看下面的代码,你会注意到 square 没有被导入,但是,它仍然被包含在 bundle 中。我们将在下一节中解决这个问题。

压缩输出

通过如上方式,我们已经可以通过 importexport 语法,找出那些需要删除的“未使用代码(dead code)”,然而,我们不只是要找出,还需要在 bundle 中删除它们。为此,我们将使用 -p(production) 这个 webpack 编译标记,来启用 uglifyjs 压缩插件。

让我们开始安装它:

代码语言:javascript
复制
npm i --save-dev uglifyjs-webpack-plugin

然后将其添加到我们的配置中:

webpack.config.js

代码语言:javascript
复制
const path = require('path');
+ const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
- }
+ },
+ plugins: [
+   new UglifyJSPlugin()
+ ]
};

注意,也可以在命令行接口中使用 --optimize-minimize 标记,来使用 UglifyJSPlugin

准备就绪后,然后运行另一个命令 npm run build,看看输出结果有没有发生改变。

你发现 dist/bundle.js 中的差异了吗?显然,现在整个 bundle 都已经被精简过,但是如果仔细观察,则不会看到 square 函数被引入,但会看到 cube 函数的修改版本(function r(e){return e*e*e}n.a=r)。现在,随着 tree shaking 和代码压缩,我们的 bundle 减小几个字节!虽然,在这个特定示例中,可能看起来没有减少很多,但是,在具有复杂的依赖树的大型应用程序上运行时,tree shaking 或许会对 bundle 产生显著的体积优化。

注意事项

请注意,webpack不会自行执行树状结构。它依赖于像UglifyJS这样的第三方工具来执行实际的死代码消除。有些情况下,树木摇晃可能无效。例如,请考虑以下模块:

transforms.js

代码语言:javascript
复制
import * as mylib from 'mylib';

export const someVar = mylib.transform({
  // ...
});

export const someOtherVar = mylib.transform({
  // ...
});

index.js

代码语言:javascript
复制
import { someVar } from './transforms.js';

// Use `someVar`...

在上面的代码中,webpack无法确定调用是否mylib.transform触发任何副作用。结果,它在安全方面发生了错误,并退出someOtherVar了捆绑代码。

一般来说,当一个工具无法保证特定的代码路径不会导致副作用时,即使您确信它不应该,该代码仍会保留在生成的包中。常见情况包括调用webpack和/或缩小器无法检查的第三方模块的功能,重新导出从第三方模块导入的功能等。

本指南中使用的代码假设您使用UglifyJS插件执行树状移动。但是,还有其他工具,例如webpack-rollup-loader或Babel Minify Webpack插件,根据您的设置可能会产生不同的结果。

结论

所以,我们所学到的是,为了利用 tree shaking,你必须......

  • 使用ES2015模块语法(即importexport)。
  • 包括支持死代码删除的缩小器(例如,UglifyJSPlugin)。

您可以将您的应用程序想象为一棵树。您实际使用的源代码和库代表树的绿色活叶。死代码表示秋季消耗的棕色枯叶。为了摆脱死叶,你必须摇动树,导致它们倒下。

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

扫码关注腾讯云开发者

领取腾讯云代金券

http://www.vxiaotou.com