Rollup源码学习(二)

前言

在上一篇文章中,我们已经简述了Rollup的入口文件和命令行工具提供的能力,这一篇文章开始,我们会建一个测试项目给Rollup进行打包,在阅读源代码的同时,尝试打断点的形式嗅探Rollup核心的底层运行原理。

测试项目介绍

以下是我的Rollup配置文件:

js 复制代码
import { defineConfig } from "rollup";
import resolve from "@rollup/plugin-node-resolve";
import replace from "@rollup/plugin-replace";
import del from "rollup-plugin-delete";

export default defineConfig({
  input: {
    main: "src/index.js",
  },
  output: {
    dir: "dist",
    chunkFileNames: "chunks/[name]-[hash].js"
  },
  plugins: [
    del({ targets: "dist/*" }),
    resolve(),
    replace({
      "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
      preventAssignment: true
    }),
  ],
});

配了三个插件:

  • 第一个是为了每次构建的时候把前一次构建的内容删除;
  • 第二个插件是为了让Rollup能够找到外部模块
  • 第三个插件主要是为我们后面的文章探索Rollup的TreeShaking做准备。

然后是项目目录结构:

先把项目的每个JS的内容向大家展示一下。

index.js

js 复制代码
import { A } from "./deps/a";

console.log("hello world");

A();

function Demo() {
  import("./deps/q").then(({ Q }) => {
    Q();
  });
}

Demo();

a.js

js 复制代码
import { B, demoB } from "./b";
import { D } from "./d";

export async function A() {
  console.log("AAAAA");
  D();
  B();
  import("./e").then(({ E }) => {
    E();
  });
  if (process.env.NODE_ENV !== "production") {
    demoB();
  }
}

import("./f").then(({ F }) => {
  F();
});

b.js

js 复制代码
import { C } from "./c";
import { D } from "./d";

export function B() {
  console.log("BBBB");
  C();
  D();
}

export function demoB() {
  console.log("demo b");
}

c.js

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

d.js

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

e.js

js 复制代码
import { F } from "./f";

export function E() {
  console.log("eee");
  F();
}

f.js

js 复制代码
import { G } from "./g";

export function F() {
  console.log("FFFF");
  G();
}

g.js

js 复制代码
import { F } from "./f";

export function G() {
  F();
  console.log("GGGGGG");
}

q.js

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

为了方便大家有一个直观的项目文件依赖关系的认识,我画了一个图,这个图即是Image,又是数据结构里面所讲的Graph图,便于一会儿阐述依赖分析。

文件内容读取

在上一篇文章中,我们提到了Graph这个核心类,此刻我们就开始从它开始去研究Rollup文件读取的逻辑。 在上一篇文章中,我们已经聊过这个方法了,大家应该不会特别陌生。

build方法开始生成资源的依赖关系图,我们会花一定的篇幅来研究它的处理逻辑。

此刻,它会调用ModuleLoader类的addEntryModules方法,这个ModuleLoader也是一个核心类,后面的篇幅我们会一直跟它打交道,这个类是在Graph类的构造函数初始化的。 紧接着调用loadEtryModule方法: 然后确定文件路径,在这个位置就触发了Rollup的生命周期resolveId

我们利用resolveId可以处理路径别名,可以实现虚拟模块的技术。

现在,我们再回过头来看Rollup的文档,resolveId这个生命周期是不是就觉得倍感熟悉啦,哈哈。

生命周期之:resolveId文档

在确定了文件的路径之后,就要开始尝试加载文件内容了。 这儿又来了一个新的核心类Module,这个类主要就是用来处理资源的,后面会经常见到它的。 然后就可以触发生命周期load,并且读取文件的内容了(读取不到则输出错误信息),不过load这个生命周期Rollup并没有向外暴露。 到这个位置,简单的文件加载逻辑就已经完成了。

