
缓存配置
在 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
中同样支持 rollupOptions
即 rollup
相关配置。所以在这里需要兼顾 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 服务器的创建,这里的逻辑相对复杂,放后面看。