关于tree-shaking:从过去到现在聊聊-Treeshaking

8次阅读

共计 4672 个字符,预计需要花费 12 分钟才能阅读完成。

前言

Tree-shaking 这一术语在前端社区内,起初是 Rich Harris 在 Rollup 中提出。简略概括起来,Tree-shaking 能够使得我的项目最终构建(Bundle)后果中只蕴含你理论须要的代码。

而且,说到 Tree-shaking,不不免提及 Dead Code Elimation,置信很多同学在一些对于 Tree-shaking 的文章中都会看到诸如这样的形容:Tree-shaking 是一项 Dead Code Elimation(以下统称 DCE)技术。

那么,既然有了 DCE 这一术语,为什么又要造一个 Tree-shaking 术语?存在既有价值,上面,让咱们一起来看看 Rich Harris 是如何答复这个问题的。

1 Tree-shaking Vs Dead Code Elimaion

在过后 Rich Haris 针对这一发问专门写了这篇文章《Tree-shaking versus dead code elimination》,文中示意 DCE 和 Tree-shaking 最终的指标是统一的(更少的代码),然而它们依然是存在区别的。

Rich Haris 举了个做蛋糕的例子,指出 DCE 就好比在做蛋糕的时候间接把鸡蛋放入搅拌,最初在做好的蛋糕中取出蛋壳,这是不完满的做法,而 Tree-shaking 则是在做蛋糕的时候只放入我想要的货色,即不会把蛋壳放入搅拌制作蛋糕。

因而,Tree-shaking 表白的不是指打消 Dead Code,而是指保留 Live Code。即便最终 DCE 和 Tree-shaking 的后果是统一的,然而因为 JavaScript 动态剖析的局限性,理论过程并不同。并且,蕴含有用的代码能够失去更好的后果,从外表看(做蛋糕的例子)这也是一种更合乎逻辑的办法。

此外,过后 Rich Haris 也认为 Tree-shaking 可能不是一个很好的名称,思考过用 Live Code Inclusion 这个短语来示意,然而仿佛会造成更多的困惑 …… 让咱们看一下 Rich Haris 的原话:

I thought about using the‘live code inclusion’phrase with Rollup, but it seemed that I’d just be adding even more confusion seeing as tree-shaking is an existing concept. Maybe that was the wrong decision?

所以,我想到这里同学们应该分明一点,Tree-shaking 和 DCE 只是最终的后果 是统一的 ,然而 2 者 实现的过程不同,Tree-shaking 是保留 Live Code,而 DCE 是打消 Dead Code。

并且,过后 Rich Harris 也指出 Rollup 也不是完满的,最好的后果是应用 Rollup + Uglify 的形式。不过,显然当初的 Rollup v2.55.1 曾经臻至完满。那么,接下来让咱们沿着工夫线看看 Tree-shaking 的演变~

2 Tree-shaking 的演变

Tree-shaking 在最后被提出的时候它只会做一件事,那就是利用 ES Module 动态导入的特点来检测模块内容的导出、导入以及被应用的状况,从而实现保留 Live Code 的目标。

兴许这个时候你会问 Tree-shaking 不是还会打消 Dead Code 吗?的确,然而也不肯定,如果你应用的是当初的 Rollup v2.55.1,它是会进行 DCE,即打消 Dead Code。然而,如果你用的是 Webpack 的话,那就是另一番状况了,它须要应用 Uglify 对应的插件来实现 DCE。

上面,咱们以 Rollup 为例,聊聊过来和当初的 Tree-shaking。

2.1 过来的 Tree-shaking

在晚期,Rollup 提出和反对 Tree-shaking 的时候,它并不会做额定的 DCE,这也能够在 15 年 Rich Haris 写的那篇文章中看出,过后他也提倡大家应用 Rollup + Uglify。所以,这里让咱们一起把 工夫倒回 Rollup v0.10.0 的 Tree-shaking。

回到 Rollup v0.10.0 版本,你会发现十分乏味的一点,就是它的 GitHub README 介绍是这样的:

Rollup 的命名来源于一首名为《Roll up》的说唱歌曲,我想这应该出乎了很多同学的预料。不过话说 Evan You 也喜爱说唱,而后我(你)也喜爱说唱,所以这兴许能够论证我(你)抉择前端仿佛没错?这里附上这首歌,你能够抉择听这首歌来拉近 Rollup 的间隔。

传送门:https://www.youtube.com/watch…

上面,咱们应用 Rollup v0.10.0 版本来做一个简略示例来验证一下后面说的。并且,在这个过程中须要留神,如果你的 Node 版本过高会导致一些不兼容,所以倡议用 Node v11.15.0 来运行上面的例子。

首先,初始化我的项目和装置根底的依赖:

npm init -y
npm i rollup@0.10.0 -D

而后,别离新建 3 个文件:

utils.js

export const foo = function () {console.log("foo");
};

export const bar = function () {console.log("bar");
};

main.js

import {foo, bar} from "./utils.js";

const unused = "a";

foo();

index.js

const rollup = require("rollup");

rollup
  .rollup({entry: "main.js",})
  .then(async (bundle) => {
    bundle.write({dest: "bundle.js",});
  });

其中,main.js 是构建的入口文件,而后 index.js 负责应用 Rollup 进行构建,它会将最终的构建后果写入到 bundle.js 文件中:

