Webpack | TreeShaking 工作原理
阅读本文,你将了解
1. Tree Shaking 的工作流程
2. 实现 Tree Shaking 的前提
3. Tree Shaking 的副作用
目的
本质上为了消除无用的js代码,减少加载文件体积的方式,使其整体执行时间更短。
那么如何判断js代码的“无用”?
这些无用的代码,又有另外一种叫法:Dead Code。
而Dead Code 是被怎么定义的:
实现前提
- Tree Shaking 的执行环境宿主一般是Node,而不是浏览器
- 若JavaScript 是模块化的,那么必须遵循ES6 Module规范,而不是CommonJS或其他,主要是因为 ES6 Module 可以静态分析的。
前者**通过作用域分析,分析出代码里变量所属的作用域以及他们之间的引用关系,进而可以推导出变量和导入依赖变量的引用关系**。
- 将mode 选项为"production",以启用 tree shaking 和 minification (代码压缩)
- 确保没有把compiler 将 es6 模块语法转换为CommonJS 模块。这一块很重要,在你使用babel-loader 或者 ts-loader 编译代码时,一定要保留import 和 export。
原理
首先,Tree-Shaking实现分两步走,
Step 1:标记出模块导出值哪些没有被用过
Step 2:使用Terser删除掉没有被用到的导出语句
其中,标记过程又可以分为三个步骤:
- Make 阶段,收集导出变量并记录到模块依赖图ModuleGraph 变量中。
- Seal 阶段,遍历ModuleGraph 标记模块导出变量有没有被使用
- 生成产物时,若变量没有被其他模块使用时则删除对应的导出语句
标记功能需要配置 optimization.useExports = true 开启
标记效果
webpack 负责对代码进行标记,把import & export 标记为3类:
- 所有 import 标记为 /* harmony import */
- 被使用过的 export 标记为/* harm export([type])*/ ,其中[type] 和 webpack 内部相关,可能是 binding,immutable 等等。
- 未被使用过的 import 标记为 /* unused harmony export [FuncName] */,其中[FuncName] 为export 的方法名称
实例说明
标记
「注:省略了 bundle.js 上边 webpack 自定义的模块加载代码,那些都是固定的。」
由上可得:
- bye 没有被使用,被标记为/* unused harmony export bye*/
- hello 被使用为正常的 /*harmony export(imutable) */
删除
使用 Terser 就可以进行第二步,把 bye 彻底清除
副作用
副作用是函数式编程的一个概念,是指当调用函数时,除了返回函数值之外,还会对调用函数产生附加的影响。
简单来说,就是除了返回值以外,还做了其他事情。
比如
- 打印Log
- 读取和修改外部变量
- 导入css文件,引入Polyfill 等
副作用代码不可被删除
举个例子:
const setTitle = ()=>{ //
document.title = "shenzhen";
}
const a = setTitle();
以上可知,虽然a变量没有被其他地方使用,但由于副作用,如果将其删除。
会导致document.title 没有成功被设置导致出现bug。
副作用出现原因
那有同学说了,不在项目中写带有副作用代码不就完了嘛?
只是难以预料的是,副作用代码很可能在编译阶段产生。
举个例子:
与此同时,如果我在第三个文件中注释掉 return Greet之后,Terser可以成功删除。
为什么呢?这是因为 return Greet 之后相当于给greet 变量赋值。而这个值在其他处有可能被修改。
而Terser(压缩代码工具),不能有效分析变量后续是否被引用、修改。
因此Terser不清楚以上代码是否产生副作用,因此保留。
总结
- Tree Shaking 用于减小文件体积
- 其工作流程是先标记,后删除
- 代码必须遵循 ES module 规范
- 删除代码失灵很可能因出现副作用代码导致
参考:
无用代码去哪了?项目减重之 rollup 的 Tree-shaking
预告:下一篇着重介绍Tree Shaking 的实践中遇到的问题以及解决办法。
予人玫瑰,手有余香,如果您觉得内容不错,希望可以点赞关注,从而帮助到更多的人。
需要交流请加我个人微信: