Tree Shaking?是一个术语,字面的理解就是摇一摇树,树上的枯叶就会掉下来,留下绿叶。
Rich Harris 的模块打包器 Rollup 普及了 JavaScript 圈内一个重要的特性:Tree shaking,即是指消除JavaScript上下文中无用代码,或更精确地说,只保留有用的代码。它依赖于ES6模块 import / export ?模块系统的静态结构(static structure)来检测哪一个模块没有被使用,因为,import 和 export 不会在运行时改变。说的再直白一点就是Tree shaking 从模块包中排除未使用的 exports 项。
webpack 2 内置引入的 Tree-shaking 代码优化技术。
示例
看一下官方文档提供的示例:
考虑一个 maths.js 库文件导出两个函数,square
和 cube
:
// 这个函数不在任何地方被使用 export function square(x) { return x * x; //平方 } // 这个函数被其他脚本使用 export function cube(x) { return x * x * x; //立方 }
在我们的 main.js 中,我们选择性地导入 cube
:
import {cube} from './maths.js'; console.log(cube(5)); // 125
运行 node_modules/.bin/webpack main.js dist.js
并检查 dist.js
显示没有导出square
,请查看 “unused harmony export square”的注释,再查看/* harmony export (immutable) */ __webpack_exports__["a"] = cube;
这条语句,显示这里只导出了cube
:
/* ... webpackBootstrap ... */ /******/ ([ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* unused harmony export square */ /* harmony export (immutable) */ __webpack_exports__["a"] = cube; // This function isn't used anywhere function square(x) { return x * x; } // This function gets included function cube(x) { return x * x * x; } /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__maths_js__ = __webpack_require__(0); console.log(__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__maths_js__["a" /* cube */])(5)); // 125 /***/ })
当运行生产环境构建,node_modules/.bin/webpack --optimize-minimize main.js dist.min.js
时,只保留了 cube
的压缩版本,而 square
并没有保留在构建的 bundle 中:
/* ... */ function(e,t,n){"use strict";function r(e){return e*e*e}t.a=r} /* ... */ function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(0);console.log(n.i(r.a)(5))}
讨论
大家可以看看关于Tree Shaking 的讨论: 如何评价 Webpack 2 新引入的 Tree-shaking 代码优化技术?
赞同尤雨溪的观点
指出的一点就是不管是 rollup 还是 webpack 2,tree-shaking 都是因为 ES6 modules 的静态特性才得以实现的。ES6 modules 的 import 和 export statements 相比完全动态的 CommonJS require,有着本质的区别。举例来说:
1. 只能作为模块顶层的语句出现,不能出现在 function 里面或是 if 里面。(ECMA-262 15.2)
2. import 的模块名只能是字符串常量。(ECMA-262 15.2.2)
3. 不管 import 的语句出现的位置在哪里,在模块初始化的时候所有的 import 都必须已经导入完成。换句话说,ES6 imports are hoisted。(ECMA-262 15.2.1.16.4 – 8.a)
4. import binding 是 immutable 的,类似 const。比如说你不能 import { a } from ‘./a’ 然后给 a 赋值个其他什么东西。(ECMA-262 15.2.1.16.4 – 12.c.3)这些设计虽然使得灵活性不如 CommonJS 的 require,但却保证了 ES6 modules 的依赖关系是确定 (deterministic) 的,和运行时的状态无关,从而也就保证了 ES6 modules 是可以进行可靠的静态分析的。对于主要在服务端运行的 Node 来说,所有的代码都在本地,按需动态 require 即可,但对于要下发到客户端的 web 代码而言,要做到高效的按需使用,不能等到代码执行了才知道模块的依赖,必须要从模块的静态分析入手。这是 ES6 modules 在设计时的一个重要考量,也是为什么没有直接采用 CommonJS。
正是基于这个基础上,才使得 tree-shaking 成为可能(这也是为什么 rollup 和 webpack 2 都要用 ES6 module syntax 才能 tree-shaking),所以说与其说 tree-shaking 这个技术怎么了不起,不如说是 ES6 module 的设计在模块静态分析上的种种考量值得赞赏。
最新评论
写的挺好的
有没有兴趣翻译 impatient js? https://exploringjs.com/impatient-js/index.html
Flexbox playground is so great!
感谢总结。
awesome!
这个好像很早就看到类似的文章了
比其他的教程好太多了
柯理化讲的好模糊…没懂