第二章:Vite 工作原理深度解析
2.1 整体架构
┌─────────────────────────────────────────────────────────┐
│ Vite 架构 │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 开发服务器 │ │ 生产构建 │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ • connect │ │ • Rollup │ │
│ │ • esbuild │ │ • 插件系统 │ │
│ │ • 中间件 │ │ • 代码分割 │ │
│ │ • HMR │ │ • 优化 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ 插件系统 (Plugin API) │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
2.2 开发服务器启动流程
步骤 1:依赖预构建
javascript
// 预构建前
import React from 'react' // → 需要从 node_modules 解析
// 预构建后
import React from '/node_modules/.vite/deps/react.js'
为什么需要预构建?
- 兼容性:将 CommonJS 转换为 ESM
- 性能:将多个内部模块合并,减少请求数
- 缓存 :预构建结果缓存在
node_modules/.vite
使用 esbuild 的原因:
- 速度极快(Go 语言编写)
- 支持 TypeScript、JSX
- 比 Rollup 快 10-100 倍
步骤 2:启动 HTTP 服务器
Vite 使用 connect 创建 HTTP 服务器,并通过中间件处理请求:
javascript
// 简化版中间件链
app.use(transformMiddleware) // 转换源码
app.use(staticMiddleware) // 处理静态文件
app.use(hmrMiddleware) // HMR 连接
步骤 3:模块解析
当浏览器请求模块时:
浏览器请求 → /src/main.js
↓
Vite 解析路径:
1. 检查是否是预构建依赖
2. 应用别名(alias)
3. 处理模块导入
2.3 模块处理流程
核心:ES Modules 按需编译
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
// Vite 会转换成:
import { createApp } from '/node_modules/.vite/deps/vue.js'
import App from '/src/App.vue'
处理不同类型文件
| 文件类型 | 处理方式 |
|---|---|
.js/.ts |
移除导入路径,添加 /@fs/ 前缀 |
.vue/.svelte |
编译成 JavaScript |
.css |
注入到 <style> 标签 |
| 图片/字体 | 返回 URL,由浏览器加载 |
2.4 热更新(HMR)机制
架构
┌──────────┐ WebSocket ┌──────────┐
│ Vite │ ◄──────────────► │ 浏览器 │
│ Server │ │ │
└──────────┘ └──────────┘
│ │
│ 文件变化 │ 接收更新
▼ ▼
监听文件 → 计算边界 → 替换模块
HMR 流程
- 监听文件变化 :通过
chokidar监听文件系统 - 重新编译模块:只编译变化的模块
- 发送更新:通过 WebSocket 发送更新信息
- 模块替换:浏览器接收并执行 HMR 回调
Vue 的 HMR 示例
javascript
// 在编译后的 .vue 文件中
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// 更新组件
newModule.render()
})
}
2.5 依赖预构建深入
预构建触发条件
- 首次启动开发服务器
node_modules发生变化- 新增依赖
- 手动清除缓存
预构建缓存机制
javascript
// _metadata.json 记录依赖信息
{
"hash": "abc123", // 依赖锁定文件 hash
"browserHash": "def456", // 浏览器访问 hash
"optimized": {
"react": {
"file": "/react.js",
"src": "../../node_modules/react/index.js"
}
}
}
2.6 生产构建原理
为什么开发和生产不同?
- 开发:追求快速启动和更新 → 按需编译
- 生产:追求体积和性能 → 全面优化
Rollup 构建流程
源码 → 解析 → 转换 → 打包 → 压缩 → 输出
↓ ↓ ↓ ↓ ↓ ↓
入口 AST 插件 合并 混淆 dist/
优化策略
- 代码分割:动态导入自动分割
- CSS 处理:提取到单独文件
- 预加载:自动注入预加载指令
- Tree Shaking:移除未使用代码
2.7 中间件系统
Vite 的中间件基于 connect,按顺序处理请求:
javascript
// 简化版中间件
const middlewares = [
// 1. 请求重写(/@fs/ 路径处理)
rewriteMiddleware,
// 2. 源码转换(编译 .vue/.ts 等)
transformMiddleware,
// 3. HMR 服务
hmrMiddleware,
// 4. 静态文件服务
staticMiddleware
]
2.8 性能优化技巧
开发环境
- 使用
optimizeDeps.include强制预构建某些模块 - 排除大型库的预构建(如
moment) - 配置
server.fs.strict控制文件系统访问
生产环境
- 启用
build.minify(默认 true) - 配置
build.rollupOptions.output.manualChunks - 使用
build.target指定浏览器版本
2.9 源码结构解析
vite/
├── packages/
│ ├── vite/ # 核心代码
│ │ ├── src/
│ │ │ ├── node/ # Node.js 部分(服务器、构建)
│ │ │ └── client/ # 客户端部分(HMR 运行时)
│ │ └── client.ts # 客户端入口
│ └── plugin-*/ # 官方插件
2.10 关键技术栈
- esbuild:依赖预构建、TypeScript 编译
- Rollup:生产构建
- connect:HTTP 服务器
- chokidar:文件监听
- ws:WebSocket 服务
- magic-string:源码操作
本章小结
Vite 的核心创新在于:
- 开发环境:利用浏览器原生 ESM + esbuild 预构建
- 生产环境:利用 Rollup 的成熟打包能力
- HMR:基于模块边界的高效更新
这种"开发/生产分离"的策略,兼顾了开发体验和生产质量。