文件读取到之后,插件有可能对其进行转换,所以得接着看: 简单起见,我们就不研究模块缓存了。

然后就调用了trsansform方法,在这个方法内,得到插件对文件内容的转换结果。 transform的逻辑很复杂,我们暂时不做研究,后续如果有时间的话,会专门写一篇文章来阐述Rollup的插件系统。

生命周期之:transform

到这个位置,文件的读取内容就已经完成了。

不过,别高兴的太早,我们只分析了一个最简单的入口文件读取,入口文件还会依赖其它文件,这是一个递归的过程,后面的文章才会分析。

文件依赖分析

到这个位置,对于你的能力提高最大之一的章节将会出现,这个位置我们将会使用到AST(Abstract Syntax Tree)的内容,如果你还不知道什么是AST的话,建议先了解一下AST的基础知识点。

文章中会将之前的项目文件源代码转化成AST进行举例子,我使用的转换工具是它:astexplorer.net

回到之前的setSource 插件返回的内容有可能是AST,也有可能不是AST,我们以非AST的内容为例,接下来将进行源代码转成AST的逻辑。 大家先对这块内容引起重视,这个astContext将传入到AST转换工具函数中去,根据解析到的语句决定调用Module相应的方法。 接下来,是从源码上不好看,需要真正把程序运行起来才能知道的逻辑。 在源码里面,nodeContrustors是一大堆的Class,其对应的是各种类型的AST节点处理,然后这些节点处理器均实现了一个initialise的方法。

我们以目前最关心的import语句的为例,看一下它的实现。

我们用前文提到的AST解析工具解析index.js的内容看看:

接下来,我们来看一下我们测试项目运行的堆栈,确定一下我们刚才的阐述是否正确。

从上面的堆栈信息可以验证我们的阐述是正确的。

我们暂时先看这个addSource方法,后面还有一些关键内容,猜测是跟后续的TreeShaking逻辑相关的。 然后记录下依赖内容的线索,一会儿再处理。 此刻,我们需要回过去的位置有点儿远,我们将回到之前的fetchModule方法。 我们暂时先看getResolveStaticDependencyPromises这个方法。 现在读取的就是刚才我们在解析AST时得到的依赖文件线索,此刻又在调用前面我们聊文件加载时提到过的resolveId,至于Rollup处理资源未找到的逻辑,我们暂时就不看了,大家有兴趣的话自行查看源码吧。

我们暂时还不考虑import函数加载资源的方式。

此时Rollup触发moduleParsed的生命周期钩子。

生命周期之:moduleParsed

来看一下解析到的module Info:

我们暂时先留一个猜想,Module类就是我们的文件在Rollup内部的包裹类,即这个类上绑定着文件的资源和文件依赖等复杂信息等等,至于对不对,我们后面再验证。

结语

考虑到篇幅的关系,在本文中,我们暂时就介绍这么多内容,在下一篇文章中我们接着分析Rollup的构建流程,未完待续.....

相关推荐
低调函数41 分钟前
TypeScript和JavaScript的区别
javascript·ubuntu·typescript
中东大鹅1 小时前
【JavaScript】下拉框的实现
前端·javascript·css·html
Domain-zhuo1 小时前
什么是前端构建工具?比如(Vue2的webpack,Vue3的Vite)
前端·javascript·vue.js·webpack·node.js·vue·es6
yanmengying2 小时前
VUE脚手架练习
前端·javascript·vue.js
APItesterCris2 小时前
对于大规模的淘宝API接口数据,有什么高效的处理方法?
linux·服务器·前端·数据库·windows
突然暴富的我2 小时前
html button 按钮单选且 高亮
前端·javascript·html
用户49430538293802 小时前
一种简单粗暴的大屏自适应方案,原理及案例
前端
fury_1232 小时前
怎么获取键值对的键的数值?
java·前端·数据库
午后书香2 小时前
看两道关于异步的字节面试题...
前端·javascript·面试