Vite源码学习(九)——DEV流程中的核心类(下)

前言

在上一篇文章中,我们借着讲DevEnvironment这个类的机会,阐述清楚了DEV流程中VITE是如何处理客户端一个请求发出,中间件分发请求,插件容器调度,插件处理内容并返回结果这一整套流程。

我们几乎是把和DevEnvironment相关的核心类都分析了个大概,在这篇文章中,我们继续来分析VITE中的核心类,这篇文章主要阐述*ModuleNode*ModuleGraph

为什么这两个类前面都加了一个*呢?因为VITE有两套NodeGraph的处理逻辑。

如果我们不分析热更新相关的内容的话,*ModuleNode*ModuleGraph涵盖的内容还是相对比较简单的,为了行文顺序的考虑,我们还是把一些内容放到VITE的WebSocket和热更新相关的篇章上去讲述。

好了,废话不多说,咱们就开始吧。

图的基本概念

在大学的数据结构课程中,老师教我们使用图(Graph)用来表达复杂的逻辑关系。

图在正常的前端业务开发中很少用得到,我也是刷算法的过程中逐渐掌握的图的相关知识点(除非绘制逻辑关系数字大屏业务,比如地图相关的业务,拓扑图等)(我们说的是严格意义上的图,因为树也是特殊的图,而实际应用中树的使用场景还是比较多的)。

大学的数据结构课程中,老师教我们使用邻接矩阵或者邻接表表示图,但是我们在实际的实现中几乎都是使用引用关系表示,实质上也是邻接表的表示。

图,主要有两个概念非常重要,一个是顶点(Vertex),另外一个是(Edge),如果边有方向,则称为有向图,否则为无向图

对于有向图,从一个顶点A到另外一个顶点B的边E,称为A的出度,称为B的入度

一个图中,从一个顶点开始出发,通过边的关系,能够访问到所有的顶点组成的集合,称为一个连通分量,如果一个图只有一个连通分量,那么这个图则称之为连通图

为了方便大家的理解,接下来我就直接以VITE的业务场景来阐述图的相关知识点了。

比如,我们的一个文件,就是VITE资源依赖图中的一个节点,这个节点上保存有很多信息,比如它的文件名称,路径,格式,最后修改时间,文件内容,等等这些属性。

我们在一个文件中,可能引入其它的文件,这个代码层面的引用关系,会被Rollup进行依赖分析(在VITE的DEV流程中不是的),它首先读取代码的内容,转成AST(抽象语法树),然后在遍历AST节点的过程中,分析和其它文件的依赖关系,这个依赖关系就是边,明显这个引用关系是有方向的,所以是一个有向图

另外,我们在处理的时候,比如一个文件变化了的时候,我们想知道哪些文件也应该随之一起变化,我们还需要知道谁是引用者,谁是被引用者

在这个图中,我们肯定是从一个主文件开始解析,然后递归的分析其中的依赖,直到分析完成所有的依赖,我们的这个依赖关系图肯定是一个连通图,但是由于我们在写代码过程中,由于可能存在循环引入的关系,则这个依赖关系图中可能存在环

在之前分析Rollup的文章中我们在分析打包的时候其实已经向大家聊过拓扑排序这个概念了,拓扑排序也是图中的一个重要知识点,考虑到有些同学还不知道这个概念,我就再顺便提一嘴。

对应构建工具来说,我们最终打包结果代码应该如何组织,这是一个问题,合并之后的代码块肯定是先定义再使用吧?如何组织这样的代码片段的输出,比如a.js->b.js->c.js,最终合并的Chunk肯定是先输出c.js,然后输出b.js,最后输出a.js,这个过程就是拓扑排序。

关于拓扑排序大家可以查看我之前的文章:拓扑排序在前端开发中的应用场景

在之前Rollup的文章中我们说过,如果依赖图中存在循环依赖的话,要确保这个依赖环被分包到一个Chunk中,否则打包的结果是无法正常加载的。

下面的这个图,就是我以某个依赖关系画的一个依赖关系图(它既是一张图,它表达的也是一个图,哈哈),其中,

  • 绿色带边框的矩形表示的整个依赖关系图。
  • 蓝色的色块表示的是某个资源文件。
  • 蓝色的线(importModules)表示的是引用关系,比如index.js->a.js
  • 红色的线(importers),表示的是被引用关系,a.js需要知道它被index.js引用着。

EnvironmentModuleNode

我们在上节中已经向大家阐述了图的一些基本知识点,接下来我们看VITE的资源图中的节点类:

