哈喽,很快乐你能点开这篇博客,本博客是针对 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
激励一下吧!