Skip to content

Vite 源代码导览

项目结构

vite/
├── packages/
│   ├── vite/                    # 核心包
│   │   ├── src/
│   │   │   ├── node/            # Node.js 特定代码
│   │   │   │   ├── server/      # 开发服务器
│   │   │   │   │   ├── index.ts         # 服务器入口
│   │   │   │   │   ├── middlewares.ts   # 中间件
│   │   │   │   │   ├── moduleGraph.ts   # 模块图
│   │   │   │   │   └── pluginContainer.ts # 插件容器
│   │   │   │   ├── optimizer/    # 依赖优化器
│   │   │   │   │   ├── index.ts         # 优化器入口
│   │   │   │   │   ├── esbuildDepPlugin.ts # esbuild 插件
│   │   │   │   │   └── scan.ts          # 依赖扫描
│   │   │   │   └── build/       # 构建器
│   │   │   │       ├── index.ts         # 构建入口
│   │   │   │       └── plugins.ts       # 构建插件
│   │   │   ├── client/           # 客户端代码
│   │   │   │   ├── client.ts     # 客户端入口
│   │   │   │   ├── env.ts        # 环境变量
│   │   │   │   └── overlay.ts    # 错误覆盖层
│   │   │   ├── shared/           # 共享代码
│   │   │   │   ├── constants.ts  # 常量
│   │   │   │   ├── utils.ts      # 工具函数
│   │   │   │   └── config.ts     # 配置
│   │   │   ├── plugins/          # 内置插件
│   │   │   │   ├── index.ts      # 插件入口
│   │   │   │   ├── css.ts        # CSS 插件
│   │   │   │   ├── esbuild.ts    # esbuild 插件
│   │   │   │   ├── json.ts       # JSON 插件
│   │   │   │   └── asset.ts      # 资源插件
│   │   │   └── index.ts          # 主入口
│   │   └── package.json
│   ├── plugin-react/             # React 插件
│   └── plugin-vue/               # Vue 插件
├── playground/                  # 测试项目
├── scripts/                      # 构建脚本
└── package.json

核心文件详解

1. 开发服务器入口

文件路径: packages/vite/src/node/server/index.ts

功能: 创建和配置 Vite 开发服务器

关键代码:

typescript
// 创建开发服务器
export async function createServer(
  inlineConfig: InlineConfig = {}
): Promise<ViteDevServer> {
  // 1. 解析配置
  const config = await resolveConfig(inlineConfig, 'serve')

  // 2. 创建 HTTP 服务器
  const server = await createHttpServer(config)

  // 3. 创建中间件
  const middlewares = createMiddlewares(config)

  // 4. 创建模块图
  const moduleGraph = new ModuleGraph()

  // 5. 创建插件容器
  const pluginContainer = new PluginContainer(config)

  // 6. 创建 WebSocket 服务器
  const ws = createWebSocketServer(config)

  // 7. 返回服务器实例
  return {
    config,
    server,
    middlewares,
    moduleGraph,
    pluginContainer,
    ws,
    // ... 其他方法
  }
}

// 创建 HTTP 服务器
async function createHttpServer(
  config: ResolvedConfig
): Promise<http.Server> {
  const { serverConfig } = config
  const middlewareMode = !!serverConfig.middlewareMode

  const server = http.createServer((req, res) => {
    // 处理请求
    middlewares(req, res)
  })

  return server
}

设计决策:

  • 使用 Node.js 原生 http 模块创建服务器
  • 通过中间件模式处理请求
  • 分离关注点:服务器、中间件、模块图、插件容器

2. 中间件系统

文件路径: packages/vite/src/node/server/middlewares.ts

功能: 处理不同类型的请求

关键代码:

typescript
// 创建中间件
export function createMiddlewares(
  config: ResolvedConfig
): Connect.Server {
  const middlewares = connect()

  // 1. 基础中间件
  middlewares.use(compression())
  middlewares.use(urlencoded())
  middlewares.use(json())

  // 2. 开发服务器中间件
  middlewares.use(baseMiddleware(config))
  middlewares.use('/', indexHtmlMiddleware(config))
  middlewares.use('/@fs/', servePublicMiddleware(config))
  middlewares.use('/@vite/', serveStaticMiddleware(config))

  // 3. 转换中间件
  middlewares.use(transformMiddleware(config))

  // 4. HMR 中间件
  middlewares.use(hmrMiddleware(config))

  // 5. 代理中间件
  if (config.server.proxy) {
    middlewares.use(proxyMiddleware(config))
  }

  return middlewares
}

