Vite Client源码
Vite
vite介绍
vite是一种新型前端构建工具,它提供两部分功能:
一个开发服务器,提供快速的模块热更新(HMR)
一套构建指令,使用Rollup打包代码
vite相较于传统的构建工具(例如webpack),最大的特点就是速度快,无论的冷启动还是热更新,速度都非常的快。
为什么vite那么快
vite的速度快体现在冷启动和热更新上。
预构建
vite将代码分为源码和依赖两部分,源码就是用户开发的源代码,而依赖通常是引入的第三方包。
vite会对依赖执行依赖预构建,vite使用了esbuild来实现依赖预构建,并且会将结果缓存到/node_modules/.vite/目录下,只有一些特殊情况才会重新预构建(例如修改vite配置文件)。
预构建主要做了两件事,一是将CommonJs模块和UMD模块转换成ESM模块,二是重写裸模块地址。预构建可以加快页面的加载速度。
ESM
vite之所以要将CommonJs模块和UMD模块转换成ESM模块,这是因为vite自身的机制就是借助ESM来承担一部分打包工作。
首先在index.html中使用ESM,引入入口文件
浏览器会识别ESM,自动加载main.tsx文件,然后会将main.tsx中import的文件也加载了,这样就可以将所有依赖的文件都加载下来。
也就是说vite会通过ESM的形式提供源码,让浏览器承担了一部分打包工作,而vite只需要在这个过程中做一些处理工作,例如将非JavaScript文件内容转换成JavaScript能够理解的代码,转换导入文件的地址等。
采用这种方式很明显的一个好处是不需要vite自己去构建依赖关系,并且加载时只加载使用的依赖和源码,没有用到的依赖或源码不会被加载。
Http缓存
前面说过vite通过ESM加载文件,而在这个过程vite还借助了http缓存来实现加载优化。
前面说过vite会将应用中的模块分为两类:源码和依赖,对于依赖而言,基本上是不会变动的(例如你引入的组件库),如果每个更新时都处理成本会很高,因此vite会对依赖模块使用强缓存Cache-Control: max-age=31536000,immutable。
对于源码而言,这部分可能是会经常改变的,因此会使用协商缓存cache-control: no-cache。
冷启动
vite在开发环境下会启动一台node服务器来提供源码和进行其他操作,vite只会在浏览器请求源码时进行转换并按需提供源码,而对于不会使用到的源码则不会进行处理,而webpack则会将源码和依赖全部构建成bundle,因此启动时速度非常慢。


热更新
在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite会将被编辑的模块重新导入(import()),并且还借助http缓存优化模块加载策略,依赖使用了强缓存不会被重新加载,而源码也只会对更改过的文件进行重新加载,使得无论应用大小如何,HMR 始终能保持快速更新。
Vite client原理分析
vite组成
vite由client和node两部分组成,分别是客户端和服务器端。
热更新
vite的热更新是依靠websocket来实现的。vite其实可以看做是一台静态资源服务器,它会监听项目源码文件的改动,如果有文件改动了,则通过ws通知客户端执行对应的回调,回调中会通过import()+时间缀的方式重新加载改动后的代码文件。
type有以下几种值
connected 初次连接时
update 更新
custom 自定义事件
full-reload 全更新,即刷新页面,浏览器当前所在的html文件被更改时执行
prune 更新后有模块不再被导入,则清除副作用
error 出现错误时执行
我们通常更关心update的处理。
css-update只针对通过link引入的css文件,加入是通过import导入css文件,那么仍然是通过js-update来更新。
可以看到vite拿到了update信息后是先执行了fetchUpdate函数,接着又执行了queueUpdate函数,接下来我们看一下这两个函数做了什么。
fetchUpdate借助import()实现了模块的重新导入,而queueUpdate则是调用了回调函数来更新渲染,这里使用了队列结构,因为http响应模块文件的顺序不一定就是模块的请求顺序,所以需要用队列确保以与发送加载时相同的顺序来重新渲染。
接下来我们看看浏览器通过ESM请求模块时,vite对模块文件做了什么处理。
我们创建一个vue文件,并导入一些模块
刷新重新加载,可以在看到浏览器请求得到的文件是这样的:
可以看出
vite服务器会对导入的路径进行转换
vite在每个模块中都添加了
createHotContext函数,并且将自身模块路径作为参数传入执行。vite通过
import.meta.hot.accept导入了渲染该模块的方法
既然使用到了createHotContext函数,那我们就来看一下这个函数的定义。
可以看到之前在fetchUpdate中用到的hotModulesMap是在这里定义的。createHotContext函数返回了一个hot对象,事实上这个就是模块中使用到的import.meta.hot对象,上面挂载了一些方法,其中比较重要的是accept方法,它的作用是当模块更新时执行对应的回调,在vite服务器返回模块文件时其实就会执行这个方法,回调函数就是重新渲染该模块的方法,因此当前面执行重新import()后,会调用模块的回调函数来渲染更新,此外accept也被暴露给开发者使用,开发者可以通过import.meta.hot.accept()来传入自定义回调函数。
综上,vite的热更新流程大致流程如下
vite服务器和浏览器建立websocket连接
vite服务器在返回模块文件时会创建热更新上下文,并将该模块的渲染方法传入到回调中
服务器监听文件改动,如果有文件更新则将模块文件path、更新type等信息发给浏览器
浏览器接收到后重新
import()该模块文件,并执行重新渲染的回调方法
最后更新于