Skip to content

Vite 源码解读系列(图文结合) —— 本地开发服务器篇

Vite 是一种新型的前端构建工具,能够显著提升前端开发体验。

我将会使用图文结合的方式,尽量让本篇文章显得不那么枯燥(显然对于源码解读类文章来说,这不是个简单的事情)。

本篇文章解读的主要是 vite 源码本体,vite 通过 connect 库提供开发服务器,通过中间件机制实现多项开发服务器配置。而 vite 在本地开发时没有借助 webpack 或是 rollup 这样的打包工具,而是通过调度内部 plugin 实现了文件的转译,从而达到小而快的效果。

好了,话不多说,我们开始吧!

项目目录

本文阅读的 Vite 源码版本是 2.8.0-beta.3

我们先来看看 Vite 这个包的项目目录吧。(如下图)

这是一个集成管理的项目,其核心就是在 packages 里面的几个包,我们来分别看看这几个包是做什么的吧。(如下)

包名作用
viteVite 主库,负责 Vite 项目的本地开发(插件调度)和生产产物构建(Rollup 调度)
create-vite用于创建新的 Vite 项目,内部存放了多个框架(如 react、vue)的初始化模板
plugin-vueVite 官方插件,用于提供 Vue 3 单文件组件支持
plugin-vue-jsxVite 官方插件,用于提供 Vue 3 JSX 支持(通过 专用的 Babel 转换插件)
plugin-reactVite 官方插件,用于提供完整的 React 支持
plugin-legacyVite 官方插件,用于为打包后的文件提供传统浏览器兼容性支持
playgroundVite 内置的一些测试用例及 Demo

这几个源码仓库其实有阅读的价值,但是我们这次还是先专注一下我们本期的主线 —— Vite,从 Vite 开始吧。

接下来我们重点解读 vite 本地开发服务命令 —— vite / vite dev / vite serve。

vite dev

我们来了解一下 vite dev 命令,也就是本地开发服务的内部工作流程。

vite dev 调用了内部的 createServer 方法创建了一个服务,这个服务利用中间件(第三方)支持了多种能力(如 跨域、静态文件服务器等),并且内部创建了 watcher 持续监听着文件的变更,进行实时编译和热重载。

而 createServer 做的事情就是我们需要关注的核心逻辑。

在 createServer 方法中,首先进行了对配置的收集工作 —— resolveConfig。

vite 支持的配置

我们先了解下 vite 项目支持的配置,有些配置可以通过命令行传递,有些只能在配置文件里指定,也可以全部通过 vite.config.js 来进行配置。

配置名称配置说明
configFile指定配置文件位置,默认读取根目录下的 vite.config.js 配置文件
envFile指定环境变量配置文件位置,默认读取根目录下的 .env 环境变量配置文件
root项目的根目录,默认值是执行命令的目录 —— process.cwd()
base类似于 webpack 中的 publicPath,也就是资源的公共基础路径
server本地运行时的服务设置,比如设置 host(主机地址)、port(运行端口)
build构建生产产物时的选项
preview预览选项,在使用了 build 命令后,可以运行 vite preview 对产物进行预览
publicDir静态资源目录,用于放置不需要编译的静态资源,默认值是 public 目录
cacheDir缓存文件夹,用于放置 vite 预编译好的一些缓存依赖,加速 vite 编译速度
mode编译模式,本地运行时默认值是 development,构建生产产物时默认是 production
define定义全局变量,其中开发环境每一项会被定义在全局,而生产环境将会被静态替换
plugins配置 vite 项目的插件
resolve参考 https://cn.vitejs.dev/config/
css关于 css 文件的编译选项
json关于 json 文件的编译选项
esbuild转换 ts 文件
assetsInclude设置需要被 picomatch 模式(一种文件匹配模式)独立处理的文件类型
optimizeDeps依赖优化选项
ssrssr 的相关选项
logLevel调整控制台输出的级别
customLogger自定义 logger,该选项没有暴露,是一个内部选项
clearScreen默认为 true,配置为 false 后,每次重新编译不会清空之前的内容
envDir用于加载环境变量配置文件 .env 的目录,默认为当前根目录
envPrefix环境变量的前缀,带前缀的环境变量将会被注入到项目中
worker配置 bundle 输出类型、plugins 以及 Rollup 配置项

配置断点调试

参考这篇文章

加载配置文件

resolveConfig 的第一步就是加载项目目录的配置文件,如果没有指定配置文件位置,会自动在根目录下寻找 vite.config.js、vite.config.mjs、vite.config.ts、vite.config.cjs。

在读取配置文件后,会将配置文件和初始化配置进行合并,然后得到一份配置。(如下图)

配置收集 - 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 目录后,导出了下面这份配置。

resolveConfig 内部还有一些额外的工作处理,主要是收集内部插件集合(如下图),还有配置一些废弃选项警告信息。

本地开发服务 - createServer

回到 createServer 方法,该方法通过 resolveConfig 拿到配置后,第一时间处理了 ssr(服务端渲染)的逻辑。

如果使用了服务端渲染,则会通过别的方式进行本地开发调试。

如果不是服务端渲染,则会创建一个 http server 用于本地开发调试,同时创建一个 websocket 服务用于热重载。(如下图)

文件监听 + 热重载

然后,vite 创建了一个 FSWatcher 对象,用于监听本地项目文件的变动。(这里使用的是 chokidar 库)

ts
  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 是如何做页面热重载的吧,原理就是监听到文件变更后,重新触发插件编译,然后将更新消息发送给客户端。(如下图)

客户端

update:加载对应的更新文件并执行

full-reload:刷新页面

插件容器

接下来,vite 创建了插件容器(pluginContainer),用于在构建的各个阶段调用插件的钩子。(如下图)

在服务启动时,调用 buildStart

pluginContainer 内部,调用每个插件的 buildStart 方法

中间件机制

接下来是一些内部中间件的处理,当配置开发服务器选项时,vite 内部通过 connect 框架的中间件能力来提供支持。(如下图)

cors 跨域选项,使用 'cors' 库中间件支持。middlewares(原 app)是 connnect 实例。

其中,对 public 目录、公共路径等多项配置都是通过 connect + 中间件实现的,充分地利用了第三方库的能力,而没有重复造轮子。

预构建依赖

接下来,vite 内部对项目中使用到的依赖进行的预构建,一来是为了兼容不同的 ES 模块规范,二来是为了提升加载性能。(如下图)

准备工作就绪后,vite 内部调用 startServer 启动本地开发服务器。(如下)

小结

至此,vite 本身的源码部分就解析完了。

可以看出,在本地开发时,vite 主要依赖 插件 + 中间件体系 来提供能力支持。因为本地开发时只涉及到少量编译工作,所以非常的快。只有在构建生产产物时,vite 才用到了 rollup 进行构建。

Released under the MIT License.