// 转换中间件
function transformMiddleware(config: ResolvedConfig) {
  return async (req: IncomingMessage, res: ServerResponse, next: NextFunction) => {
    const url = req.url!

    // 1. 检查是否需要转换
    if (!isTransformRequest(url)) {
      return next()
    }

    // 2. 读取文件
    const file = await resolveFile(url, config)

    // 3. 应用插件转换
    const result = await pluginContainer.transform(file, url)

    // 4. 返回转换结果
    res.setHeader('Content-Type', 'application/javascript')
    res.end(result.code)
  }
}

设计决策:

  • 使用 connect 库作为中间件基础
  • 按顺序应用中间件
  • 每个中间件专注于特定功能

3. 模块图

文件路径: packages/vite/src/node/server/moduleGraph.ts

功能: 追踪模块依赖关系,管理 HMR

关键代码:

typescript
// 模块图类
export class ModuleGraph {
  private urlToModuleMap = new Map<string, ModuleNode>()
  private idToModuleMap = new Map<string, ModuleNode>()
  private fileToModulesMap = new Map<string, Set<ModuleNode>>()

  // 获取模块
  getModuleByUrl(url: string): ModuleNode | undefined {
    return this.urlToModuleMap.get(url)
  }

  // 创建模块
  async ensureEntryFromUrl(url: string): Promise<ModuleNode> {
    const mod = this.urlToModuleMap.get(url)
    if (mod) {
      return mod
    }

    const module = new ModuleNode(url)
    this.urlToModuleMap.set(url, module)

    // 解析模块
    await this.resolveModule(module)

    return module
  }

  // 解析模块
  private async resolveModule(module: ModuleNode) {
    // 1. 读取文件
    const source = await fs.readFile(module.url, 'utf-8')

    // 2. 解析导入
    const imports = parseImports(source)

    // 3. 解析依赖
    for (const imp of imports) {
      const dep = await this.ensureEntryFromUrl(imp)
      module.imports.add(dep)
      dep.importers.add(module)
    }
  }

  // 更新模块(HMR)
  async updateModule(file: string) {
    const modules = this.fileToModulesMap.get(file)
    if (!modules) return

    // 1. 重新转换模块
    for (const mod of modules) {
      await this.transformModule(mod)
    }

    // 2. 触发 HMR
    await this.triggerHMR(modules)
  }

  // 触发 HMR
  private async triggerHMR(modules: Set<ModuleNode>) {
    const updates: Update[] = []

    for (const mod of modules) {
      const update: Update = {
        type: 'js-update',
        timestamp: Date.now(),
        path: mod.url,
        acceptedPath: mod.url
      }
      updates.push(update)
    }

    // 发送 HMR 更新
    this.ws.send({
      type: 'update',
      updates
    })
  }
}

// 模块节点类
class ModuleNode {
  url: string
  id: string
  imports: Set<ModuleNode>
  importers: Set<ModuleNode>
  transformResult: TransformResult | null
  lastHMRTimestamp: number

  constructor(url: string) {
    this.url = url
    this.id = generateId(url)
    this.imports = new Set()
    this.importers = new Set()
    this.transformResult = null
    this.lastHMRTimestamp = 0
  }
}

设计决策:

  • 使用 Map 数据结构快速查找模块
  • 双向追踪:imports 和 importers
  • 支持增量更新

4. 依赖优化器

文件路径: packages/vite/src/node/optimizer/index.ts

功能: 使用 esbuild 预构建依赖

关键代码:

typescript
// 依赖优化器类
export class DepOptimizer {
  private config: ResolvedConfig
  private cacheDir: string
  private metadataPath: string

