vite 源码 - 创建服务

缓存配置

vite 中有一个全局变量,叫 usedConfigs,是一个 WeakSet,作用是缓存配置,避免重复创建。

js 复制代码
const usedConfigs = new WeakSet<ResolvedConfig>()
if (usedConfigs.has(config)) {
    throw new Error(`There is already a server associated with the config.`)
}

usedConfigs.add(config)

初始化静态文件

在这一步做的工作其实就一句话,即递归读取静态资源文件夹(public)所有文件,并返回层级为 1 的文件路径数组。

js 复制代码
// 缓存配置对应的静态资源文件
const publicFilesMap = new WeakMap<ResolvedConfig, Set<string>>()
export async function initPublicFiles(config) {
  let fileNames
  // 递归读取文件
  fileNames = await recursiveReaddir(config.publicDir)
  // 提取文件名和在静态资源文件夹中的路径
  const publicFiles = new Set(
    fileNames.map((fileName) => fileName.slice(config.publicDir.length)),
  )
  publicFilesMap.set(config, publicFiles)
  return publicFiles
}
const initPublicFilesPromise = initPublicFiles(config)
const publicFiles = await initPublicFilesPromise

处理 https 配置

这里的逻辑很简单,就是把相关的证书等文件读取到内存中,不存在则返回 undefined

scss 复制代码
export async function resolveHttpsConfig(https) {
  if (!https) return undefined

  const [ca, cert, key, pfx] = await Promise.all([
    readFileIfExists(https.ca),
    readFileIfExists(https.cert),
    readFileIfExists(https.key),
    readFileIfExists(https.pfx),
  ])
  return { ...https, ca, cert, key, pfx }
}
const httpsOptions = await resolveHttpsConfig(config.server.https)

处理输出目录配置

这里的输出目录指的是打包后的资源放置的文件目录,对应配置中的 outDir 属性。但是在 vite 中同样支持 rollupOptionsrollup 相关配置。所以在这里需要兼顾 rollupOptions.output 字段。

js 复制代码
export function getResolvedOutDirs(root: string,outDir: string,outputOptions) {
  const resolvedOutDir = path.resolve(root, outDir)
  if (!outputOptions) return new Set([resolvedOutDir])

  return new Set(
    // arraify 的作用是判断是否为数组,否则用数组包装并返回
    arraify(outputOptions).map(({ dir }) =>
      dir ? path.resolve(root, dir) : resolvedOutDir,
    ),
  )
}
const resolvedOutDirs = getResolvedOutDirs(
    config.root,
    config.build.outDir,
    config.build.rollupOptions.output,
)

是否清空输出目录

vite 中在构建前是否清空输出目录 outDir 默认为 true,如果显式传入了 emptyOutDir 字段那么会直接返回 emptyOutDir,如果没有,那么会判断每一个输出目录是否在项目中,如果有一个不在,则返回 false

typescript 复制代码
export function resolveEmptyOutDir(
  emptyOutDir: boolean | null,
  root: string,
  outDirs: Set<string>,
  logger?: Logger,
): boolean {
  if (emptyOutDir != null) return emptyOutDir

  for (const outDir of outDirs) {
    if (!normalizePath(outDir).startsWith(withTrailingSlash(root))) {
      return false
    }
  }
  return true
}
const emptyOutDir = resolveEmptyOutDir(
    config.build.emptyOutDir,
    config.root,
    resolvedOutDirs,
)

处理文件监听配置

这里主要做的是对监听需要忽略的文件做初始化。对应到配置中则是 ignored 数组。在这里会加入默认需要忽略的文件,如 **/.git/** **/node_modules/**,还会加入开发者自定义文件,最后加入所有输出目录。

typescript 复制代码
export function resolveChokidarOptions(
  options: WatchOptions | undefined,
  resolvedOutDirs: Set<string>,
  emptyOutDir: boolean,
  cacheDir: string,
): WatchOptions {
  const { ignored: ignoredList, ...otherOptions } = options ?? {}
  const ignored: WatchOptions['ignored'] = [
    '**/.git/**',
    '**/node_modules/**',
    '**/test-results/**', // Playwright
    escapePath(cacheDir) + '/**',
    ...arraify(ignoredList || []),
  ]
  if (emptyOutDir) {
    ignored.push(
      ...[...resolvedOutDirs].map((outDir) => escapePath(outDir) + '/**'),
    )
  }

  const resolvedWatchOptions: WatchOptions = {
    ignored,
    ignoreInitial: true,
    ignorePermissionErrors: true,
    ...otherOptions,
  }

  return resolvedWatchOptions
}

初始化 connect 实例

connect 是一个极简的中间件框架。它提供一个"洋葱式/链式"的中间件堆栈:每个请求进来后按顺序执行 middleware(req, res, next),你在中间件里处理、转发(next())、或直接结束响应(res.end())。

示例用法 复制代码
var connect = require('connect');
var http = require('http');

var app = connect();

// gzip/deflate outgoing responses
var compression = require('compression');
app.use(compression());

//create node.js http server and listen on port
http.createServer(app).listen(3000);

在 vite 中,直接调用。

arduino 复制代码
const middlewares = connect() as Connect.Server

创建 http 服务器

这个创建 http 服务器的逻辑相对简单,直接看代码。

javascript 复制代码
export async function resolveHttpServer(
  { proxy }: CommonServerOptions,
  app: Connect.Server,
  httpsOptions?: HttpsServerOptions,
): Promise<HttpServer> {
  // 如果没有传入 httpsOptions ,直接使用 http1.1 创建 server
  if (!httpsOptions) {
    const { createServer } = await import('node:http')
    return createServer(app)
  }

  // 如果存在代理,使用 http1.1 创建 server,避免兼容性问题
  if (proxy) {
    const { createServer } = await import('node:https')
    return createServer(httpsOptions, app)
  } else { // 使用 http2 创建 server
    const { createSecureServer } = await import('node:http2')
    return createSecureServer(
      {
        // 提高每个 HTTP/2 会话的内存上限,避免大量请求时触发 `ENHANCE_YOUR_CALM`(过载)
        // 导致的 502。
        maxSessionMemory: 1000,
        ...httpsOptions,
        allowHTTP1: true,
      },
      app,
    )
  }
}

结束

接下来,会分析 webSocket 服务器的创建,这里的逻辑相对复杂,放后面看。

相关推荐
q***498610 分钟前
MySQL数据的增删改查(一)
android·javascript·mysql
我有一个object11 分钟前
uniapp上传文件报错:targetSdkVersion设置>=29后在Android10+系统设备不支持当前路径。请更改为应用运行路径!
前端·javascript·vue.js·uniapp
北极糊的狐15 分钟前
关于jQuery 事件绑定,记录常用事件类型及核心注意事项
前端·javascript·jquery
星空的资源小屋17 分钟前
极速精准!XSearch本地文件搜索神器
javascript·人工智能·django·电脑
_Kayo_21 分钟前
vue3 computed 练习笔记
前端·vue.js·笔记
CodeSheep25 分钟前
VS 2026 正式发布,王炸!
前端·后端·程序员
无奈何杨25 分钟前
CoolGuard事件查询增加策略和规则筛选,条件结果展示
前端·后端
梦里不知身是客1128 分钟前
正则表达式常见的介绍
前端·javascript·正则表达式
初学小白...1 小时前
HTML知识点
前端·javascript·html
鹏多多1 小时前
flutter睡眠与冥想数据可视化神器:sleep_stage_chart插件全解析
android·前端·flutter