第 94 期 - Rollup 构建后的输出流程解析
logoFRONTALK AI/1月26日 16:30/阅读原文

摘要

文章阐述 Rollup 构建完成后的输出流程,包括 Module 排序、循环引用检测、Chunk 生成、内容生成以及输出生命周期,还提及目前内容只是宏观层面,后续将讲述 TreeShaking 等更多内容。

一、Rollup 构建后的整体流程

Rollup 构建完成后,内存中充满 Module 类实例,输出流程就是将这些数据写入磁盘完成前端工程构建。这其中涉及多个环节,包括 Module 的排序、与 Chunk 和 Bundle 类的交互、处理循环引用、生成 Chunk、生成内容以及输出生命周期等内容。

二、Module 排序原理

(一)排序的必要性

JS 引用有先后顺序,为避免访问未声明变量而报错,Module 需要排序。

(二)排序的方法

以拓扑排序为例,若有a.js->b.js->c.js这样的依赖关系,无依赖的c.js先输出,依赖c.jsb.js其次,依赖b.jsc.jsa.js最后输出。

(三)Rollup 中的拓扑排序

  1. analyseModuleExecution函数内部定义analyseModule函数用于递归调用。
  2. 第一个for语句依次调用analyseModule函数,dependencies存储当前 Module 依赖内容,做深度优先遍历,有定义记录引用的Map(值为parents),每处理一个 Module 记录引用关系。
  3. nextExecIndex是最外层变量,递归体在nextExecIndex ++语句前,按之前例子最终c.jsindex为 1,b.js为 2,a.js为 3,用于后续排序。

三、循环引用检测

(一)循环引用的危害

在实际开发中应尽量减少编写循环引用代码。

(二)Rollup 中的处理

  1. 当 Module 间有循环依赖且被打包进一个Chunk时没问题,但划分至多个Chunk时,加载产物会报错。
  2. Rollup 处理循环引用的函数执行条件是已确定有循环依赖,其逻辑是典型循环链表探测环,module是头结点,不断向后迭代,当迭代到的节点为module时读取到完整环,处理过程中还会往 Module 里增加节点信息。
  3. 对于getCyclePath函数,第一个if条件成立时可能是已处理模块,当两个if条件同时成立说明有大量 Module 节点无法完成拓扑排序,需记录依赖环。

四、生成 Chunk

(一)自动分包策略

在不配置自定义分包策略时,Rollup 利用位运算生成键,当键相同 Module 可划分到同一Chunk

(二)位运算原理

  1. let chunkSignature = 0n; for (const entryIndex of dependentEntries) {chunkSignature |= 1n << BigInt(entryIndex);}为例。
  2. 使用BigInt和位移操作创建位标记:1n << BigInt(entryIndex)将初始值为 1 的大整数左移entryIndex位,每个entryIndex对应二进制位图中独特位置。
  3. 使用按位或叠加多个标志位:遍历dependentEntries时对chunkSignature不断进行OR运算,将出现在集合中的entryIndex转化为在二进制数chunkSignature中置位。
  4. 意义是将一组依赖入口索引压缩成唯一二进制标识,快速对具有相同依赖入口模式的模块分组。

(三)后续操作

Chunk划分完成后要进行产物输出准备,将 Module 内容按之前确定的排序依据合并(打包)。

五、生成内容

(一)基本处理

  1. 先不考虑TreeShaking,调用每个Chunkrender函数处理 Module 内容生成。
  2. magicString为例,它是打包合并初加工产物但部分哈希码未确定,其来源是将AST转化成生成目标进行输出。
  3. 可在生成内容的headerfooter插入自定义内容。

(二)路径处理

对粗加工打包代码进行精加工,把代码里引用的文件路径替换成带哈希码的真实相对路径。

六、输出生命周期

  1. 首先触发outputOptions,然后renderStart生命周期。
  2. 根据 Module 信息划分Chunk,划分后为输出准备工作,触发bannerfooterintrooutro等生命周期可追加版权信息。
  3. 把文件内容合并成粗加工内容,触发renderChunk生命周期,根据用户配置得到哈希依据触发argumentChunkHash生命周期,粗加工内容根据哈希码替换引用规则后触发generateBundle生命周期。
  4. 内容准备好后写入磁盘,触发writeBundle生命周期,最后closeBundle生命周期。
  5. 所有逻辑在try - catch中执行,遇到错误执行renderError生命周期。部分生命周期如renderDynamicImportresolveFileUrlresolveImportMeta未在此分析。
 

扩展阅读

Made by 捣鼓键盘的小麦 / © 2025 Front Talk 版权所有