  constructor(config: ResolvedConfig) {
    this.config = config
    this.cacheDir = path.join(config.root, 'node_modules/.vite')
    this.metadataPath = path.join(this.cacheDir, '_metadata.json')
  }

  // 扫描依赖
  async scanDeps(): Promise<Record<string, string>> {
    const entry = this.config.root
    const result = await esbuild.context({
      entryPoints: [entry],
      bundle: true,
      write: false,
      plugins: [esbuildDepPlugin(this.config)]
    })

    const deps: Record<string, string> = {}

    // 解析依赖
    for (const file of result.outputFiles) {
      const imports = parseImports(file.text)
      for (const imp of imports) {
        if (isDep(imp)) {
          deps[imp] = resolveDep(imp)
        }
      }
    }

    return deps
  }

  // 预构建依赖
  async preBundle(deps: Record<string, string>) {
    const entryPoints = Object.keys(deps)

    const result = await esbuild.build({
      entryPoints,
      bundle: true,
      format: 'esm',
      write: true,
      outdir: this.cacheDir,
      plugins: [esbuildDepPlugin(this.config)]
    })

    // 保存元数据
    await this.saveMetadata(deps)

    return result
  }

  // 检查缓存
  async checkCache(): Promise<boolean> {
    if (!fs.existsSync(this.metadataPath)) {
      return false
    }

    const metadata = JSON.parse(fs.readFileSync(this.metadataPath, 'utf-8'))
    const currentHash = await this.computeHash()

    return metadata.hash === currentHash
  }

  // 计算哈希
  private async computeHash(): Promise<string> {
    const lockFile = path.join(this.config.root, 'package-lock.json')
    const content = fs.readFileSync(lockFile, 'utf-8')
    return createHash('sha256').update(content).digest('hex')
  }

  // 保存元数据
  private async saveMetadata(deps: Record<string, string>) {
    const hash = await this.computeHash()
    const metadata = {
      hash,
      deps,
      timestamp: Date.now()
    }

    fs.writeFileSync(this.metadataPath, JSON.stringify(metadata, null, 2))
  }
}

设计决策:

  • 使用 esbuild 进行快速预构建
  • 基于文件哈希的缓存策略
  • 元数据持久化

5. 插件容器

文件路径: packages/vite/src/node/server/pluginContainer.ts

功能: 管理插件,执行插件钩子

关键代码:

typescript
// 插件容器类
export class PluginContainer {
  private plugins: Plugin[]
  private hooks: Map<string, Function[]>

  constructor(config: ResolvedConfig) {
    this.plugins = config.plugins
    this.hooks = new Map()

    // 收集插件钩子
    this.collectHooks()
  }

  // 收集插件钩子
  private collectHooks() {
    for (const plugin of this.plugins) {
      // 配置钩子
      if (plugin.config) {
        this.addHook('config', plugin.config)
      }

      // 解析钩子
      if (plugin.resolveId) {
        this.addHook('resolveId', plugin.resolveId)
      }

      // 加载钩子
      if (plugin.load) {
        this.addHook('load', plugin.load)
      }

      // 转换钩子
      if (plugin.transform) {
        this.addHook('transform', plugin.transform)
      }
    }
  }

  // 添加钩子
  private addHook(name: string, hook: Function) {
    if (!this.hooks.has(name)) {
      this.hooks.set(name, [])
    }
    this.hooks.get(name)!.push(hook)
  }

  // 执行钩子
  async runHook(name: string, ...args: any[]): Promise<any> {
    const hooks = this.hooks.get(name)
    if (!hooks) return

    for (const hook of hooks) {
      const result = await hook(...args)
      if (result !== undefined) {
        return result
      }
    }
  }

  // 解析模块 ID
  async resolveId(
    id: string,
    importer: string
  ): Promise<ResolveIdResult | null> {
    return await this.runHook('resolveId', id, importer)
  }

  // 加载模块
  async load(id: string): Promise<LoadResult | null> {
    return await this.runHook('load', id)
  }

  // 转换模块
  async transform(
    code: string,
    id: string
  ): Promise<TransformResult | null> {
    return await this.runHook('transform', code, id)
  }
}

设计决策:

  • 钩子按顺序执行
  • 第一个返回非 undefined 的结果被使用
  • 支持异步钩子

