WebPack Tree Shaking
WebPack Tree Shaking
tree shaking 是术语,通常用于描述移除 JavaScript 上下文中的未引用(dead-code)。它依赖于 ES2015 模块系统中的,例如 和 。这个术语和概念实际上是兴起于 ES2015 模块打包工具 。
新的 webpack 4 正式版本,扩展了这个检测能力,通过 package.json 的 "sideEffects" 作为,向 compiler 提供,表明项目中的哪些是 "pure(纯的 ES2015 模块)",由此可以安全地中未使用的部分。
本指南的继承自起步指南。如果你尚未阅读该指南,请先行阅读。
通用模块
在我们的项目中新的通用模块 src/math.js,此导出两个:
project
webpack-demo |- package.json |- webpack.con.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 是由当前 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 模块中 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 中。我们将在下一节中这个问题。
将为无副作用(side-effect-free)
在纯粹的 ESM 模块世界中,识别出哪些有副作用很简单。然而,我们的项目无法达到这种纯度,所以,此时有必要向 webpack 的 compiler 提供哪些是“纯粹部分”。
这种方式是通过 package.json 的 "sideEffects" 来实现的。
{ "name": "your-project", "sideEffects": false}
如同上面提到的,如果所有都不包含副作用,我们就可以简单地将该为 false,来告知 webpack,它可以安全地未用到的 export 导出。
「副作用」的定义是,在导入时会执行特殊行为的,而不是仅仅暴露 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
如果你的确实有一些副作用,那么可以改为提供数组:
{ "name": "your-project", "sideEffects": [ "./src/some-side-effectful-file.js" ]}
数组方式相关的相对路径、和 glob 模式。它在内部使用 。
注意,任何导入的都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 loader 并导入 CSS ,则需要将其到 side effect 列表中,以免在生产模式中无意中将它:
{ "name": "your-project", "sideEffects": [ "./src/some-side-effectful-file.js", "*.css" ]}
最后,还可以在 中设置 "sideEffects"。
压缩
通过如上方式,我们已经可以通过 import 和 export 语法,找出那些需要的“未使用(dead code)”,然而,我们不只是要找出,还需要在 bundle 中它们。为此,我们将使用 -p(production) 这个 webpack 编译,来启用 uglifyjs 压缩。
注意,--optimize-minimize 也会在 webpack 内部 UglifyJsPlugin。
从 webpack 4 开始,也可以通过 "mode" 配置选项轻松切换到压缩,只需设置为 "production"。
webpack.con.js
const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist')- }+ },+ mode: "production"};
注意,也可以在命令行接口中使用 --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 产生显著的体积优化。
结论
为了学会使用 tree shaking,你必须……
使用 ES2015 模块语法(即 import 和 export)。
在项目 package.json 中, "sideEffects" 入口。
引入能够未引用(dead code)的压缩工具(minifier)(例如 UglifyJSPlugin)。
你可以将应用程序想象成一棵树。绿色表示实际用到的源码和 library,是树上活的树叶。灰色表示无用的,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。
如果你对优化很感兴趣的话,请进入到下指南,来了解生产环境中进行构建的详细细节。