在这一部分的webpack 4 教程中,我们继续进一步考虑优化。我们要学习“tree shaking”是啥以及如何使用。你会学到在webpack 4中使用“tree shaking”技术有什么要求,它能带来什么好处。开始吧!
Webpack 4 Tree Shaking
首先,我们回答“tree shaking”是啥技术以及它能带来什么好处。我们常常碰到这样的案例,需要从某文件中命名导出(某一个或几个变量、函数、对象等),然而这个文件还有许多其它(我们这次并不需要)的导出,webpack会不管三七二十一简单粗暴的将整个模块包含进来,使得我们最终打包的文件里有了许多不需要的垃圾。这就到了tree shaking出手的地方了,因为它能帮助我们干掉那些死代码,大大减少打包的尺寸。
如果你想对imports和exports了解更多,请查阅本教程1。
要想让tree shaking能“摇起来”,有几个要求,首先,必须使用ES6模块,不能使用其它类型的模块如CommonJS之流。如果使用Babel的话,这里有一个小问题,因为Babel的预案(preset)默认会将任何模块类型都转译成CommonJS类型。修正这个问题也很简单,不是在.babelrc文件中就是在webpack.config.js文件中设置modules: false就好了。
// .babelrc
{
"presets": [
["env",
{
"modules": false
}
]
]
}
// webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', { modules: false }]
}
}
}
]
},
如果你想阅读更多有关babel-loader或loaders的一般用法,请看本教程之2
第二个要求,需要使用UglifyJsPlugin插件。如果在mode:"production"模式,这个插件已经默认添加了,如果在其它模式下,可以手工添加它。
不熟悉UglifyJsPlugin插件的话,请参阅本教程5
另外要记住的是打开optimization.usedExports。在mode: "production"模式下,它也是默认打开了的。它告诉webpack每个模块明确使用exports。这样之后,webpack会在打包文件中添加诸如/* unused harmony export */
这样的注释,其后UglifyJsPlugin插件会对这些注释作出理解。
Harmony 是 ES6 和 ES2015的代号。
我们来看看下面的情形:
// utilities.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// index.js
import { add } from './utilities';
console.log(add(1,2));
console.log(add(3,4));
配置不恰当的运行就会有如下的输出:
/*(...)*/
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "add", function() { return add; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "substract", function() { return substract; });
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
/***/ })
/******/ ]);
正如你所见,webpack没有“摇”我们的包!这里既有add函数又有subtract函数。再使用如下的配置我们来体验一点点不同:
// webpack.config.js
const webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const UglifyJS = require('uglify-es');
const DefaultUglifyJsOptions = UglifyJS.default_options();
const compress = DefaultUglifyJsOptions.compress;
for(let compressOption in compress) {
compress[compressOption] = false;
}
compress.unused = true;
module.exports = {
mode: 'none',
optimization: {
minimize: true,
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
compress,
mangle: false,
output: {
beautify: true
}
},
})
],
}
}
我关掉了UglifyJsPlugin插件的大部分选项配置以便更清楚的观察代码中发生了什么。运行之后有下面的输出:
/* (...) */
/* 0 */
/***/ function() {
"use strict";
// CONCATENATED MODULE: ./src/utilities.js
function add(a, b) {
return a + b;
}
// CONCATENATED MODULE: ./src/index.js
console.log(add(1, 2));
console.log(add(3, 4));
/***/}
/******/ ]);
正因为optimization.usedExports和UglifyJsPlugin插件关掉的配置项,垃圾代码清除了。请注意,这是UglifyJsPlugin插件的默认行为,所以默认配置下使用UglifyJsPlugin也将清除死代码(除非运行了其它的压缩进程)。
库的Tree Shaking
如果你想要对库进行tree shake,首先要记住的注意点还是前面所说的:使用ES6模块。然而许多库并不一定使用ES6模块,典型的例子比如lodash就是这样,看它的源代码,很明显它没有使用ES6模块。
假设要使用lodash库中的debounce函数。
// index.js
import _ from 'lodash';
console.log(_.debounce);
现在你的输出里把整个lodash都打进来了。当使用import _ from 'lodash'时没有办法避免这一点。还好,有人创建了一个叫lodash-es的库,它改写lodash按ES6模块的形式导出。
import { debounce } from 'lodash';// (译注:原文如此,应该为'lodash-es')
console.log(debounce);
很不幸,webpack依旧没有对其tree shake。根据ECMAScript规范,所有的子模块都需要去评估,因为它们可能有副作用。我推荐读一下Sean Larking(他是webpack核心团队成员之一)在Stack Overflow上写的这篇很好的解释。库的作者可以在package.json文件里注明它的库没有副作用。打开lodash库的package.json文件,可以看到有"sideEffects": false的标注。那么这儿的问题是什么?
Webpack默认忽略了sideEffect标注,改变此行为需要设置optimization.sideEffects为true。你能手工设置它或通过设置mode:"production"模式也行。
// webpack.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'none',
optimization: {
minimize: true,
minimizer: [
new UglifyJsPlugin()
],
usedExports: true,
sideEffects: true
},
plugins: [
new HtmlWebpackPlugin()
]
}
现在webpack终于对lodash库“摇”起来了。
小结
要让tree shaking好好工作,有一定条件。这么很有用的功能,当然值得学习。希望通过这篇文章,你会知道怎么去使用它,因为它能大大减少你打包的体积。记住需要使用ES6模块和UglifyJsPlugin插件,以及配置optimization选项,设置usedExports和sideEffects为true。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。