Tree Sharking 可以用来剔除 JavaScript 中用不上的死代码。它依赖静态的 ES6 模块化语法,例如通过 import 和 export 导入导出。 Tree Sharking 最先在 Rollup 中出现,Webpack 在 2.0 版本中将其引入。
import
export
为了更直观的理解它,来看一个具体的例子。假如有一个文件 util.js 里存放了很多工具函数和常量,在 main.js 中会导入和使用 util.js,代码如下:
util.js
main.js
util.js 源码:
export function funcA() { } export function funB() { }
main.js 源码:
import {funcA} from './util.js'; funcA();
Tree Sharking 后的 util.js:
export function funcA() { }
由于只用到了 util.js 中的 funcA,所以剩下的都被 Tree Sharking 当作死代码给剔除了。
funcA
需要注意的是要让 Tree Sharking 正常工作的前提是交给 Webpack 的 JavaScript 代码必须是采用 ES6 模块化语法的, 因为 ES6 模块化语法是静态的(导入导出语句中的路径必须是静态的字符串,而且不能放入其它代码块中),这让 Webpack 可以简单的分析出哪些 export 的被 import 过了。 如果你采用 ES5 中的模块化,例如 module.export={...}、require(x+y)、if(x){require('./util')},Webpack 无法分析出哪些代码可以剔除。
module.export={...}
require(x+y)
if(x){require('./util')}
上面讲了 Tree Sharking 是做什么的,接下来一步步教你如何配置 Webpack 让 Tree Sharking 生效。
首先,为了把采用 ES6 模块化的代码交给 Webpack,需要配置 Babel 让其保留 ES6 模块化语句,修改 .babelrc 文件为如下:
.babelrc
{ "presets": [ [ "env", { "modules": false } ] ] }
其中 "modules": false 的含义是关闭 Babel 的模块转换功能,保留原本的 ES6 模块化语法。
"modules": false
配置好 Babel 后,重新运行 Webpack,在启动 Webpack 时带上 --display-used-exports 参数,以方便追踪 Tree Sharking 的工作, 这时你会发现在控制台中输出了如下的日志:
--display-used-exports
> webpack --display-used-exports bundle.js 3.5 kB 0 [emitted] main [0] ./main.js 41 bytes {0} [built] [1] ./util.js 511 bytes {0} [built] [only some exports used: funcA]
其中 [only some exports used: funcA] 提示了 util.js 只导出了用到的 funcA,说明 Webpack 确实正确的分析出了如何剔除死代码。
[only some exports used: funcA]
但当你打开 Webpack 输出的 bundle.js 文件看下时,你会发现用不上的代码还在里面,如下:
bundle.js
/* harmony export (immutable) */ __webpack_exports__["a"] = funcA; /* unused harmony export funB */ function funcA() { console.log('funcA'); } function funB() { console.log('funcB'); }
Webpack 只是指出了哪些函数用上了哪些没用上,要剔除用不上的代码还得经过 UglifyJS 去处理一遍。 要接入 UglifyJS 也很简单,不仅可以通过4-8压缩代码中介绍的加入 UglifyJSPlugin 去实现, 也可以简单的通过在启动 Webpack 时带上 --optimize-minimize 参数,为了快速验证 Tree Sharking 我们采用较简单的后者来实验下。
--optimize-minimize
通过 webpack --display-used-exports --optimize-minimize 重启 Webpack 后,打开新输出的 bundle.js,内容如下:
webpack --display-used-exports --optimize-minimize
function r() { console.log("funcA") } t.a = r
可以看出 Tree Sharking 确实做到了,用不上的代码都被剔除了。
当你的项目使用了大量第三方库时,你会发现 Tree Sharking 似乎不生效了,原因是大部分 Npm 中的代码都是采用的 CommonJS 语法, 这导致 Tree Sharking 无法正常工作而降级处理。 但幸运的时有些库考虑到了这点,这些库在发布到 Npm 上时会同时提供两份代码,一份采用 CommonJS 模块化语法,一份采用 ES6 模块化语法。 并且在 package.json 文件中分别指出这两份代码的入口。
package.json
以 redux 库为例,其发布到 Npm 上的目录结构为:
redux
node_modules/redux |-- es | |-- index.js # 采用 ES6 模块化语法 |-- lib | |-- index.js # 采用 ES5 模块化语法 |-- package.json
package.json 文件中有两个字段:
{ "main": "lib/index.js", // 指明采用 CommonJS 模块化的代码入口 "jsnext:main": "es/index.js" // 指明采用 ES6 模块化的代码入口 }
在2-4Resolve mainFields 中曾介绍过 mainFields 用于配置采用哪个字段作为模块的入口描述。 为了让 Tree Sharking 对 redux 生效,需要配置 Webpack 的文件寻找规则为如下:
mainFields
module.exports = { resolve: { // 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件 mainFields: ['jsnext:main', 'browser', 'main'] }, };
以上配置的含义是优先使用 jsnext:main 作为入口,如果不存在 jsnext:main 就采用 browser 或者 main 作为入口。 虽然并不是每个 Npm 中的第三方模块都会提供 ES6 模块化语法的代码,但对于提供了的不能放过,能优化的就优化。
jsnext:main
browser
main
目前越来越多的 Npm 中的第三方模块考虑到了 Tree Sharking,并对其提供了支持。 采用 jsnext:main 作为 ES6 模块化代码的入口是社区的一个约定,假如将来你要发布一个库到 Npm 时,希望你能支持 Tree Sharking, 以让 Tree Sharking 发挥更大的优化效果,让更多的人为此受益。
本实例提供项目完整代码
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8