6. CSS 插件

文件路径: packages/vite/src/plugins/css.ts

功能: 处理 CSS 文件

关键代码:

typescript
// CSS 插件
export function cssPlugin(): Plugin {
  return {
    name: 'vite:css',

    // 转换 CSS
    async transform(code, id) {
      if (!id.endsWith('.css')) {
        return null
      }

      // 1. 处理 @import
      const imports = parseImports(code)
      for (const imp of imports) {
        code = code.replace(imp, `import '${imp}'`)
      }

      // 2. 处理 url()
      const urls = parseUrls(code)
      for (const url of urls) {
        const resolved = resolveUrl(url)
        code = code.replace(url, resolved)
      }

      // 3. 返回转换结果
      return {
        code: `import { updateStyle } from '/@vite/client'\n` +
              `const css = ${JSON.stringify(code)}\n` +
              `updateStyle('${id}', css)\n`,
        map: null
      }
    }
  }
}

// 解析 CSS 导入
function parseImports(code: string): string[] {
  const imports: string[] = []
  const regex = /@import\s+['"]([^'"]+)['"]/g

  let match
  while ((match = regex.exec(code)) !== null) {
    imports.push(match[1])
  }

  return imports
}

// 解析 CSS URL
function parseUrls(code: string): string[] {
  const urls: string[] = []
  const regex = /url\(['"]([^'"]+)['"]\)/g

  let match
  while ((match = regex.exec(code)) !== null) {
    urls.push(match[1])
  }

  return urls
}

设计决策:

  • 将 CSS 转换为 JavaScript
  • 使用客户端运行时注入样式
  • 支持热更新

7. esbuild 插件

文件路径: packages/vite/src/plugins/esbuild.ts

功能: 使用 esbuild 转换 TypeScript 和 JSX

关键代码:

typescript
// esbuild 插件
export function esbuildPlugin(config: ResolvedConfig): Plugin {
  return {
    name: 'vite:esbuild',

    // 转换代码
    async transform(code, id) {
      // 1. 检查是否需要转换
      if (!isTransformable(id)) {
        return null
      }

      // 2. 使用 esbuild 转换
      const result = await esbuild.transform(code, {
        loader: getLoader(id),
        target: config.build.target,
        sourcemap: true
      })

      // 3. 返回转换结果
      return {
        code: result.code,
        map: result.map
      }
    }
  }
}

// 获取加载器
function getLoader(id: string): esbuild.Loader {
  if (id.endsWith('.ts')) return 'ts'
  if (id.endsWith('.tsx')) return 'tsx'
  if (id.endsWith('.jsx')) return 'jsx'
  if (id.endsWith('.js')) return 'js'
  return 'js'
}

// 检查是否可转换
function isTransformable(id: string): boolean {
  return /\.(ts|tsx|jsx|js)$/.test(id)
}

设计决策:

  • 使用 esbuild 进行快速转换
  • 自动检测文件类型
  • 生成 source map

8. 客户端代码

文件路径: packages/vite/src/client/client.ts

功能: 客户端运行时,处理 HMR

关键代码:

typescript
// 客户端入口
async function initClient() {
  // 1. 创建 WebSocket 连接
  const ws = new WebSocket(`ws://${location.host}`)

  // 2. 监听 HMR 更新
  ws.addEventListener('message', async (event) => {
    const payload = JSON.parse(event.data)

    if (payload.type === 'update') {
      await handleHMRUpdate(payload.updates)
    }
  })

  // 3. 监听错误
  ws.addEventListener('error', (error) => {
    console.error('WebSocket error:', error)
  })
}

// 处理 HMR 更新
async function handleHMRUpdate(updates: Update[]) {
  for (const update of updates) {
    if (update.type === 'js-update') {
      await handleJSUpdate(update)
    }
  }
}

// 处理 JS 更新
async function handleJSUpdate(update: JSUpdate) {
  const { path, acceptedPath, timestamp } = update

  // 1. 重新导入模块
  const mod = await import(path + `?t=${timestamp}`)

  // 2. 调用 HMR 接受
  if (mod.__hmrAccept) {
    await mod.__hmrAccept()
  }

  // 3. 通知客户端
  console.log(`[HMR] ${path} updated`)
}

