原文地址:http://www.2ality.com/2015/12/webpack-tree-shaking.html
校对:Kathy
Rich Harris 的模块打包器 Rollup 普及了 JavaScript 圈内一个重要的特性:Tree-shaking,即从模块包中排除未使用的 exports 项。Rollup 基于 ES6 模块的静态结构(引入和导出不会在运行时改变)来检测哪一个模块没有被使用。
Webpack 的 Tree-shaking 特性目前正在 beta 阶段,这篇博文解释了它是如何工作的,相关的项目代码可以在 Github 上查看:Tree-shaking Demo。
1 Webpack 2 如何消除未使用的导出
Webpack 2 是一个尚处于 beta 阶段的新版本,它通过下面两个步骤来消除未使用的导出:
- 首先,所有 ES6 模块文件会被合并为一个打包文件。在这个文件中在任何地方都没有被引入的导出将不再会被打包(译注:
export
语句将会直接被删除)。 - 其次,通过消除不可到达的代码,将打包文件精简。因此,那些既没有被导出,又没有在其模块中被使用的代码将不会出现在精简的打包文件中。如果没有第一个步骤,那些被导出的不可到达代码永远不会被删除(
export
关键字会访问他们)。
只有模块系统拥有一个静态的结构时,模块加载器才能在构建阶段对没有使用过的 export
语句进行可靠的检测。因此,Webpack 2 可以解析并且理解所有 ES6 代码,并且仅对检测到的 ES6 模块进行 Tree-shaking。但不管怎样,只有 import
和 export
语句会被 Webpack 转译为 ES5,如果希望所有代码都转译为 ES5,你仍然需要一个转译器对剩余部分的代码进行转译。在这篇博文中,我们会使用 Babel 6。
2 输入:ES6 代码
Demo 项目有两个 ES6 模块。
helpers.js
包含辅助函数:
// helpers.js |
main.js
,web 应用的入口:
// main.js |
需要注意的是在 helpers 模块中导出的 bar 在整个项目的任何地方都没有被使用过。
3 不使用 Tree-shaking 的输出
Babel 6 的典型选择是使用 es2015 这个 preset:
{ |
不管怎样,这一 preset 包含了 transform-es2015-modules-commonjs 这个插件,这意味着 Babel 会输出 CommonJS 规范的模块,这时 Webpack 无法进行 Tree-shaking:
function(module, exports) { |
你可以看到在代码精简的过程中,bar
作为导出的一部分使它不能被识别为不可访问的代码。
4 使用 Tree-shaking 的输出
我们需要的是 Babel 的 es2015,并不需要其中的 transform-es2015-modules-commonjs 插件。这时,实现以上需求的唯一途径是在配置中声明除 transform-es2015-modules-commonjs 以外的所有的插件。preset 的源代码托管在 Gibhub 上,所以我们这里复制粘贴需要的插件:
{ |
如果现在我们构建这个项目,helper 模块在打包文件中看起来就像这样:
function(module, exports, __webpack_require__) { |
现在,只有 foo
被导出了,但 bar
的代码仍然存在。经过代码压缩,helpers 看起来就像这样:
function (t, n, r) { |
Et voilà – 再也没有 bar
方法了!