从零读 Vite 4.0 源码(二)———— vite 基本框架

接上文

确定基本框架

首先需要创建一个 server:

以下代码节选自 vite : packages/vite/src/node/cli.ts

// Node.js 中间件框架,详见:https://www.npmjs.com/package/connect
import connect from 'connect'

const middlewares = connect()

// 创建一个 HTTP 服务器对象
const { createServer } = await import('node:https');
const httpServer = createServer({}, middlewares);

// 启动服务
await httpServer.listen()

每次打开网页,浏览器会开始发送获取文件源码的请求,以请求入口文件 main.js 为例,请求头的内容大致如下:

GET / HTTP/1.1
// 对于源代码,设置为不缓存
Cache-Control: no-cache
Pragma: no-cache

响应体的内容大致如下:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: text/html
Cache-Control: no-cache
// 标识资源的特定版本,用于缓存验证,由 vite 处理生成
Etag: W/"2b5-KZ3sz8zeNzxWXnhE8SqlEtHRDac"

从中我们可以看到 vite 是如何利用浏览器的特性,去管理资源的缓存的。

当客户端请求某个资源时,服务器可以通过计算该资源的 Etag 值并将其发送给客户端。客户端可以将这个 Etag 值存储起来,并在后续请求时将其发送给服务器。服务器可以通过比较客户端发送的 Etag 值与当前资源的 Etag 值来验证资源是否发生变化。

如果 Etag 值相同,表示资源未发生变化,则 vite 会返回一个特殊的响应状态码(例如 304 Not Modified),告诉客户端可以使用缓存的版本,从而避免不必要的数据传输。

转换文件

上文提到了 connect 库,它是十分常用的 Node.js 中间件框架。

它的作用是简化和增强 Node.js 的 HTTP 服务器功能,提供了一种方便的方式来处理 HTTP 请求和生成 HTTP 响应。它允许你通过使用中间件来构建功能强大的服务器,每个中间件都可以处理请求、修改请求或响应,以及将控制权传递给下一个中间件。

使用 connect 库,可以按照特定的顺序将多个中间件连接在一起,每个中间件都可以对请求和响应进行修改。

因此,我们可以创建一个中间件,用来转换文件。

const middlewares = connect()

// 使用 transformMiddleware 中间件
middlewares.use(transformMiddleware())

function transformMiddleware() {
  return async function viteTransformMiddleware(req, res, next) {
    // resolve, load and transform
    const result = await transformRequest(url);

    // 设置缓存相关请求头
    res.setHeader('Etag', result.etag);
    res.setHeader('Cache-Control', 'no-cache')
    res.statusCode = 200

    // 将转换后的内容返回
    res.end(result.code)

  }
}

核心函数 transformRequest 的实现:

async function transformRequest(url) {
  // 处理 url 路径
  const resolved = await pluginContainer.resolveId(url)
  const id = resolved?.id

  // 加载 url 路径下的资源内容
  const loadResult = await pluginContainer.load(id)

  // 对 url 路径下的资源内容进行转换
  const loadResult = await pluginContainer.transform(id)
}

上面代码中 pluginContainer 是指 vite 构建工具中的插件容器,允许开发者通过插件来扩展和定制构建过程,并使插件能够参与到构建过程的不同阶段。所以以上代码的含义是调用了核心插件的钩子函数,来完成对文件的转换。

值得一提的是,由于 vite 使用了 rollup 作为其底层的构建引擎。Vite 的插件系统是基于 rollup 的插件系统进行扩展和定制。因此 vite 的插件开发方式与 rollup 的插件开发方式类似,开发者可以使用相似的插件 API 和钩子函数来编写 vite 插件。

vite 在构建的过程中,以下是部分常用的钩子(与 rollup 一致),用于实现特定的功能或修改代码:

  • resolveId: 用于解析模块的路径,一般用于处理特定的模块路径格式。
  • load: 用于加载模块的源代码,一般用于对源码进行修改。
  • transform: 用于转换模块的源代码,一般用于使用 Babel 进行ES6+到ES5的转换,处理 TS 语法等。

vite 内置了多个核心的插件, 用于实现一些特定的功能。让我们来看一下 vite 提供了哪些核心插件:

  1. vite:css-post 对导入的 CSS 文件,主要用于处理 CSS 导入,CSS 预处理器支持,自动注入 CSS 到 HTML 页面等。

  2. vite:asset 负责处理静态资源文件的导入、路径解析和哈希处理,以及相应的构建和优化工作。

  3. vite:dynamic-import-vars 处理动态 import 语法。

  4. vite:json 处理 json 后缀文件格式。

  5. vite: import-analysis 通过分析源代码中的导入语句,该插件可以构建出模块之间的依赖关系,主要用于热重载等功能的实现。

  6. vite:esbuild 提供了 ESBuild 的集成,用于快速和高效地进行 JavaScript 代码的转译和打包。用于预构建等功能的实现。

以上插件的具体实现会在下文进行详细说明。

监听文件变化

目前为止,我们基本走通了最基本的工具流程。但是目前每次开发者改动文件,都需要手动刷新才能看到效果。 因此,我们需要一个 watcher 来监听开发者对文件的改动,每次改动,会触发热更新或刷新页面等操作。

// 用于实时监测文件和目录的变化
import chokidar from 'chokidar'

const watcher = chokidar.watch(
    // 要监听的文件路径
    [root],
    // 可选的配置项
    resolvedWatchOptions,
)

// 监听文件变化
watcher.on('change', async (file) => {
    // 处理变化的文件
    // 通知浏览器刷新页面
})

// 监听新增文件
watcher.on('add', async (file) => {})
// 监听文件删除
watcher.on('unlink', async (file) => {})

Tobe continue…


309 Words

2023-09-22 14:06 +0000