Rollup源码学习(五)——揭开TreeShaking的神秘面纱

前言

在前4篇文章中,我们大致已经对Rollup的构建及产物生成逻辑略知一二了,在这个过程中,我们有意的跳过了一些复杂但有用的知识点,比如TreeShaking就是一个有用的知识点。

在这一篇文章中,我们就从Rollup的源码角度来分析一下它是如何完成TreeShaking这一伟大的优化操作的。

这篇文章和前面的第二篇文章有一些关联,在阅读这篇文章之前,已经阅读过之前的文章。

TreeShaking的概念与优势

现实生活中有一个生动形象的例子可以与之对应,当到了秋天,树叶黄了,我们摇一摇树干,就会被树上的枯叶摇掉,落红不是无情物,化作春泥更护花,呵呵。

树摇优化即Tree Shaking 是一种在打包工具(如 Rollup、Webpack、esbuild、Parcel 等)中常用的优化技术,它通过分析代码的依赖关系和可达性,将未被实际使用(导出但未被引用)的代码 "摇掉",从而减小最终的打包结果大小。

简单来说,TreeShaking会在构建过程中检查你的模块中到底有哪些函数、类、变量从未真正使用,然后在最终产物中将这些无关代码删除掉。

核心前提

  • 必须是基于ES Module风格的代码,因为ESM是静态可分析的,也就是在编译时能明确知道哪个模块导出什么,哪些导出在其它模块被使用。

  • 严格模式与无副作用函数,当函数或者模块执行会产生副作用(如修改全局变量、console 输出等),Tree Shaking 会变得困难和不可靠。打包工具往往要求开发者申明某些包或模块是无副作用的,以便安全地移除未使用的导出。对于一些可能有副作用的内容,可以在项目的package.json文件中的sidEffects中指定,这样可以告诉打包工作,在这些目录下面的文件都是不能被移除的。

  • 有些打包工具需要要求在生产模式下。

意义与好处

  1. 减小代码体积
    通过移除未使用的代码,最终生成的 bundle 文件体积更小,加载更快,提高前端性能与用户体验。
  2. 提升加载速度
    更小的代码包意味着用户在加载页面时消耗的时间和流量更少,有助于减少白屏时间及加快首屏渲染。
  3. 优化资源利用
    减少无用代码也可让浏览器更快地解析、执行代码,进而降低 CPU 和内存占用。
  4. 有利于代码维护
    当开发者察觉到某些函数、模块完全未被使用却依然保留在代码中,这会促使他们更好地维护代码库,清理冗余依赖,保持代码整洁。这也是可以防止我们写出屎山代码的一种微不足道的方式。

Rollup如何进行TreeShaking

之前的项目准备的有点儿复杂,在这一章节,我们就用2个简单的文件来尝试即可。

主入口文件:

index.js 复制代码
import { demo1, demo2 } from "./demo";

function demo() {
  console.log("demo");
  demo1();
  if (process.env.NODE_ENV !== "production") {
    demo2();
  }
}

demo();

用来测试TreeShaking的文件:

js 复制代码
export function demo1() {
  console.log("demo1");
}

export function demo2() {
  console.log("demo2");
}

在之前的文章中,我们已经提到过,Module类是Rollup中处理文件的核心类,它身上绑定着文件的源码,解析得到的AST,以及与之相关的模块引用关系。

引用标记

我们暂时先回到Module的setSource方法: 在准备做AST解析的时候,Rollup初始化了一个辅助上下文,这个上下文关联了很多操作,在解析AST的时候届时文件的关联操作就可以通过这个上下文进行。

到这会儿,暂时还没有看到最终生成代码引用关系的建立,这个位置只确定了文件的依赖关系。

到这个位置的时候,就确定了文件内容代码层面上的依赖关系了。 我们先看看这个includeStatements函数: 看这个语句的意思有点儿接近我们想要的答案了,如果直接配置了不进行TreeShaking,那么就直接把所有内容打包到bundle,如果没有的话,再考虑处理代码层面的引用关系了。

因为接下来的内容都是AST的操作,操作AST的内容又臭又长,也不好进行直观的查看,我们通过调试查看堆栈的形式,反向推导。

已知上面我们给出来的代码,demo2函数是要被摇掉的,那么,我们先看一下renderChunk之后,这个Module里面剩下的数据情况是什么样的。

那么,我们需要看一下,这个includedImports是在什么位置添加的就好了。

