引言
Tree shaking是对指模块打包时通过一定方案将模块中无用的代码给剔除, 达到减少代码体积, 加快加载速度的目的;
Tree Shaking 在前端界由 rollup 首先提出并实现,Webpack 在 2.x 版本也借助于 UglifyJS 实现了;
Tree-shaking 的本质是消除无用的 js 代码。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为 DCE(dead code elimination)。
Tree-shaking 是 DCE 的一种新的实现,Javascript 同传统的编程语言不同的是,javascript 绝大多数情况需要通过网络进行加载,然后执行,加载的文件大小越小,整体执行时间更短,所以去除无用代码以减少文件体积,对 javascript 来说更有意义。
作者:百度外卖大前端技术团队
链接:https://juejin.im/post/5a4dc842518825698e7279a9
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在指定了构建环境为生产环境(mode: ‘production’)后, Webpack 会自动执行 tree shaking 和代码压缩等流程, 减少输出 bundle 的体积;
Webpack 中的模块有哪些?
- ES6
import
语句 - CommonJS
require()
语句 - AMD
define
和require
语句 - css/scss/stylus 中的
@import
语句 - css 样式
url(..)
或 HTML<img>
标签中的图片链接
Webpack 的 tree shaking 优化机制
Webpack 的 Tree shaking 机制依赖于 ES6 Module 规范, 因为它有如下几个特点:
- 只能作为模块顶层的语句出现
- import 的模块名只能是字符串常量
- import binding 是 immutable(不能改变的)
- 依赖关系是确定的, 和运行时状态无关, 可以进行可靠的静态分析, 然后消除;
主要特点还是依赖 ES6 的静态分析,在预编译时便确定要引入的模块, 而 CommonJS 语法中require
必须到运行时才能确定模块; 受限于 JavaScript 作为脚本语言的特性;
副作用(side-effects)
如果一个模块(函数)会产生副作用, 那么称这个模块(函数)是不纯的;
纯函数一般在函数式编程中提及较多, 它的定义是:
对于相同的输入有着相同的输出, 不依赖外部环境, 也不会改变外部环境
如果符合以上条件, 这个函数便成为纯函数, 否则就认为这个函数会产生副作用, 是不纯的;
1 | import _ from 'lodash-es' |
而带有副作用的函数, 即使是无用的, 也会影响 Webpack 的 tree shaking;
1 | // module.js |
1 | // main.js |
执行打包后的结果:
可以看到, 即使 index.js 根本没有引入 useless
函数, 打包结果还是包含了额外的 vendors 文件, 因为 useless
函数的副作用导致 tree shaking 失效了;
如果将 module.js 对 lodash
的引用去除:
1 | export const useless = function() { |
可以看到打包结果已经成功 tree shaking:
polyfill
类似 @babel/polyfill
这类垫片库也是产生副作用的对象, 它们并没有直接地暴露 export
, 但会在导入时执行特殊代码, 对全局作用域产生影响(比如为 Array.prototype 挂载新方法, shim 一个 Promise 类等); 然而 Webpack 的 tree shaking 机制并不能判断这个:
1 | // polyfill.js |
上述的文件, Webpack 在打包后会自动将 polyfill.js 的内容删去, 因为 Webpack 判断其并没有export
, 自然理解为无用代码;
解决方法是在package.json
的sideEffects
属性中指明会产生副作用的文件(或者在 Option.rules 中指定), 注意如果通过css-loader
来辅助导入 css 模块时(如 import 'main.css'
), 也要将其指定为副作用文件, 否则 Webpack tree shaking 对其也会生效;
1 | "sideEffects": [ |
总结
不同条件下 tree shaking 的情况:
- Tree shaking 只会对采用 ES6 Module 规范的模块生效
- Tree shaking 对会产生副作用的模块不能准确识别, 无法筛去:
- 尽量编写无副作用的函数
- 但对于 polyfill 这种不 export 而是直接影响全局环境的模块, tree shaking 会直接筛去:
- 在 package.json 或 module.rules 中配置 sideEffects 选项指定会产生副作用的文件;
参考
你的 Tree-Shaking 并没什么卵用
Tree-Shaking 性能优化实践 - 原理篇
如何更好使用 webpack tree-shaking