• 作者:老汪软件技巧
  • 发表时间:2024-12-06 00:09
  • 浏览量:

前言

在上一篇文章中,我们已经研究了Rollup是如何加载文件以及如何进行文件的依赖分析的,并没有继续分析它如何完成文件加载的,在这篇文章中,我们接着向下分析。

文件的递归加载

接上文我们分析到的fetchModule方法,然后回忆一下之前加载入口文件的函数调用堆栈:

addEntryModules->loadEntryModule->fetchModule

现在我们把目光聚焦到fetchModuleDependencies这个方法上来。

fetchModuleDependencies是在fetchModule这个函数里面调用的。那么好的,我们此刻再整理一下fetchModuleDependencies的调用堆栈:

fetchModule->fetchModuleDependencies->fetchStaticDependencies->fetchResolvedDependency->fetchModule

这个递归体就已经被我们找到了,这个递归并不是一个函数单独的自身调用,而是几个函数形成的调用链,这也符合我们八股文所背诵的Rollup的处理流程:

Rollup从入口文件开始处理,然后以深度优先搜索的方式依次搜索相关的依赖文件,直到搜索完成所有的依赖文件。

文件循环依赖怎么办?

我们来构建一个循环依赖的例子,看一下最终解析到的Module信息是怎么样的。

在之前,我们我们展示的fetchModule方法的源码,有两个if分支是被折叠起来了的,现在我们着重看其中的一个if分支,在这儿就可以处理循环依赖的问题:

直接从已经加载过的缓存里面读取:

可以明显的看到,这个跟我们处理深拷贝时嗅探循环引用的手段是一样的,所以,数据结构的知识点是万变不离其宗的。

虽然Rollup可以处理循环依赖,但是并不代表你就可以高枕无忧的在项目中编写有循环依赖的代码,在某些场景下是灾难性的,后续我们会聊这个事儿,我之前在实际项目中就遇到过,当时整个人看到报错麻了,哈哈哈,最后还是借助Chatgpt才解决的。

打印一下我临时构建的一个循环依赖的信息:

使用import函数导入的依赖文件处理

在第二篇的时候,我们说暂时还不考虑import函数的加载,此时此刻我们得需要考虑了,否则,什么关键内容都被跳过了,那还学什么呀,哈哈哈。

还是回到之前聊到的fetchModule方法。

在addModuleSource这个函数成功执行完成时候,AST就已经解析完成了的,Rollup负责对应语句处理的AST应该是已经把相应的动态导入依赖记录下来了。

至于是不是我们猜想的这样,一会儿我们还是用堆栈信息来跟踪一下AST的解析就可以一探究竟。

然后触发resolveDynamicImport生命周期。

以下堆栈信息就是AST解析过程中处理imort函数的逻辑:

同一份资源既有静态导入,又有了动态导入,怎么办?

我们先不看源码,直接瞎想,就是瞎想,我确定我不是打错字哦。

既然你已经是使用静态导入了,那么,当前代码执行的时候,所属依赖内容此刻是不是已经加载到磁盘了?那么,既然都加载进来了,分包还有意义吗?那显然是没有意义了,所以如果一个资源既有动态导入又有静态导入,默认情况下Rollup应该是不会对它进行自动分包的,在后续的文章我们来揭晓这个答案。

如何处理外部模块

我们以一个外部配置模块来举例子,就使用lodash进行举例。

首先得修改一下之前的那个rollup.config.mjs:

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",
  },

源码怎么学_源码讲解_

// 配置需要外部导入的内容 external: ["lodash"], output: { dir: "dist", format: 'esm', chunkFileNames: "chunks/[name]-[hash].js", // 配置全局变量 globals: { lodash: "_", }, }, plugins: [ del({ targets: "dist/*" }), resolve(), replace({ "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), preventAssignment: true, }), ], });

我们现在需要看一下,resolveId.external是在什么时候处理的。

这个external判断条件的函数是在Rollup调用的开头,Rollup在做标准化的时候赋值的。

最终的处理逻辑即用到了我们在配置选项里面external的内容:

目前来看,ExternalModule和Module在依赖内容解析的时候还是差不多的,最终的差异肯定是将来体现在代码打包输出的时候,那我们暂时就讨论这么多。

构建生命周期总结

以下是Rollup官方文档上的流程图:

接下来,我为大家总结一下这些生命周期的触发流程:

首先,Rollup触发options和buildStart生命周期,然后解析入口文件,这会导致触发resolveId生命周期。

当确定了入口文件之后,Rollup尝试加载入口文件,因此会触发load生命周期,当加载完成入口文件之后,Rollup开始解析入口文件的内容,因此触发transform生命周期,当解析完成入口文件的内容的时候,modulePasred生命周期就被触发了,因为当前入口文件的内容可能存在import函数的形式导入资源,因此有可能触发resolveDynamicImport生命周期,当然,也有可能是普通的import语句,因此,也有可能再次触发resolveId这个生命周期。

接着再进行的生命周期就跟之前主入口文件的流程是一样的了,前文我们已经分析过了,这是一个递归的解析过程,直到解析完成所有的资源,最后,当所有的文件都处理完成的时候,触发buildEnd生命周期。

有的同学可能会觉得,明明看了我的3篇文章了,一直分析的都是文件内容的读取和解析,怎么构建生命周期都已经分析完了却感觉不到呢?这是因为之前我们一笔带过了transform生命周期,原生的transform是做AST的解析,而Rollup能做很多事儿,是因为插件利用transform生命周期完成了很多复杂的业务逻辑,所以,这两篇文章所阐述的内容就是整个构建过程。

结语

到这个位置为止,我们基本上就已经把Rollup的构建流程搞清楚了,并且向大家分析了循环依赖和外部模块的处理逻辑,接下来就要开始分析Rollup的文件生成处理逻辑。

到这个位置,我们可以证明前文所提到的Module类是Rollup内部处理文件内容的代理对象这个结论可以是正确的。

这个Module类的实例上关联了很多关键的内容,比如文件依赖的资源,文件对外导出的内容,还有文件的AST以及源码等等关键信息,将来在代码合并、生成的时候有重要作用。

下一篇文章开始,我们将开始分析Rollup的产物生成处理逻辑,未完待续......


上一条查看详情 +实现一个客服icon 页面上滑隐藏
下一条 查看详情 +没有了