904 lines
21 KiB
Markdown
904 lines
21 KiB
Markdown
|
|
---
|
||
|
|
tags: [DEV, VITE, architecture, internals]
|
||
|
|
create time: 2026-04-18
|
||
|
|
---
|
||
|
|
|
||
|
|
# Vite 架构原理
|
||
|
|
|
||
|
|
## 核心设计理念
|
||
|
|
|
||
|
|
Vite 的核心创新在于**利用浏览器原生能力和分层优化策略**,将开发环境和生产环境采用完全不同的处理方式。
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
graph TB
|
||
|
|
A[用户请求] --> B{环境判断}
|
||
|
|
B -->|开发环境| C[Dev Server]
|
||
|
|
B -->|生产环境| D[Pre-built Assets]
|
||
|
|
C --> E[ESM 按需编译]
|
||
|
|
E --> F[即时返回]
|
||
|
|
D --> G[Rollup 优化产物]
|
||
|
|
G --> H[CDN/HTTP 服务器]
|
||
|
|
```
|
||
|
|
|
||
|
|
## 开发环境架构
|
||
|
|
|
||
|
|
### 服务启动流程
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
sequenceDiagram
|
||
|
|
participant CLI as 用户命令行
|
||
|
|
participant Config as 配置加载器
|
||
|
|
participant Server as Dev Server
|
||
|
|
participant Plugin as 插件系统
|
||
|
|
participant Monitor as 文件监听器
|
||
|
|
|
||
|
|
CLI->>Config: 读取配置文件
|
||
|
|
Config->>Server: 创建服务器实例
|
||
|
|
Server->>Plugin: 注册所有插件
|
||
|
|
Plugin->>Server: 返回钩子函数
|
||
|
|
Server->>Monitor: 启动文件监听
|
||
|
|
Monitor->>Server: 建立连接
|
||
|
|
Server-->>CLI: 服务器启动完成
|
||
|
|
```
|
||
|
|
|
||
|
|
### HTTP 请求处理流程
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
graph TD
|
||
|
|
A[HTTP 请求] --> B[中间件层]
|
||
|
|
B --> C{请求类型判断}
|
||
|
|
C -->|HTML 文件| D[响应 HTML]
|
||
|
|
C -->|JS/TS 文件| E[transform 链]
|
||
|
|
C -->|CSS 文件| F[CSS 处理器]
|
||
|
|
C -->|静态资源| G[资源处理]
|
||
|
|
E --> H[Plugin 转换]
|
||
|
|
H --> I[返回编译后代码]
|
||
|
|
F --> I
|
||
|
|
G --> J[返回资源引用]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 请求处理实现
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class ViteDevServer {
|
||
|
|
private pluginContainer: PluginContainer
|
||
|
|
private fileWatcher: FSWatcher
|
||
|
|
private moduleGraph: ModuleGraph
|
||
|
|
|
||
|
|
async handleRequest(req: IncomingMessage, res: ServerResponse) {
|
||
|
|
const url = req.url!
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 1. 处理 HTML 入口文件
|
||
|
|
if (url.endsWith('.html')) {
|
||
|
|
await this.handleHtmlRequest(url, res)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. 处理模块请求
|
||
|
|
if (this.isModuleRequest(url)) {
|
||
|
|
const module = await this.transformModule(url)
|
||
|
|
this.sendModuleResponse(module, res)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// 3. 处理静态资源
|
||
|
|
const asset = await this.resolveAsset(url)
|
||
|
|
if (asset) {
|
||
|
|
this.sendAssetResponse(asset, res)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
this.handleError(error, res)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 模块系统实现
|
||
|
|
|
||
|
|
### 模块图结构
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
graph LR
|
||
|
|
A[main.tsx] --> B[App.tsx]
|
||
|
|
A --> C[router.tsx]
|
||
|
|
B --> D[Header.tsx]
|
||
|
|
B --> E[Counter.tsx]
|
||
|
|
C --> F[Pages.tsx]
|
||
|
|
E --> G[useState.ts]
|
||
|
|
E --> H[useEffect.ts]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 模块节点实现
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class ModuleNode {
|
||
|
|
public readonly id: string
|
||
|
|
public url: string
|
||
|
|
public file: string
|
||
|
|
|
||
|
|
// 依赖关系
|
||
|
|
public importers = new Set<ModuleNode>()
|
||
|
|
public importedModules = new Set<ModuleNode>()
|
||
|
|
public importedBindings: Record<string, string[]> = {}
|
||
|
|
|
||
|
|
// 转换结果
|
||
|
|
public transformResult: TransformResult | null = null
|
||
|
|
public lastHMRTimestamp = 0
|
||
|
|
|
||
|
|
// SSR 信息
|
||
|
|
public ssrModule: any = null
|
||
|
|
public ssrTransformResult: TransformResult | null = null
|
||
|
|
|
||
|
|
constructor(id: string) {
|
||
|
|
this.id = id
|
||
|
|
this.url = id
|
||
|
|
this.file = path.normalize(id)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class ModuleGraph {
|
||
|
|
private modules = new Map<string, ModuleNode>()
|
||
|
|
private fileToModulesMap = new Map<string, Set<ModuleNode>>()
|
||
|
|
|
||
|
|
async ensureEntryFromUrl(url: string): Promise<ModuleNode> {
|
||
|
|
const resolved = await this.resolveUrl(url)
|
||
|
|
return this.ensureModule(resolved.id, resolved.url)
|
||
|
|
}
|
||
|
|
|
||
|
|
async ensureModule(
|
||
|
|
id: string,
|
||
|
|
url: string,
|
||
|
|
ssr?: boolean
|
||
|
|
): Promise<ModuleNode> {
|
||
|
|
let module = this.modules.get(id)
|
||
|
|
|
||
|
|
if (!module) {
|
||
|
|
module = new ModuleNode(id)
|
||
|
|
this.modules.set(id, module)
|
||
|
|
|
||
|
|
// 建立文件映射
|
||
|
|
const file = path.resolve(id)
|
||
|
|
let fileModules = this.fileToModulesMap.get(file)
|
||
|
|
if (!fileModules) {
|
||
|
|
fileModules = new Set()
|
||
|
|
this.fileToModulesMap.set(file, fileModules)
|
||
|
|
}
|
||
|
|
fileModules.add(module)
|
||
|
|
}
|
||
|
|
|
||
|
|
return module
|
||
|
|
}
|
||
|
|
|
||
|
|
updateModule(module: ModuleNode, transformed: TransformResult) {
|
||
|
|
module.transformResult = transformed
|
||
|
|
module.lastHMRTimestamp = Date.now()
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 失效模块及其导入者
|
||
|
|
*/
|
||
|
|
invalidateModule(mod: ModuleNode): void {
|
||
|
|
mod.transformResult = null
|
||
|
|
mod.ssrModule = null
|
||
|
|
mod.ssrTransformResult = null
|
||
|
|
|
||
|
|
// 级联失效所有导入者
|
||
|
|
const invalidators = new Set<ModuleNode>()
|
||
|
|
const queue: ModuleNode[] = [...mod.importers]
|
||
|
|
|
||
|
|
while (queue.length > 0) {
|
||
|
|
const importer = queue.pop()!
|
||
|
|
invalidators.add(importer)
|
||
|
|
|
||
|
|
// 如果导入者的代码需要重新编译
|
||
|
|
if (!importer.transformResult) {
|
||
|
|
importer.importers.forEach(dep => {
|
||
|
|
if (!invalidators.has(dep)) {
|
||
|
|
queue.push(dep)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 检测循环依赖
|
||
|
|
*/
|
||
|
|
hasCircularDependency(module: ModuleNode): boolean {
|
||
|
|
const visited = new Set<string>()
|
||
|
|
const stack = [module.id]
|
||
|
|
|
||
|
|
while (stack.length > 0) {
|
||
|
|
const currentId = stack.pop()!
|
||
|
|
|
||
|
|
if (visited.has(currentId)) {
|
||
|
|
console.warn(`[vite] Circular dependency detected: ${currentId}`)
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
visited.add(currentId)
|
||
|
|
const currentModule = this.modules.get(currentId)
|
||
|
|
|
||
|
|
if (currentModule) {
|
||
|
|
currentModule.importedModules.forEach(dep => {
|
||
|
|
if (!visited.has(dep.id)) {
|
||
|
|
stack.push(dep.id)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## ES Module 编译器
|
||
|
|
|
||
|
|
### 源码转换流水线
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
graph TD
|
||
|
|
A[原始源代码] --> B[Plugin Transform 钩子]
|
||
|
|
B --> C[语法转换]
|
||
|
|
C --> D[依赖注入]
|
||
|
|
D --> E[包装 ESM]
|
||
|
|
E --> F[生成 Source Map]
|
||
|
|
F --> G[返回处理后的代码]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 依赖预构建
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class DepsOptimizer {
|
||
|
|
private depsCacheDir: string
|
||
|
|
private scanner: DepsOptimizer
|
||
|
|
|
||
|
|
async scanImports(): Promise<ScanResult> {
|
||
|
|
const entries = await this.getEntryPoints()
|
||
|
|
|
||
|
|
const discovered = await esbuild.context({
|
||
|
|
entryPoints: entries,
|
||
|
|
bundle: true,
|
||
|
|
write: false,
|
||
|
|
onEnd: (result) => {
|
||
|
|
const dependencies = this.extractDependencies(result.metafile)
|
||
|
|
this.updateOptimizedDeps(dependencies)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
return discovered
|
||
|
|
}
|
||
|
|
|
||
|
|
async optimizeDeps(deps: Record<string, string>): Promise<void> {
|
||
|
|
const optimizedDeps = new Map<string, string>()
|
||
|
|
|
||
|
|
for (const [id, file] of Object.entries(deps)) {
|
||
|
|
try {
|
||
|
|
// 转换为 ESM
|
||
|
|
const result = await esbuild.build({
|
||
|
|
entryPoints: [file],
|
||
|
|
bundle: true,
|
||
|
|
format: 'esm',
|
||
|
|
target: 'esnext',
|
||
|
|
write: false,
|
||
|
|
packages: 'external'
|
||
|
|
})
|
||
|
|
|
||
|
|
optimizedDeps.set(id, file)
|
||
|
|
|
||
|
|
// 缓存到 disk
|
||
|
|
await this.saveOptimizedDep(id, result)
|
||
|
|
} catch (e) {
|
||
|
|
console.error(`Failed to optimize dependency: ${id}`, e)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async saveOptimizedDep(id: string, result: BuildResult): Promise<void> {
|
||
|
|
const outputPath = path.join(this.depsCacheDir, `${id}.js`)
|
||
|
|
const metaPath = path.join(this.depsCacheDir, `${id}.js.meta.json`)
|
||
|
|
|
||
|
|
await fs.writeFile(
|
||
|
|
outputPath,
|
||
|
|
result.outputFiles[0].text,
|
||
|
|
'utf-8'
|
||
|
|
)
|
||
|
|
|
||
|
|
await fs.writeFile(
|
||
|
|
metaPath,
|
||
|
|
JSON.stringify({
|
||
|
|
file: id,
|
||
|
|
src: path.basename(outputPath)
|
||
|
|
}),
|
||
|
|
'utf-8'
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 热模块替换 (HMR)
|
||
|
|
|
||
|
|
### HMR 工作机制
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
sequenceDiagram
|
||
|
|
participant File as 文件系统
|
||
|
|
participant Watcher as 监听器
|
||
|
|
participant Server as Dev Server
|
||
|
|
participant WS as WebSocket
|
||
|
|
participant Client as 浏览器客户端
|
||
|
|
|
||
|
|
File->>Watcher: 文件变更
|
||
|
|
Watcher->>Server: 触发 change 事件
|
||
|
|
Server->>Server: 失效相关模块
|
||
|
|
Server->>Server: 重新编译变更模块
|
||
|
|
Server->>WS: 发送 HMR 更新
|
||
|
|
WS->>Client: 接收更新通知
|
||
|
|
Client->>Client: 执行模块替换
|
||
|
|
Client->>Server: 发送确认消息
|
||
|
|
```
|
||
|
|
|
||
|
|
### HMR 协议
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 服务端发送的 HMR 消息类型
|
||
|
|
interface HMRCustomEvent {
|
||
|
|
type: 'custom'
|
||
|
|
event: string
|
||
|
|
data: any
|
||
|
|
}
|
||
|
|
|
||
|
|
interface HMRUpdateEvent {
|
||
|
|
type: 'update'
|
||
|
|
updates: Array<{
|
||
|
|
type: 'js-update' | 'css-update' | 'static-update'
|
||
|
|
path: string
|
||
|
|
acceptedPath: string
|
||
|
|
timestamp: number
|
||
|
|
}>
|
||
|
|
}
|
||
|
|
|
||
|
|
interface HMRConnectedEvent {
|
||
|
|
type: 'connected'
|
||
|
|
}
|
||
|
|
|
||
|
|
interface HMRPruneEvent {
|
||
|
|
type: 'prune'
|
||
|
|
paths: string[]
|
||
|
|
}
|
||
|
|
|
||
|
|
// 客户端处理实现
|
||
|
|
class HMRClient {
|
||
|
|
private ws: WebSocket
|
||
|
|
private pendingImports = new Map<string, Promise<void>>()
|
||
|
|
|
||
|
|
async handleUpdate(updates: HMRUpdateEvent['updates']) {
|
||
|
|
for (const update of updates) {
|
||
|
|
switch (update.type) {
|
||
|
|
case 'js-update':
|
||
|
|
await this.handleJSModuleUpdate(update)
|
||
|
|
break
|
||
|
|
case 'css-update':
|
||
|
|
await this.handleCSSUpdate(update)
|
||
|
|
break
|
||
|
|
case 'static-update':
|
||
|
|
this.handleStaticUpdate(update)
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async handleJSModuleUpdate(update: Extract<HMRUpdateEvent['updates'][0], { type: 'js-update' }>) {
|
||
|
|
const { path, timestamp, acceptedPath } = update
|
||
|
|
|
||
|
|
// 使用浏览器原生 import API 重新导入模块
|
||
|
|
const importPromise = import(`${path}?t=${timestamp}`).then((mod) => {
|
||
|
|
// 查找模块的热替换处理器
|
||
|
|
const modUrl = acceptedPath || path
|
||
|
|
const modObject = window.__vite_module_cache__[modUrl]
|
||
|
|
|
||
|
|
if (modObject && modObject.hot) {
|
||
|
|
// 触发热替换处理器
|
||
|
|
modObject.hot.accept(mod)
|
||
|
|
|
||
|
|
// 执行清理回调
|
||
|
|
if (modObject.hot._dispose) {
|
||
|
|
modObject.hot._dispose()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
this.pendingImports.set(path, importPromise)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 组件级 HMR
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// React 组件的 HMR 实现
|
||
|
|
import { createHot } from '@vitejs/plugin-react/client'
|
||
|
|
|
||
|
|
// 在开发环境中,Vite 会自动注入
|
||
|
|
if (import.meta.hot) {
|
||
|
|
import.meta.hot.accept('./App.tsx', (newModule) => {
|
||
|
|
// React Fast Refresh 处理
|
||
|
|
const prevComponent = window.__vite_plugin_react_component__
|
||
|
|
window.__vite_plugin_react_component__ = newModule.default
|
||
|
|
|
||
|
|
// 触发组件重新渲染
|
||
|
|
window.__vite_plugin_react_rerender__()
|
||
|
|
})
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 插件系统架构
|
||
|
|
|
||
|
|
### 插件生命周期
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
graph TD
|
||
|
|
A[Config 阶段] --> B[ConfigResolved]
|
||
|
|
B --> C[ConfigureServer]
|
||
|
|
C --> D[BuildStart]
|
||
|
|
D --> E[Transform Phase]
|
||
|
|
E --> F[Load Phase]
|
||
|
|
F --> G[BuildEnd]
|
||
|
|
G --> H[CloseBundle]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 插件容器实现
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class PluginContainer {
|
||
|
|
private plugins: Plugin[]
|
||
|
|
private hooks: Record<string, Function[]>
|
||
|
|
|
||
|
|
constructor(plugins: Plugin[]) {
|
||
|
|
this.plugins = plugins
|
||
|
|
this.hooks = {}
|
||
|
|
this.registerHooks(plugins)
|
||
|
|
}
|
||
|
|
|
||
|
|
private registerHooks(plugins: Plugin[]) {
|
||
|
|
const hookNames: validHooks[] = [
|
||
|
|
'buildStart', 'buildEnd',
|
||
|
|
'resolveId', 'load', 'transform',
|
||
|
|
'configureServer'
|
||
|
|
]
|
||
|
|
|
||
|
|
hookNames.forEach(hookName => {
|
||
|
|
this.hooks[hookName] = plugins
|
||
|
|
.map(plugin => plugin[hookName])
|
||
|
|
.filter(Boolean)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
async resolveId(id: string, importer?: string): Promise<string | null> {
|
||
|
|
const resolveHooks = this.hooks['resolveId']
|
||
|
|
|
||
|
|
for (const hook of resolveHooks) {
|
||
|
|
const result = await hook.call(this, id, importer)
|
||
|
|
|
||
|
|
if (result) {
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
async load(id: string): Promise<string | null> {
|
||
|
|
const loadHooks = this.hooks['load']
|
||
|
|
|
||
|
|
for (const hook of loadHooks) {
|
||
|
|
const result = await hook.call(this, id)
|
||
|
|
|
||
|
|
if (result) {
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
async transform(code: string, id: string): Promise<TransformResult | null> {
|
||
|
|
const transformHooks = this.hooks['transform']
|
||
|
|
let result = { code, map: null }
|
||
|
|
|
||
|
|
for (const hook of transformHooks) {
|
||
|
|
const transformed = await hook.call(this, result.code, id)
|
||
|
|
|
||
|
|
if (transformed) {
|
||
|
|
result = {
|
||
|
|
code: transformed.code,
|
||
|
|
map: transformed.map || result.map
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return result.code !== code ? result : null
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 构建系统 (生产环境)
|
||
|
|
|
||
|
|
### Rollup 集成
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
graph TD
|
||
|
|
A[入口文件] --> B[依赖图构建]
|
||
|
|
B --> C[模块解析]
|
||
|
|
C --> D[代码转换]
|
||
|
|
D --> E[代码分割]
|
||
|
|
E --> F[Tree Shaking]
|
||
|
|
F --> G[minification]
|
||
|
|
G --> H[输出生成]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 构建流程实现
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class BuildSystem {
|
||
|
|
async build(config: ResolvedConfig): Promise<BuildResult> {
|
||
|
|
// 1. 分析入口文件
|
||
|
|
const entries = this.resolveEntries(config)
|
||
|
|
|
||
|
|
// 2. 构建模块图
|
||
|
|
const moduleGraph = await this.buildModuleGraph(entries)
|
||
|
|
|
||
|
|
// 3. 代码优化
|
||
|
|
const optimizedModules = await this.optimizeModules(moduleGraph)
|
||
|
|
|
||
|
|
// 4. 代码分割
|
||
|
|
const chunks = this.splitChunks(optimizedModules)
|
||
|
|
|
||
|
|
// 5. 压缩混淆
|
||
|
|
const minifyResults = await this.minifyChunks(chunks)
|
||
|
|
|
||
|
|
// 6. 生成输出
|
||
|
|
const outputs = this.generateOutputs(minifyResults)
|
||
|
|
|
||
|
|
return { modules: outputs, warnings: [], errors: [] }
|
||
|
|
}
|
||
|
|
|
||
|
|
private async optimizeModules(moduleGraph: ModuleGraph): Promise<ModuleNode[]> {
|
||
|
|
const modules = Array.from(moduleGraph.modules.values())
|
||
|
|
|
||
|
|
// 应用插件转换
|
||
|
|
for (const module of modules) {
|
||
|
|
if (module.transformResult) {
|
||
|
|
const result = await this.pluginContainer.transform(
|
||
|
|
module.transformResult.code,
|
||
|
|
module.id
|
||
|
|
)
|
||
|
|
|
||
|
|
if (result) {
|
||
|
|
module.transformResult = result
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return modules
|
||
|
|
}
|
||
|
|
|
||
|
|
private splitChunks(modules: ModuleNode[]): Chunk[] {
|
||
|
|
const chunks: Map<string, Chunk> = new Map()
|
||
|
|
|
||
|
|
// 识别共享依赖
|
||
|
|
const sharedDeps = this.findSharedDependencies(modules)
|
||
|
|
|
||
|
|
// 按配置生成代码分割
|
||
|
|
const manualChunksConfig = {
|
||
|
|
'vendor-react': ['react', 'react-dom'],
|
||
|
|
'vendor-ui': ['antd']
|
||
|
|
}
|
||
|
|
|
||
|
|
Object.entries(manualChunksConfig).forEach(([chunkName, depNames]) => {
|
||
|
|
const chunkModules = modules.filter(mod =>
|
||
|
|
depNames.some(dep => mod.id.includes(dep))
|
||
|
|
)
|
||
|
|
|
||
|
|
chunks.set(chunkName, {
|
||
|
|
name: chunkName,
|
||
|
|
modules: chunkModules,
|
||
|
|
fileName: `${chunkName}.js`
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
return Array.from(chunks.values())
|
||
|
|
}
|
||
|
|
|
||
|
|
private async minifyChunks(chunks: Chunk[]): Promise<MinifyResult[]> {
|
||
|
|
const results: MinifyResult[] = []
|
||
|
|
|
||
|
|
for (const chunk of chunks) {
|
||
|
|
const code = this.generateChunkCode(chunk)
|
||
|
|
|
||
|
|
// 使用 terser 进行压缩
|
||
|
|
const minified = await terser.minify(code, {
|
||
|
|
compress: {
|
||
|
|
drop_console: true,
|
||
|
|
pure_funcs: ['console.log', 'console.info']
|
||
|
|
},
|
||
|
|
mangle: {
|
||
|
|
toplevel: true,
|
||
|
|
properties: {
|
||
|
|
regex: /^_/
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
if (minified.code) {
|
||
|
|
results.push({
|
||
|
|
chunkName: chunk.name,
|
||
|
|
code: minified.code,
|
||
|
|
map: minified.map
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return results
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 性能优化机制
|
||
|
|
|
||
|
|
### 缓存策略
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
graph TD
|
||
|
|
A[请求模块] --> B{缓存检查}
|
||
|
|
B -->|内存缓存命中| C[返回缓存]
|
||
|
|
B -->|磁盘缓存命中| D[加载并返回]
|
||
|
|
B -->|缓存未命中| E[编译模块]
|
||
|
|
E --> F[写入缓存]
|
||
|
|
F --> C
|
||
|
|
```
|
||
|
|
|
||
|
|
### 多层缓存实现
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class CacheSystem {
|
||
|
|
private memoryCache = new Map<string, CacheEntry>()
|
||
|
|
private diskCache: DiskCache
|
||
|
|
private etagCache = new Map<string, string>()
|
||
|
|
|
||
|
|
async get(key: string): Promise<string | null> {
|
||
|
|
// 1. 检查内存缓存
|
||
|
|
const memEntry = this.memoryCache.get(key)
|
||
|
|
if (memEntry && !this.isExpired(memEntry)) {
|
||
|
|
return memEntry.content
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. 检查磁盘缓存
|
||
|
|
try {
|
||
|
|
const diskEntry = await this.diskCache.get(key)
|
||
|
|
if (diskEntry) {
|
||
|
|
// 写入内存缓存
|
||
|
|
this.memoryCache.set(key, {
|
||
|
|
content: diskEntry.content,
|
||
|
|
etag: diskEntry.etag,
|
||
|
|
timestamp: Date.now()
|
||
|
|
})
|
||
|
|
return diskEntry.content
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
// 缓存损坏,忽略
|
||
|
|
}
|
||
|
|
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
async set(key: string, content: string, options: CacheOptions = {}): Promise<void> {
|
||
|
|
const etag = this.generateETag(content)
|
||
|
|
const entry: CacheEntry = {
|
||
|
|
content,
|
||
|
|
etag,
|
||
|
|
timestamp: Date.now(),
|
||
|
|
ttl: options.ttl || 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// 写入内存缓存
|
||
|
|
this.memoryCache.set(key, entry)
|
||
|
|
|
||
|
|
// 持久化到磁盘
|
||
|
|
if (options.persistent) {
|
||
|
|
await this.diskCache.set(key, {
|
||
|
|
content,
|
||
|
|
etag
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private generateETag(content: string): string {
|
||
|
|
const hash = crypto.createHash('sha1')
|
||
|
|
hash.update(content)
|
||
|
|
return hash.digest('hex')
|
||
|
|
}
|
||
|
|
|
||
|
|
private isExpired(entry: CacheEntry): boolean {
|
||
|
|
if (entry.ttl === 0) return false
|
||
|
|
return Date.now() - entry.timestamp > entry.ttl
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### HTTP 缓存优化
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class HTTPCacheManager {
|
||
|
|
setupCacheHeaders(res: ServerResponse, content: string): void {
|
||
|
|
const etag = this.generateETag(content)
|
||
|
|
const lastModified = new Date().toUTCString()
|
||
|
|
|
||
|
|
// 设置缓存头
|
||
|
|
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
|
||
|
|
res.setHeader('ETag', etag)
|
||
|
|
res.setHeader('Last-Modified', lastModified)
|
||
|
|
|
||
|
|
// 检查客户端缓存
|
||
|
|
if (this.request) {
|
||
|
|
const ifNoneMatch = this.request.headers['if-none-match']
|
||
|
|
const ifModifiedSince = this.request.headers['if-modified-since']
|
||
|
|
|
||
|
|
if ((ifNoneMatch && ifNoneMatch === etag) ||
|
||
|
|
(ifModifiedSince && ifModifiedSince === lastModified)) {
|
||
|
|
res.statusCode = 304
|
||
|
|
res.end()
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 进阶架构特性
|
||
|
|
|
||
|
|
### 虚拟模块系统
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 虚拟模块插件示例
|
||
|
|
export function virtualModulePlugin(): Plugin {
|
||
|
|
return {
|
||
|
|
name: 'virtual-module',
|
||
|
|
resolveId(id) {
|
||
|
|
if (id === 'virtual:env') {
|
||
|
|
return '\0virtual:env'
|
||
|
|
}
|
||
|
|
},
|
||
|
|
load(id) {
|
||
|
|
if (id === '\0virtual:env') {
|
||
|
|
const env = {
|
||
|
|
NODE_ENV: process.env.NODE_ENV,
|
||
|
|
VERSION: '1.0.0'
|
||
|
|
}
|
||
|
|
return `export default ${JSON.stringify(env)}`
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 使用虚拟模块
|
||
|
|
import env from 'virtual:env'
|
||
|
|
console.log(env) // { NODE_ENV: 'development', VERSION: '1.0.0' }
|
||
|
|
```
|
||
|
|
|
||
|
|
### Source Map 生成
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class SourceMapGenerator {
|
||
|
|
async generateSourceMap(
|
||
|
|
originalCode: string,
|
||
|
|
transformedCode: string,
|
||
|
|
filePath: string
|
||
|
|
): Promise<SourceMap> {
|
||
|
|
const sourceMap: SourceMap = {
|
||
|
|
version: 3,
|
||
|
|
file: path.basename(filePath),
|
||
|
|
sourceRoot: '',
|
||
|
|
sources: [filePath],
|
||
|
|
names: [],
|
||
|
|
mappings: ''
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
// 使用 magic-string 生成精确的 mappings
|
||
|
|
const magicString = new MagicString(originalCode)
|
||
|
|
const transformed = new MagicString(transformedCode)
|
||
|
|
|
||
|
|
// 映射转换的位置
|
||
|
|
const mappings = this.generateMappings(originalCode, transformedCode)
|
||
|
|
sourceMap.mappings = mappings
|
||
|
|
|
||
|
|
return sourceMap
|
||
|
|
}
|
||
|
|
|
||
|
|
private generateMappings(original: string, transformed: string): string {
|
||
|
|
// 使用 VLQ 编码生成 mappings
|
||
|
|
const originalLines = original.split('\n')
|
||
|
|
const transformedLines = transformed.split('\n')
|
||
|
|
|
||
|
|
const mappings: string[] = []
|
||
|
|
let currentLine = 0
|
||
|
|
let currentColumn = 0
|
||
|
|
|
||
|
|
for (let i = 0; i < transformedLines.length; i++) {
|
||
|
|
const lineMappings: number[][] = []
|
||
|
|
|
||
|
|
for (let j = 0; j < transformedLines[i].length; j++) {
|
||
|
|
// 找到原始代码中的对应位置
|
||
|
|
const { line, column } = this.findOriginalPosition(
|
||
|
|
transformedLines[i],
|
||
|
|
j,
|
||
|
|
originalLines
|
||
|
|
)
|
||
|
|
|
||
|
|
if (line !== -1) {
|
||
|
|
lineMappings.push([
|
||
|
|
j - currentColumn, // 生成的列偏移
|
||
|
|
line - currentLine, // 原始行偏移
|
||
|
|
column // 原始列
|
||
|
|
])
|
||
|
|
|
||
|
|
currentColumn = j
|
||
|
|
currentLine = line
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 使用 VLQ 编码
|
||
|
|
mappings.push(lineMappings.map(mapping =>
|
||
|
|
this.encodeVLQ(mapping)
|
||
|
|
).join(','))
|
||
|
|
|
||
|
|
currentLine = transformedLines.length
|
||
|
|
currentColumn = 0
|
||
|
|
}
|
||
|
|
|
||
|
|
return mappings.join(';')
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 架构对比
|
||
|
|
|
||
|
|
### 与传统构建工具对比
|
||
|
|
|
||
|
|
| 特性 | Vite | Webpack | Parcel |
|
||
|
|
|------|------|---------|--------|
|
||
|
|
| 启动原理 | ESM 按需编译 | 全量打包 | 零配置打包 |
|
||
|
|
| HMR 机制 | 模块级更新 | 全局重编译 | 智能更新 |
|
||
|
|
| 构建产出 | Rollup | 内置 | 内置 |
|
||
|
|
| 配置复杂度 | 低 | 高 | 极低 |
|
||
|
|
| 插件生态 | 新兴 | 成熟 | 有限 |
|
||
|
|
| 开发体验 | 优秀 | 可优化 | 良好 |
|
||
|
|
| 生产构建 | 高质量 | 高质量 | 良好 |
|
||
|
|
|
||
|
|
### 适用场景分析
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
graph TD
|
||
|
|
A[项目类型] --> B{选择构建工具}
|
||
|
|
B -->|React/Vue SPA| C[Vite 推荐]
|
||
|
|
B -->|复杂构建需求| D[Webpack]
|
||
|
|
B -->|零配置快速开发| E[Parcel]
|
||
|
|
B -->|Next.js SSR| F[Next.js 内置]
|
||
|
|
|
||
|
|
C --> G[👍 启动快<br/>👍 HMR 优秀<br/>👍 配置简单]
|
||
|
|
D --> H[👍 生态成熟<br/>👍 高度可定制<br/>👎 复杂]
|
||
|
|
E --> I[👍 开箱即用<br/>👍 智能配置<br/>👎 生态有限]
|
||
|
|
```
|
||
|
|
|
||
|
|
理解 Vite 的架构原理,不仅能帮助我们更好地使用它,还能为开发自定义插件和解决复杂问题提供理论支撑。Vite 的成功在于它巧妙地利用了现代浏览器和工具链的能力,选择了正确的技术路径。
|
||
|
|
|
||
|
|
相关文档:
|
||
|
|
- [[DEV/VITE/配置详解.md]] - 配置与插件开发
|
||
|
|
- [[DEV/VITE/优化实践.md]] - 架构层面的性能优化
|