// bundle.js
const foo = function () {console.log("foo");
};

const unused = "a";

foo();

能够看到,在 bundle.js 保留了 utils.js 中的 foo() 函数(因为被调用了),而导入的 uitls.js 中的 bar() 函数(没有被调用)则 不会保留,并且定义的变量 ununsed 尽管没有被应用,然而依然保留了下来。

所以,通过这么一个小的示例,咱们能够验证得悉 Rollup 的 Tree-shaking 最后并不反对 DCE,它仅仅 只是在构建后果中保留你导入的模块中须要的代码

2.2 当初的 Tree-shaking

后面,咱们从过来的 Tree-shaking 开始理解,大抵建设起了对 Tree-shaking 的初印象。这里咱们来看一下当初 Rollup 官网上对 Tree-shaking 的介绍:

Tree-shaking,也被称为 Live Code Inclusion,是指 Rollup 打消我的项目中理论未应用的代码的过程,它是一种 Dead Code Elimation 的形式,然而在输入方面会比其余办法更无效。该名称源自模块的形象语法树(Abstract Sytanx Tree)。该算法首先会标记所有相干的语句,而后通过摇动语法树来删除所有的 Dead Code。它在思想上相似于 GC(Garbage Collection)中的标记革除算法。只管,该算法不限于 ES Module,但它们使其效率更高,因为它容许 Rollup 将所有模块一起视为具备共享绑定的大形象语法树。

从这段话,咱们能够很容易地发现随着工夫的推移,Rollup 对 Tree-shaking 的定义曾经不仅仅是 ES Module 相干,此外它还反对了 DCE。所以,有时候咱们看到一些文章介绍 Tree-shaking 实现会是这样:

  • 利用 ES Module 能够进行动态剖析 的特点来检测模块内容的导出、导入以及被应用的状况,保留 Live Code
  • 打消 不会被执行 没有副作用(Side Effect) 的 Dead Code,即 DCE 过程

那么,在后面咱们曾经晓得 Tree-shaking 基于 ES Module 动态剖析的特点会做的事件。所以,这里咱们来认真看一下第 2 点,换个角度看,它指的是当代码 没有被执行 ,然而它会 存在副作用,这个时候 Tree-shaking 就不会把这部分代码打消。

那么,显然对副作用建设良好的认知,能够让我的项目中代码能更好地被 Tree-shaking。所以,上面让咱们来通过一个简略的例子来认识一下副作用。

2.2.1 副作用(Side Effect)

在 Wiki 上对副作用(Side Effect)做出的介绍:

在计算机科学中,如果操作、函数或表达式在其本地环境之外批改某些状态变量值,则称其具备副作用。

把这段话转换成咱们相熟的,它指的是当你批改了 不蕴含在以后作用域 的某些变量值的时候,则会产生副作用。这里咱们把下面的例子稍作批改,把 sayHi() 函数的形参删掉,改为间接拜访定义好的 name 变量:

utils.js

export const name = "wjc";

export const sayHi = function () {console.log(`Hi ${name}`);
};

main.js

import {sayHi} from "./maths.js";

sayHi();

而后,咱们把这个例子通过 Rollup 提供的 REPL 来 Tree-shaking 一下,输入的后果会是这样:

const name = "wjc";

const sayHi = function () {console.log(`Hi ${name}`);
};

sayHi();

能够看到,这里咱们并没有间接导入 utils.js 文件中的 name 变量,然而因为在 sayHi() 函数中拜访了它作用域之外的变量 name,产生了副作用,所以最初输入的后果也会有 name 变量。

当然,这仅仅只是一个非常简单的产生副作用的场景,也是很多同学不会犯的谬误。此外,一个很乏味的场景就是应用 Class 关键字申明的类通过 Babel 转换为 ES5 的代码(为了保障 Class 可枚举)后会产生副作用。

对下面提到的这个问题感兴趣的同学,能够看这篇文章 你的 Tree-Shaking 并没什么用 认真理解,这里就不做反复阐述了~

结语

写这篇文章的动机次要是出于对 Tree-shaking 和 DCE 这两个术语十分相似,然而 Tree-shaking 必然有其存在的意义,所以就诞生了这篇文章。尽管,文章中并没有波及 Tree-shaking 的底层实现,然而我想有时候 搞清楚一些含糊的概念的优先级是优于理解其底层实现的

并且,通过比照 2015 年 Rich Harris 在提出 Tree-shaking 的初衷,到当初 Tree-shaking 所具备的能力来说,随着工夫的演变 Rollup 的 Tree-shaking 默认也反对了 DCE,这也难免会造成一些同学对 Tree-shaking 的了解产生凌乱。所以,如果想要追溯根源(Tree-shaking 由来)的同学,我还是蛮举荐仔细阅读一下《Tree-shaking versus dead code elimination》这篇文章的。

最初,如果文中存在表白不当或谬误的中央,欢送各位同学提 Issue ~

点赞

通过浏览本篇文章,如果有播种的话,能够 点个赞,这将会成为我继续分享的能源,感激~

我是五柳,喜爱翻新、捣鼓源码,专一于源码(Vue 3、Vite)、前端工程化、跨端等技术学习和分享。此外,我的所有文章都会收录在 https://github.com/WJCHumble/Blog,欢送 Watch Or Star!

正文完
 0