摇树优化 | Tree Shaking
Tree Shaking
tree shaking
是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import
和 export
。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。
webpack 2发行版内置了对ES2015模块(别名 harmony 模块
)的支持以及未使用的模块导出检测。
本指南的其余部分将来自入门。如果您尚未阅读该指南,请现在就这样做。
在我们的项目中添加一个新的通用模块文件 src/math.js
,此文件导出两个函数:
项目
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;
}
接着,更新入口脚本,使用其中一个新方法,并且为了简单,将 lodash
删除:
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()
注意,我们__并未从 src/math.js
模块中 import
导入 square
方法__。这个功能是所谓的“未引用代码(dead code)”,也就是说,应该删除掉未被引用的 export
。现在让我们运行我们的npm 脚本 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;
}
注意,上面的 unused harmony export square
注释。如果你看下面的代码,你会注意到 square
没有被导入,但是,它仍然被包含在 bundle 中。我们将在下一节中解决这个问题。
压缩输出
通过如上方式,我们已经可以通过 import
和 export
语法,找出那些需要删除的“未使用代码(dead code)”,然而,我们不只是要找出,还需要在 bundle 中删除它们。为此,我们将使用 -p
(production) 这个 webpack 编译标记,来启用 uglifyjs 压缩插件。
让我们开始安装它:
npm i --save-dev uglifyjs-webpack-plugin
然后将其添加到我们的配置中:
webpack.config.js
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
import * as mylib from 'mylib';
export const someVar = mylib.transform{
// ...
}
export const someOtherVar = mylib.transform{
// ...
}
index.js
import { someVar } from './transforms.js';
// Use `someVar`...
在上面的代码中,webpack无法确定调用是否mylib.transform
触发任何副作用。结果,它在安全方面发生了错误,并退出someOtherVar
了捆绑代码。
一般来说,当一个工具无法保证特定的代码路径不会导致副作用时,即使您确信它不应该,该代码仍会保留在生成的包中。常见情况包括调用webpack和/或缩小器无法检查的第三方模块的功能,重新导出从第三方模块导入的功能等。
本指南中使用的代码假设您使用UglifyJS插件执行树状移动。但是,还有其他工具,例如webpack-rollup-loader或Babel Minify Webpack插件,根据您的设置可能会产生不同的结果。
结论
所以,我们所学到的是,为了利用 tree shaking
,你必须......
- 使用ES2015模块语法(即
import
和export
)。
- 包括支持死代码删除的缩小器(例如,
UglifyJSPlugin
)。
您可以将您的应用程序想象为一棵树。您实际使用的源代码和库代表树的绿色活叶。死代码表示秋季消耗的棕色枯叶。为了摆脱死叶,你必须摇动树,导致它们倒下。
如果您对更多优化输出的方法感兴趣,请跳到下一个指南,了解有关构建生产的详细信息。