// 更新样式
export function updateStyle(id: string, css: string) {
  const style = document.getElementById(id) as HTMLStyleElement

  if (style) {
    style.textContent = css
  } else {
    const newStyle = document.createElement('style')
    newStyle.id = id
    newStyle.textContent = css
    document.head.appendChild(newStyle)
  }
}

// 启动客户端
initClient()

设计决策:

  • 使用 WebSocket 进行实时通信
  • 支持模块级热更新
  • 样式热更新

关键算法

1. 依赖解析算法

typescript
// 递归解析依赖
async function resolveDeps(
  entry: string,
  seen: Set<string> = new Set()
): Promise<string[]> {
  if (seen.has(entry)) {
    return []
  }

  seen.add(entry)

  const deps: string[] = [entry]

  // 1. 读取文件
  const code = await fs.readFile(entry, 'utf-8')

  // 2. 解析导入
  const imports = parseImports(code)

  // 3. 递归解析依赖
  for (const imp of imports) {
    const resolved = await resolveId(imp, entry)
    if (resolved) {
      const subDeps = await resolveDeps(resolved, seen)
      deps.push(...subDeps)
    }
  }

  return deps
}

2. HMR 算法

typescript
// 触发 HMR
async function triggerHMR(file: string) {
  // 1. 找到受影响的模块
  const modules = findAffectedModules(file)

  // 2. 检查模块是否接受 HMR
  const accepted = modules.filter(mod => mod.acceptsHMR)

  if (accepted.length === 0) {
    // 整页刷新
    sendFullReload()
    return
  }

  // 3. 更新接受的模块
  for (const mod of accepted) {
    await updateModule(mod)
  }

  // 4. 发送 HMR 更新
  sendHMRUpdate(accepted)
}

// 找到受影响的模块
function findAffectedModules(file: string): ModuleNode[] {
  const modules: ModuleNode[] = []
  const visited = new Set<string>()

  // BFS 遍历
  const queue = [file]

  while (queue.length > 0) {
    const current = queue.shift()!
    if (visited.has(current)) continue

    visited.add(current)

    const mod = moduleGraph.getModuleByUrl(current)
    if (mod) {
      modules.push(mod)

      // 添加导入者
      for (const importer of mod.importers) {
        queue.push(importer.url)
      }
    }
  }

  return modules
}

3. 缓存算法

typescript
// 缓存管理器
class CacheManager {
  private cache: Map<string, CacheEntry>

  constructor() {
    this.cache = new Map()
  }

  // 获取缓存
  get(key: string): any {
    const entry = this.cache.get(key)
    if (!entry) return null

    // 检查是否过期
    if (Date.now() > entry.expires) {
      this.cache.delete(key)
      return null
    }

    return entry.value
  }

  // 设置缓存
  set(key: string, value: any, ttl: number = 60000) {
    this.cache.set(key, {
      value,
      expires: Date.now() + ttl
    })
  }

  // 清除缓存
  clear() {
    this.cache.clear()
  }

  // 清除过期缓存
  clearExpired() {
    const now = Date.now()
    for (const [key, entry] of this.cache.entries()) {
      if (now > entry.expires) {
        this.cache.delete(key)
      }
    }
  }
}

interface CacheEntry {
  value: any
  expires: number
}

总结

Vite 的源代码结构清晰,模块化程度高。核心功能包括:

  1. 开发服务器: 处理 HTTP 请求,提供 HMR
  2. 中间件系统: 处理不同类型的请求
  3. 模块图: 追踪模块依赖关系
  4. 依赖优化器: 使用 esbuild 预构建依赖
  5. 插件系统: 扩展 Vite 功能
  6. 内置插件: 处理 CSS、TypeScript、JSX 等
  7. 客户端运行时: 处理 HMR 和样式注入

通过理解这些核心文件和算法,可以深入掌握 Vite 的工作原理。

架构师AI杜公众号二维码

扫描二维码关注"架构师AI杜"公众号,获取更多技术内容和最新动态