Vite源码学习(一)——从CLI起步

前言

我将会从本文开始更新自己学习Vite相关的经验与心得。

Vite,从我的使用体验来说,我的它的整体认知是一个BundlelessDev Server+增强自Rollup的打包器。

关于这些基础概念,我在本文中不会做讲解,默认大家已知,如果您还不知道的话,请查阅相应的学习资料。

我在本系列文章中可能会借鉴到之前学习Rollup源码的专栏文章中所提到的知识点,如果你没有阅读过我这部分的文章的话,建议您先阅读我的这个专栏:Rollup

本文仍然会采用之前分析Rollup源码的方式,对一些非主线流程的代码,会进行选择性忽视,如果大家觉得我的阐述忽略了重要的内容的话,可以联系我。

因为Vite的这种架构模式,我们将先学习Vite的构建流程,再学习Vite的Dev Server启动流程,这样将有效的降低我们的学习难度,从而更好的掌握Vite的核心原理。

如何调试Vite的源码?

调试Vite的源码稍微跟Rollup不同,我采用的方式是直接利用Vite源码提供的playground进行调试。(相比于Rollup的release包,Vite的release包是丑化、压缩过的,无法追踪,Rollup仅仅是做了一个TS编译成JS,然后打包JS

各位读者,如果有兴趣的话,大家可以跟着我的示例一起实践。

首先将Vite的源码Clone到本地,github.com/vitejs/vite

然后,我们打开到Vite的子目录:

bash 复制代码
pnpm i
npm run dev

然后,我们随便找一个Playground的子目录:

bash 复制代码
pnpm i
npm run dev

如何调试Vite的源码呢? 我们利用VSCode的JavaScript Debug Terminal,然后在Vite的源码里面编写一个debugger断点指令。 再运行Playground中Demo项目的某个命令(我以build为例),就会命中断点: 如果你在调试过程中,遇到structuredClone is not defined的错误的话,请用比较新的Node版本哦。

从CLI开始

Vite使用的是cac这个包来管理命令的,这个包非常好用,看了Vite的源码之后,我在自己开发公司的CLI工具时也才用这个包,仓库地址:github.com/cacjs/cac

首先是创建cac的实例: 关于cac的使用方法,本文不会阐述,大家有兴趣的话,请参考cac的文档。

vite的CLI主要提供了4个命令,分别是devbuildpreviewoptimize

我们会按build->dev->preview->optimize的顺序分别来阐述Vite所提供的能力。

因为之前我们已经学过了Rollup,所以build命令就是最简单的一个命令了,我们的关注点将会是Vite对Rollup的构建进行了哪些方面的增强。

build 命令分析

我们从cac注册build命令的位置开始看。 它引入build文件中的createBuilder方法,接下来我们就看看这个createBuilder方法做了什么工作? 上面的函数我们折起来了一些内容,主线流程就是读取Vite最终的build配置,然后设置环境,返回构建的上下文。

接下来看一下这里面的细节:

所以,在builder的buildApp调用时,await等待的就是ViteBuilder的实例builderbuild方法的解决。 最终,这个build方法就是来自于跟它一块儿定义的build方法。 接下来,我们就要看这个超级超级长的buildEnvironment方法,这个方法有200多行代码。

我把非关键的代码都折叠起来。 最终,可以很清晰的看到,导入了Rollup 然后,得到Rollup的打包结果,Vite根据用户的配置,决定是把内容写入到磁盘还是io(这个,我们在Rollup源码讲解的时候已经聊过了,大家可以回看一下)。

刚才折起来的代码,我们只挑关键的内容看: Vite增强了Rollup的生命周期,我们看一下是怎么增强这个生命周期的: Vite传递了当前的环境变量,这个环境变量比较复杂,是一个类。 这个位置,暂时我们不展开了,后面我们讲Plugin的时候,应该会再讲到,大家可以不用着急。

可以看到,这个增强,只是改变了插件的执行上下文,并没有改变插件的参数规格,这也符合Vite的文档描述,如果开发者编写的插件没有用到Vite的特征生命周期的话,建议兼容Rollup插件

到这个位置,我们对于Rollup的构建流程其实已经掌握的差不多了,不过,我们暂时还忽略了一个重要的东西,那就是Vite独有的生命周期。

我们单独开一个小节来阐述。

Vite独有的生命周期

在说这个知识点之前,我要向大家阐述Vite扩展了Rollup没有的生命周期,如果我们现在只讨论build环境的话,那么可以参与讨论的生命周期钩子有以下几个:

  • config
  • configResolved
  • transformIndexHtml

在这个位置加载了Vite内置的默认配置,然后跟用户的配置进行合并,接下来,我们的关注重点就可以切到Vite解析配置和加载插件的处理逻辑上面去了。 在加载配置文件的时候,Vite把文件进行了一下打包,其实就是调用的是我们以JS API形式调用的那个build方法,这就是为什么Vite的配置文件支持几乎各种形式的配置的根本原因,怎么样,非常巧妙吧,嘿嘿。 下面这个图中的build方法,就是我们通过JS API调用vite的build方法。 就跟C语言的编译器是C语言写的一样,哈哈哈。

好了,让配置加载完成的时候,就要开始触发config生命周期了: 如果你对Rollup的生命周期不熟悉的话,请参考我的这篇文章。在这篇文章中,详细的讲述了Rollup几种生命周期的原理。

然后,后面就开始了做各种根据环境判断进行合并的逻辑,这儿的代码就不向大家展示了,大家明白这个含义即可。

然后,就触发configResolved生命周期了。

我个人习惯在实际开发中一般在configResolved生命周期中编写逻辑获取Vite的配置,我个人感觉是这样可以拿到的是最终的配置,比较确定。

最后,返回确定的配置给外界,即最终传递给build方法的参数。

Vite内置插件

在本节我们不会浓墨重彩的讲Vite的内置插件,我们只会讲一个插件,向大家说明Vite是如何对构建进行增强的。

之前我们提到了transformIndexHtml这个生命周期,这个生命周期是在一个内置插件中完成的,这个插件叫做vite:build-html

回到之前我们分析Vite解析配置的逻辑处理: Vite在这儿处理插件,就加入了自己的内置插件。 这儿有太多的插件了,后续带着大家分析一些常见的插件,本文中,我们只关注这个vite:build-html插件。

接下来,看一下这个插件的实现: 这儿有接近千行代码,我们先不关注具体实现,我们就只看个大概,Vite利用transform钩子往这个里面处理了html的逻辑。

然后它在生成Bundle的时候,把一些内容注入到html文件中,再把Bundle中已经注入的内容删除。

之前在Rollup源码学习中,向大家表示过,generateBundle这个生命周期非常重要,我们需要重点掌握。

后面的插件分析中,我们着重对这个插件进行分析,在这节,我们的重点关注是Vite如何处理transformIndexHtml这个生命周期的。

Vite把定义transformIndexHtml了的生命周期筛选出来,然后准备调用:

这三类Hooks的调用时机不同,preHooks,调用在transform生命周期, 而剩下的两类Hook调用在generateBundle生命周期。 关于这两者的区别,我们同样放在后面的文章解释为什么。

至此,我们对Vite的构建流程就有了一个整体的认知了。

总结

Vite的对外暴露一个叫做build的方法,这个方法调用Rollup的JS API进行打包,我们可以通过传入Vite所需要的配置直接调用这个方法,这就是所谓的JS API

Vite的CLI同样调用的是这个方法,只不过Vite在CLI中处理了很多默认的参数,其余并没有什么差异。

接下来,我们总结一下Vite的整体构建的流程,当我们调用Vite的CLI时,Vite会尝试去加载我们传入的配置,并且做一些标准化的处理,Vite在加载我们传递的配置文件的时候,会调用JS API中的build方法,把我们的配置进行编译处理,这样可以使得我们可以支持绝大多数类型的配置文件。当解析到配置的时候,Vite就会触发自己独有的生命周期config

然后Vite就会根据各种环境处理一些自己的逻辑,然后跟用户的配置进行合并,合并完成之后,对外触发自己独有的生命周期configResolved

紧接着,Vite就会调用Rollup的构建方法,开始走Rollup的构建逻辑,在Rollup的构建钩子transform中,Vite首先会根据当前处理的文件是否是html文件,然后触发自己独有的生命周期钩子:transformIndexHtml(order为pre的钩子),在Rollup的生成钩子generateBundle的中会再次触发自己独有的生命周期钩子transformIndexHtml(order不为pred的钩子)。

最后,得到Rollup的构建产物,将产物输出到io或者磁盘,完成构建。

以上是我自己总结的Vite的构建流程,欢迎大家刊误。

本文只是简单的讲述了一下Vite的构建逻辑,后面的文章我们还会接着本文作跳过的内容进行讲解,未完待续......

相关推荐
三天不学习17 分钟前
CSS 之 position 定位属性详解
前端·css·定位·position
亦可呀24 分钟前
HTML-CSS-常见标签与样式
前端·css·html
web150850966412 小时前
【MsSQL】数据库基础 & 库的基本操作
前端·数据库·sqlserver
纳尼亚awsl2 小时前
处理元素卡在视野边界,滚动到视野内
前端·javascript·vue.js
黑客Jack2 小时前
XSS Challenges
前端·javascript·xss
黑客-秋凌2 小时前
XSS讲解
前端·xss
永远不会太晚2 小时前
JavaScript的diff库详解(示例:vue项目实现两段字符串比对标黄功能)
前端·javascript·vue.js
Json____2 小时前
网页单机版五子棋小游戏项目练习-初学前端可用于练习~
前端·javascript·css·html·五子棋·网页五子棋单机小程序
m0_548049703 小时前
【MySQL — 数据库基础】深入理解数据库服务与数据库关系、MySQL连接创建、客户端工具及架构解析
数据库·mysql·架构