Vite 源码解读系列(图文结合) —— 本地开发服务器篇
哈喽,很高兴你能点开这篇博客,本博客是针对 Vite
源码的解读系列文章,认真看完后相信你能对 Vite
的工作流程及原理有一个简单的了解。
Vite
是一种新型的前端构建工具,能够显著提升前端开发体验。
我将会使用图文结合的方式,尽量让本篇文章显得不那么枯燥(显然对于源码解读类文章来说,这不是个简单的事情)。
如果你还没有使用过 Vite
,那么你可以看看我的前两篇文章,我也是刚体验没两天呢。(如下)
- Vite + Vue3 初体验 —— Vite 篇
- Vite + Vue3 初体验 —— Vue3 篇
本篇文章解读的主要是 vite
源码本体,vite
通过 connect
库提供开发服务器,通过中间件机制实现多项开发服务器配置。而 vite
在本地开发时没有借助 webpack
或是 rollup
这样的打包工具,而是通过调度内部 plugin
实现了文件的转译,从而达到小而快的效果。
好了,话不多说,我们开始吧!
vite dev
项目目录
本文阅读的 Vite
源码版本是 2.8.0-beta.3
,如果你想要和我一起阅读的话,你可以在这个地址下载 Vite 源码。
我们先来看看 Vite
这个包的项目目录吧。(如下图)
这是一个集成管理的项目,其核心就是在 packages
里面的几个包,我们来分别看看这几个包是做什么的吧。(如下)
包名 | 作用 |
---|---|
vite
|
Vite 主库,负责 Vite 项目的本地开发(插件调度)和生产产物构建(Rollup 调度)
|
create-vite
|
用于创建新的 Vite 项目,内部存放了多个框架(如 react、vue )的初始化模板
|
plugin-vue
|
Vite 官方插件,用于提供 Vue 3 单文件组件支持
|
plugin-vue-jsx
|
Vite 官方插件,用于提供 Vue 3 JSX 支持(通过 专用的 Babel 转换插件)。
|
plugin-react
|
Vite 官方插件,用于提供完整的 React 支持
|
plugin-legacy
|
Vite 官方插件,用于为打包后的文件提供传统浏览器兼容性支持
|
playground
|
Vite 内置的一些测试用例及 Demo
|
这几个源码仓库其实有阅读的价值,但是我们这次还是先专注一下我们本期的主线 —— Vite
,从 Vite
开始吧。
接下来我们重点解读 vite
本地开发服务命令 —— vite / vite dev / vite serve
。
vite dev
我们来了解一下 vite dev
命令,也就是本地开发服务的内部工作流程。
vite dev
调用了内部的 createServer
方法创建了一个服务,这个服务利用中间件(第三方)支持了多种能力(如 跨域
、静态文件服务器
等),并且内部创建了 watcher
持续监听着文件的变更,进行实时编译和热重载。
而 createServer
做的事情就是我们需要关注的核心逻辑。
在 createServer
方法中,首先进行了对配置的收集工作 —— resolveConfig
。
vite 支持的配置
我们正好可以通过源码看看 vite
项目支持的配置,你也可以直接参照 Vite 官方文档。(如下)
配置名称 | 配置说明 |
---|---|
configFile
|
配置文件,默认读取根目录下的 vite.config.js 配置文件
|
envFile
|
环境变量配置文件,默认读取根目录下的 .env 环境变量配置文件
|
root
|
项目的根目录,默认值是执行命令的目录 —— process.cwd()
|
base
|
类似于 webpack 中的 publicPath ,也就是资源的公共基础路径
|
server
|
本地运行时的服务设置,比如设置 host(主机地址)、port(运行端口)…详细配置可以参考 vite 文档 |
build
|
构建生产产物时的选项,可以参考 vite 文档 |
preview
|
预览选项,在使用了 build 命令后,可以运行 vite preview 对产物进行预览,具体配置可以参考 vite 文档
|
publicDir
|
静态资源目录,用于放置不需要编译的静态资源,默认值是 public 目录
|
cacheDir
|
缓存文件夹,用于放置 vite 预编译好的一些缓存依赖,加速 vite 编译速度
|
mode
|
编译模式,本地运行时默认值是 development ,构建生产产物时默认是 production
|
define
|
定义全局变量,其中开发环境每一项会被定义在全局,而生产环境将会被静态替换 |
plugins
|
配置 vite 项目的插件
|
resolve
|
resolve 支持的配置较多,可以参考 vite 文档
|
css
|
关于 css 文件的编译选项,可以参考 vite 文档
|
json
|
关于 json 文件的编译选项,可以参考 vite 文档
|
esbuild
|
看官方文档是用于转换文件的,但是不太清楚具体的工作是做什么的,有了解的麻烦在评论区留言解惑一下 |
assetsInclude
|
设置需要被 picomatch 模式(一种文件匹配模式)独立处理的文件型
|
optimizeDeps
|
依赖优化选项,具体可以参考 vite 文档 |
ssr
|
ssr 的相关选项,具体可以参考 vite 文档
|
logLevel
|
调整控制台输出的级别,默认为 info
|
customLogger
|
自定义 logger ,该选项没有暴露,是一个内部选项
|
clearScreen
|
默认为 true ,配置为 false 后,每次重新编译不会清空之前的内容
|
envDir
|
用于加载环境变量配置文件 .env 的目录,默认为当前根目录
|
envPrefix
|
环境变量的前缀,带前缀的环境变量将会被注入到项目中 |
worker
|
配置 bundle 输出类型、plugins 以及 Rollup 配置项
|
在上面这些配置中,有一部分可以在启动时,通过命令行参数添加,比如通过 vite --base / --mode development
的形式进行设置。
如果你希望该配置可以通过配置读取,也可以全部通过 vite.config.js
来进行配置。
配置断点调试
在粗略看过一遍 vite
支持的配置后,我们回到 createServer
函数,准备开始阅读。
在此之前,如果我们能够直接运行 vite dev
命令并打上断点,能够更好地帮助我们更好的阅读源码,所以我们先来配置一下。
我们需要先进入 vite/packages/vite
,安装依赖,然后在 scripts
中运行 npm run build
,将 vite
构建到 dist
目录中。
然后,我们使用 vscode
的调试功能,创建一个 launch.json
(如下),运行我们的一个 vite
项目。
// launch.json
{"version": "0.2.0","configurations": [{"type": "pwa-node","request": "launch","name": "Launch Program","skipFiles": ["<node_internals>/**"],"program": "packages/vite/bin/vite.js","args": ["/Users/Macxdouble/Desktop/ttt/vite-try"]}]
}
调试配置完成后,我们可以在 resolveConfig
函数中打一个断点,查看效果(文件位置在 dist
目录中,大家需要根据自己的引用找到对应文件)。
加载配置文件
resolveConfig
的第一步就是加载项目目录的配置文件,如果没有指定配置文件位置,会自动在根目录下寻找 vite.config.js
、vite.config.mjs
、vite.config.ts
、vite.config.cjs
。
如果没有找到配置文件,则直接会中止程序。
vite
项目初始化时,会在项目根目录下自动生成vite.config.js
配置文件。
在读取配置文件后,会将配置文件和初始化配置(优先级更高,有部分配置来自于命令行参数)进行合并,然后得到一份配置。(如下图)
配置收集 - resolveConfig
在 createServer
的开头,调用了 resolveConfig
函数,进行配置收集。
我们先来看看 resolveConfig
都做了哪些事情吧。
处理插件执行顺序
首先,resolveConfig
内部处理了插件排序规则,对应下面的排序规则。
在后续处理的过程中,插件将按照对应的排序规则先后执行,这样能够让插件更好地工作在各个生命周期节点。
合并插件配置
在插件排序完成后,vite
的 插件
暴露了一个配置 config
字段,可以通过设置该属性,使插件能够新增或改写 vite
的一些配置。(如下图)
处理 alias
然后,resolveConfig
内部处理了 alias
的逻辑,将指定的 alias
替换成对应的路径。
读取环境变量配置
接下来,resolveConfig
内部找到 env
的配置目录(默认为根目录),然后在目录中读取对应的 env
环境变量配置文件。我们可以看看内部的读取规则优先级(如下图)
可以看出,读取的优先级分别是 .env.[mode].local
、.env.[mode]
。如果不存在对应 mode
的配置文件,则会尝试去寻找 .env.local
、.env
配置文件,读取到配置文件后,使用 doteenv
将环境变量写入到项目中;如果这些环境变量配置文件都不存在的话,则会返回一个空对象。
该环境变量配置文件并不影响项目运行,所以不配置也没有什么影响。
导出配置
接下来,vite
初始化了构建配置,也就是文档中的 build
属性,详情可以参照 构建选项文档
最后,resolveConfig
处理了一些 publicDir
、cacheDir
目录后,导出了下面这份配置。
const resolved: ResolvedConfig = {...config,configFile: configFile ? normalizePath(configFile) : undefined,configFileDependencies,inlineConfig,root: resolvedRoot,base: BASE_URL,resolve: resolveOptions,publicDir: resolvedPublicDir,cacheDir,command,mode,isProduction,plugins: userPlugins,server,build: resolvedBuildOptions,preview: resolvePreviewOptions(config.preview, server),env: {...userEnv,BASE_URL,MODE: mode,DEV: !isProduction,PROD: isProduction},assetsInclude(file: string) {return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file)},logger,packageCache: new Map(),createResolver,optimizeDeps: {...config.optimizeDeps,esbuildOptions: {keepNames: config.optimizeDeps?.keepNames,preserveSymlinks: config.resolve?.preserveSymlinks,...config.optimizeDeps?.esbuildOptions}},worker: resolvedWorkerOptions}
resolveConfig
内部还有一些额外的工作处理,主要是收集内部插件集合(如下图),还有配置一些废弃选项警告信息。
本地开发服务 - createServer
回到 createServer
方法,该方法通过 resolveConfig
拿到配置后,第一时间处理了 ssr
(服务端渲染)的逻辑。
如果使用了服务端渲染,则会通过别的方式进行本地开发调试。
如果不是服务端渲染,则会创建一个 http server
用于本地开发调试,同时创建一个 websocket
服务用于热重载。(如下图)
文件监听 + 热重载
然后,vite
创建了一个 FSWatcher
对象,用于监听本地项目文件的变动。(这里使用的是 chokidar
库)
const watcher = chokidar.watch(path.resolve(root), {ignored: [// 忽略 node_modules 目录的文件变更'**/node_modules/**',// 忽略 .git 目录的文件变更'**/.git/**',// 忽略用户传入的 `ignore` 目录文件的变更...(Array.isArray(ignored) ? ignored : [ignored])],ignoreInitial: true,ignorePermissionErrors: true,disableGlobbing: true,...watchOptions}) as FSWatcher
然后,vite
将多个属性和方法组织成了一个 server
对象,该对象负责启动本地开发服务,也负责服务后续的开发热重载。
接下来,我们看看 watcher
是如何做页面热重载的吧,原理就是监听到文件变更后,重新触发插件编译,然后将更新消息发送给客户端。(如下图)
插件容器
接下来,vite
创建了插件容器(pluginContainer
),用于在构建的各个阶段调用插件的钩子。(如下图)
实际上插件容器是在热重载之前创建的,为了方便阅读,文章将热重载的内容都放在了一起。
中间件机制
接下来是一些内部中间件的处理,当配置开发服务器选项时,vite
内部通过 connect
框架的中间件能力来提供支持。(如下图)
其中,对 public
目录、公共路径等多项配置都是通过 connect
+ 中间件实现的,充分地利用了第三方库的能力,而没有重复造轮子。
预构建依赖
接下来,vite
内部对项目中使用到的依赖进行的预构建,一来是为了兼容不同的 ES 模块规范,二来是为了提升加载性能。(如下图)
准备工作就绪后,vite
内部调用 startServer
启动本地开发服务器。(如下)
// ...
httpServer.listen(port, host, () => {httpServer.removeListener('error', onError)resolve(port)
})
小结
至此,vite
本身的源码部分就解析完了。
可以看出,在本地开发时,vite
主要依赖 插件 + 中间件体系
来提供能力支持。因为本地开发时只涉及到少量编译工作,所以非常的快。只有在构建生产产物时,vite
才用到了 rollup
进行构建。
我们用一张流程图来最后梳理一遍 vite 本地开发服务
内部的工作流程吧。
那么本期文章就到此结束,在下一篇文章,我会挑选 1 - 2 个比较典型的插件或是 build
篇(生产产物构建)来进行源码解析。
最后一件事
如果您已经看到这里了,希望您还是点个赞再走吧~
您的点赞是对作者的最大鼓励,也可以让更多人看到本篇文章!
如果觉得本文对您有帮助,请帮忙在 github 上点亮 star
鼓励一下吧!
Vite 源码解读系列(图文结合) —— 本地开发服务器篇相关推荐
- Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager)
Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager) 本篇主要讲解iOS开发中的网络监控 前言 在开发中,有时候我们需要获取这些信息: 手机是否联网 ...
- Alamofire源码解读系列(十一)之多表单(MultipartFormData)
本篇讲解跟上传数据相关的多表单 前言 我相信应该有不少的开发者不明白多表单是怎么一回事,然而事实上,多表单确实很简单.试想一下,如果有多个不同类型的文件(png/txt/mp3/pdf等等)需要上传给 ...
- Alamofire源码解读系列(九)之响应封装(Response)
本篇主要带来Alamofire中Response的解读 前言 在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下.我更想同大家分享这些顶级框架在设计和编码层次究竟有哪些过人的地方?当然, ...
- Alamofire源码解读系列(五)之结果封装(Result)
本篇讲解Result的封装 前言 有时候,我们会根据现实中的事物来对程序中的某个业务关系进行抽象,这句话很难理解.在Alamofire中,使用Response来描述请求后的结果.我们都知道Alamof ...
- py-faster-rcnn源码解读系列
转载自: py-faster-rcnn源码解读系列(一)--train_faster_rcnn_alt_opt.py - sunyiyou9的博客 - 博客频道 - CSDN.NET http://b ...
- Hadoop源码解读系列目录
Hadoop源码解读系列 1.hadoop源码|common模块-configuration详解 2.hadoop源码|core模块-序列化与压缩详解 3.hadoop源码|core模块-远程调用与N ...
- Alamofire源码解读系列(十二)之请求(Request)
本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...
- spring源码解读系列(八):观察者模式--spring监听器详解
一.前言 在前面的文章spring源码解读系列(七)中,我们继续剖析了spring的核心refresh()方法中的registerBeanPostProcessors(beanFactory)(完成B ...
- 【注意力机制集锦】Channel Attention通道注意力网络结构、源码解读系列一
Channel Attention网络结构.源码解读系列一 SE-Net.SK-Net与CBAM 1 SENet 原文链接:SENet原文 源码链接:SENet源码 Squeeze-and-Excit ...
最新文章
- 给定二叉树先序、中序遍历序列,求后序遍历
- java打包后的怎么转成源码_PDF转成Word或PPT后还是图片是怎么回事?
- 分享自制的C#和VB Code互转工具
- 构建程序员快捷代码键盘
- java.math.BigDecimal cannot be cast to java.lang.Integer
- linux远程控制本地用户登录,linux 本地无法登录 远程可以登陆的解决办法
- php mysql 1040_php – 如何修复消息:SQLSTATE [08004] [1040]连接太多
- C语言快速找答案,C语言单选题找答案
- 多元线性回归模型检验-续上篇
- 配置mysql环境变量之后,仍然无法打开mysql的解决方法
- 2022年汽车修理工(中级)上岗证题库及答案
- 灵敏性,特异性,阳性预测值,阴性预测值
- Google知识集锦
- 正确使用计算机键盘的方法是,电脑键盘指法练习的方法
- 一个超赞的开源串口虚拟示波器项目,玩起来!
- 如何将Mac设置为热点?
- 英语语音篇 - 看词能读
- 玩转亚马逊 AWS IoT(1): IoT 业务梳理
- 系统构架设计应考虑的因素
- 工业网络安全 智能电网,SCADA和其他工业控制系统等关键基础设施的网络安全(总结)...