乐趣区

关于vite: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.jsvite.config.mjsvite.config.tsvite.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 解决了一些 publicDircacheDir 目录后,导出了上面这份配置。

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 激励一下吧!

退出移动版