我们直接在源码里面搜索: 打一个断点,查看堆栈信息。 恭喜,我们可以通过堆栈信息找到是在这个位置处理的引用标记,这是在用户不关闭TreeShaking的能力的场景下走到的这个分支。 接下来,就回到冗余无味的AST处理引用的标记了。 这个AST的来源,即是之前我们读取文件内容时,解析到的AST。

关于AST的处理引用的问题,如果全都写出来,篇幅太长了,在本文中这部分知识点,我就做这么多解析,如果读者有任何问题,可以和我交流。

内容输出消除

在之前的小节,我们已经分析到了,Rollup通过对AST的分析,确定文件之间引用的内容,接下来,我们需要看一下Rollup在做内容输出的时候,是如何做到删除未引用的内容仅仅输出有用的内容的逻辑。

在上一篇文章中,我们在聊Rollup的输出逻辑的时候已经讲过了Module上有一个render方法,主要的功能是把当前文件的内容输出。

这个调用关系是上面几个图的说明: Bundle.renderChunks->renderChunks->Chunk.render->Chunk.renderModules->Module.render

在Module里面,调用AST的render方法,将AST重新转换成目标代码。 这个方法是一个基类的方法,AST处理逻辑中,有很多种子类处理语句,每种语句均实现render方法,负责把对应的AST输出成目标代码,所以我们可以搜索到很多实现。

所以,再看这个位置跳转过去的ast的处理逻辑就没有多大的意义了。 不过,还是给大家看一看,哈哈。 这个处理是处理Program类型的AST,AST的Root节点正是这个类型的节点。 如果当前抽象语法树节点没有被标记引入,则认为当前的节点是可以被丢弃的。 我们还是通过打断点的方式来看一下,我们之前写的demo里面demo2函数是如何被摇掉的。 这儿AST的仍然存在不直观的问题,我们直接把那个magicString处理前后打印出来,就知道了。 到这个位置,我们就已经把Rollup如何进行TreeShaking的原理讲清楚了。

总结

最后,给大家总结一下Rollup进行TreeShaking的总体流程。

首先在构建阶段,Rollup在正常解析文件,初始化一个Module类用来保存文件的内容信息,Rollup会将将解析到文件内容转成AST存储在Module中。

当所有的文件都已经解析完成之后,如果用户配置的是不进行TreeShaking的话,那么所有的那内容都讲打包到最后的bundle中。

如用户配置需要TreeShaking的话,Rollup会借助AST对文件引用的内容进行分析,它将会分析出哪些抽象语法树节点是需要的,哪些节点是不需要的,完成初步的标记

然后到了生成阶段,Rollup先根据依赖或者自定义的Chunk划分依据,划分出不同的Chunk,然后Chunk调用自身所关联的所有Module,Module根据自身存储的AST信息,可以把AST根据用户的需求重新转换成目标代码。

而这个过程中,因为我们在构建阶段已经对AST进行了标记,我们已经清楚的知道哪些抽象语法树节点是需要的,哪些节点是不需要的,然后在生成目标代码的过程中,选择性的忽略一些内容,从而实现将不需要的内容消掉,减少最终的Bundle体积。

相信很多读者学习这些知识点,更多的是为了面试做准备吧,我也总结一个简单的步骤帮助大家记忆:

源代码->AST->标记与分析->AST输出(跳过不需要节点)->目标代码

以上就是我通过阅读Rollup源代码得出其进行TreeShaking的底层原理,分析过程中如果存在纰漏或错误还请大家谅解,如果读者有任何质疑或者困惑可以尝试和我联系,你们的建议和意见是我进步的不竭动力。

相关推荐
码字哥12 分钟前
EasyExcel设置表头上面的那种大标题(前端传递来的大标题)
java·服务器·前端
GDDGHS_43 分钟前
Flink的架构体系
大数据·架构·flink
李宥小哥1 小时前
架构15-服务网格
架构
李宥小哥1 小时前
架构11-虚拟化容器
架构
小春学渗透1 小时前
DAY168内网对抗-基石框架篇&单域架构&域内应用控制&成员组成&用户策略&信息收集&环境搭建
网络·安全·架构·内网攻防
GIS好难学2 小时前
《Vue进阶教程》第六课:computed()函数详解(上)
前端·javascript·vue.js
nyf_unknown2 小时前
(css)element中el-select下拉框整体样式修改
前端·css
m0_548514772 小时前
前端打印功能(vue +springboot)
前端·vue.js·spring boot
执键行天涯2 小时前
element-plus中的resetFields()方法
前端·javascript·vue.js
Days20502 小时前
uniapp小程序增加加载功能
开发语言·前端·javascript