Tree Shaking
ps:官方文档中本节目录结构继承于起步。
Tree Shaking是一个常用于移除js环境中无用代码的术语,它依赖于ES2015模块系统中的静态结构特性比如import和export。它的名字和概念其实兴起于ES2015打包工具rollup。
webpack发行后就内置支持ES2015模块和无用模块导出检测。而webpack4又扩展了这个功能:通过package.js的"side effect"属性来提示编译器项目中哪些文件是纯粹的(可以放心地删除)。
添加通用模块
添加一个导出两个函数的通用模块:src/math.js。
project
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; }
把mode设置为开发模式来防止输出包被压缩:
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
- }
+ },
+ mode: 'development'
};
在入口脚本中引入其中一个方法,为了简洁删除loadash:
src/index.js
- import _ from 'lodash';
+ import { cube } from './math.js';
function component() {
- var element = document.createElement('div');
+ var element = document.createElement('pre'); - // lodash 是由当前 script 脚本 import 导入进来的 - 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导入square方法,这个方法就成了未引用代码,也意味着应该被删除。现在构建一下项目观察一下输出包:
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"注释,继续看一下往下的代码你会发现它没有被引用但是输出包里面依然有这个方法,接下来我们会解决这个问题。
将文件标记为无副作用
ps:副作用指的是一个模块被导入时会有一些特殊的行为而不只是暴露出一个或多个export,比如polyfill,它会影响全局作用域而且不提供export。
在一个纯粹的ESM模块世界中,识别副作用很简单,但是我们没法达到这种程度,所以给webpack编译器一些关于你的代码的纯净度的提示是很有必要的。
解决方案就是package.json的"sideEffects"属性:
{
"name": "your-project", "sideEffects": false }
之前的代码都没有什么副作用所以我们可以简单地把属性设置为false来告诉webpack可以安全地删除所有的无用导出模块。
但如果你的代码有一些副作用那你可以提供一个数组:
{
"name": "your-project", "sideEffects": [ "./src/some-side-effectful-file.js" ] }
这个数组支持相关文件的相对路径、绝对路径和glob模式。它内部使用micromatch。
ps:任何导入的文件都会受到tree shaking的影响,也就是说如果你在项目中使用了css-loader导入了css文件,那这些文件也需要添加到tree shaking列表否则它就会在生产模式中被无意删掉。
{
"name": "your-project", "sideEffects": [ "./src/some-side-effectful-file.js", "*.css" ] }
最后,"sideEffects"也可以在module.rules配置选项中设置。
压缩输出:
如上我们已经可以使用import和export找出那些需要被删除的无用代码,但是我们还需要在输出包中删掉它们。为此我们把mode设置为生产模式就可以压缩输出了:
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
- mode: 'development'
+ mode: 'production'
};
ps:也可以在命令行中使用--optimize--minimize标记,它会在内部调用压缩插件UglifyJsPlugin。
现在构建一下项目观察一下结果:现在整个输出包都已经被精简过了,square函数没有被引入而且cube函数也被精简过了(function r(e){return e*e*e}n.a=r
)。经过tree shaking和输出压缩输出包小了几个字节,虽然这不算多但是在大型项目中会起到可观的效果。
结论
为了学会使用 tree shaking,你必须……
- 使用 ES2015 模块语法(即
import
和export
)。 - 在项目
package.json
文件中,添加一个 "sideEffects" 入口。 - 引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如
UglifyJSPlugin
)。
你可以将应用程序想象成一棵树。绿色表示实际用到的源码和 library,是树上活的树叶。灰色表示无用的代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。