这个类超级简单,其中importedModules存储的是当前资源文件引用的资源集合,而importers存储的是自己被谁引用着。(不要问我是怎么知到的,哈哈哈,我也是打断点调试出来的)

给大家看一下为什么是这样的:

EnvironmentModuleGraph

这个类就是我们上上小节中的那个背景框,这个类中存储了项目中所有的资源。 这个类关联着上一篇文章中我们聊过的DevEnviroment的实例,并且,这个类在初始化的时候,把如何加载资源路径的方法传递进来了,这个resolveId方法就是插件系统的resolveId方法。

VITE为了考虑到资源的高效加载,在处理过程中有效利用了缓存,所以我们看到这个这个Graph类中定义了4个哈希表,为的就是能够快速的找到资源,如果没有找到资源,则从磁盘读取,并且将获取到的结果关联到这些哈希表上,下次再取用的时候,直接利用缓存,从而提高了DevServer的效率。

在上述代码中,为什么只有fileToModulesMap是一个多级的Set,因为有些文件它是可能对应多个文件的,比如我们的.vue文件,它会被编译成2-3(主要是看你的写法,具体大家可以用@vitejs/vue-plugin试一下,有些时候template部分也会被抽出来,所以说是2-3个部分)个部分,以app.vue文件为例,主体部分会被编译成app.vue?lang=js,这个文件中包含template编译结果render函数和我们本来写在script标签里面的JS脚本,而我们写的style标签则会编译成app.vue?lang=css&index=0这样的内容,这个查询字符串中index标记的顺序,可以使得我们在vue文件中书写多个style标签。所以这就是为什么VITE会这样设计的原因。

以下是一个断点调试的内存信息:

以下是在初始化的过程中传入的插件系统的resolveId方法:

VITE的缓存有几种策略,我们就以URL的处理过程来看一下处理逻辑: 调用插件容器的resolveId方法,确定url和资源id的关联关系: 在之前我们聊transformRequest方法的时候,当时我们还没有聊缓存,现在在回过头来看的话,大家就觉得一目了然了。

如果URL能够命中缓存则走缓存,否则尝试以文件id查找缓存,若没有查询到,则真正的走磁盘加载了。

EnvironmentModuleGraph还有很多方法,我们只是在这篇文章中暂时不讲,后续的热更新的文章中我们还会再分析它的,大家敬请期待。

ModuleGraph和ModuleNode

这两个类,从目前VITE源码的注释来看,似乎是不会再用了。

目前看起来里面的处理逻辑主要是内聚了SSR和CSR的处理逻辑,可能是VITE经过了重构,对此,我们只需要知道这两个曾经的功臣已经功成身退,无需关心就好了。

总结

本文主要向大家阐述了一些图的知识点,并且向大家阐述了VITE是如何使用图这个数据结构处理DEV流程中的资源引用关系的,本文的内容相对来说还是比较简单的,不过大家不要太过乐观,因为在本文我们暂时还跳过了较为核心的热更新相关的内容。

关于VITE中的核心类,我认为重要的就这些了,当你掌握了这几个核心类之后,再弄清楚VITE使用中间件映射请求到最终的结果这个整套流程,可以说就已经把VITE整个DEV流程中最核心的内容掌握了,不过我们要的是全部,所以还要接着继续往更深的知识盲区钻研。

从下一篇文章开始,我们将开始阐述VITE中WebSocket的处理流程以及热更新相关的处理逻辑,届时我们再详细分析EnvironmentModuleGraph类中的内容,未完待续,敬请期待......

相关推荐
徐小夕3 小时前
我用 AI 撸了个开源"万能预览器":浏览器直接打开 Office、CAD 和 3D 模型
前端·vue.js·github
小码哥_常3 小时前
Flutter Android 延迟加载代码指南:提升应用性能的关键
前端
这是个栗子3 小时前
TypeScript(三)
前端·javascript·typescript·react
kvo7f2JTy3 小时前
基于机器学习算法的web入侵检测系统设计与实现
前端·算法·机器学习
北风toto3 小时前
前端CSS样式详细笔记
前端·css·笔记
nanfeiyan4 小时前
git commit
前端
KaneLogger4 小时前
如何把AI方面的先发优势转化为结构优势
人工智能·程序员·架构
前端精髓6 小时前
移除 Effect 依赖
前端·javascript·react.js
码云之上6 小时前
从一个截图函数到一个 npm 包——pdf-snapshot 的诞生记
前端·node.js·github
码事漫谈7 小时前
AI提效,到底能强到什么程度?
前端·后端