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 服务器的创建,这里的逻辑相对复杂,放后面看。

相关推荐
跟着珅聪学java3 小时前
vue通过spring boot 下载文件教程
前端·spring boot·后端
码侯烧酒3 小时前
前端IM应用开发中的难点解析与总结
前端·websocket
非专业程序员4 小时前
逆向分析CoreText中的字体级联/Font Fallback机制
前端·ios
我的写法有点潮4 小时前
彻底理解 JavaScript 的深浅拷贝
前端·javascript·vue.js
Holin_浩霖4 小时前
前端原型与继承全景学习图解版
前端
palpitation974 小时前
iOS Universal Link 配置
前端
csgo打的菜又爱玩4 小时前
Vue 学习与实践大纲(后端视角)
前端·学习
柯南二号5 小时前
【大前端】Vue 和 React 的区别详解 —— 两大前端框架深度对比
前端·vue.js·前端框架
IT_陈寒5 小时前
「Redis性能翻倍的5个核心优化策略:从数据结构选择到持久化配置全解析」
前端·人工智能·后端