tree shaking的原理都说是通过ES6 Module来进行静态分析,可是具体是如何静态分析的?

具体是如何静态分析的?是和AST有关吗?我应该如何入手探究这些原理?
关注者
23
被浏览
9,170

2 个回答

前言

什么是Tree Shaking

Tree-Shaking 是一种基于ES Module规范的 Dead Code Elimination(死代码优化)技术,它会在运行过程中静态分析模块之间的导入导出,确定 ESM 模块中哪些导出值未曾其它模块使用,并将其删除,以此实现打包产物的优化(减少打包体积)。

什么是ES Module

JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。

简单比较如下:

  • ES Module 输出的是值的引用,而 CommonJS 输出的是值的拷贝;
  • ES Module 是编译时执行,而 CommonJS 模块是在运行时加载;
  • ES6 Module可以导出多个值,而CommonJs 是单个值导出;
  • ES6 Module 静态语法只能写在顶层,而CommonJs 是动态语法可以写在判断里;
  • ES6 Module的 this 是 undefined,而CommonJs 的 this 是当前模块;

静态化的意义:模块的依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,Tree-Shaking 正是基于这个前提才有实现的可能。

Tree Shaking实现原理

以Webpack中Tree-Shaking处理为例,其实现分为如下阶段,不同阶段执行不同流程:

一、收集模块导出

收集各个模块导出变量并记录到模块依赖关系图变量ModuleGraph中;

二、标记模块导出

模块导出信息收集完毕后,根据entry,Webpack 标记出各个模块的导出列表中,哪些导出值有被其它模块用到,哪些没有用到,

三、生成代码

经过前面的收集与标记步骤后,Webpack 已经在 ModuleGraph 体系中清楚地记录了每个模块都导出了哪些值,每个导出值有没有被引入使用。接下来,Webpack 会根据导出值的使用情况生成不同的代码:

模块导出列表中被使用的值都会定义在 __webpack_exports__对象中;

四、 删除 Dead Code

经过前面几步操作之后,模块导出列表中未被使用的值都不会定义在__webpack_exports__对象中,标记形成一段不可能被执行的 Dead Code 效果。

在此之后,将由 Terser、UglifyJS 等 DCE 工具“摇”掉这部分无效代码,构成完整的 Tree Shaking 操作;

其中UglifyJS 等 DCE 工具“摇”掉这部分无效代码包括:

  • 标记的没有使用的函数;
  • 没有使用的变量;

总结

现在所提到的Tree Shaking都是在ES Module标准只上的:静态编译,确定输入输出值;

Tree Shaking主要流程可以简化为:搜集依赖—>标记引用—>清除DeadCode。

参考

Webpack 原理系列九:Tree-Shaking 实现原理

Tree Shaking:从原理到实现

ECMAScript 6 入门:Module

所谓的静态分析, 就是在不运行代码的情况下, 对代码进行检测扫描分析.


// demo.js
export const a = 'a'
export const b = 'b'

// test.js
import { a } from './demo.js'



// 以上代码不运行, 仅仅经过扫描分析, 抛弃了const b, 代码缩减了size

// 这就是tree shaking的静态分析的基本原理: 就是有引用就保留, 没有引用就抛弃

关于程序静态分析详见: