《Vite 设计与实现》完整目录
第18章 设计模式与架构决策
开篇引言
在前面十七章中,我们从源码层面深入分析了 Vite 的每一个核心子系统。现在是时候退后一步,从更高的抽象层次审视 Vite 的设计智慧了。
Vite 不仅是一个构建工具,更是一本优秀的软件架构教材。它在有限的领域中展示了大量可迁移的设计模式:中间件栈模式处理请求管线,基于图的失效传播驱动 HMR,插件 Hook 管线提供了灵活的扩展点,按需转换架构消除了不必要的计算,多环境隔离通过类型层次实现了关注点分离,缓存策略在多个层次上优化了性能。
本章将提炼这些设计模式,分析其动机、权衡和适用场景,最终讨论如何借鉴这些思想构建自己的开发工具。
:::tip 本章要点
- 提炼 Vite 中的七大可迁移设计模式
- 理解每种模式的动机、实现策略和适用边界
- 分析关键架构决策背后的权衡
- 掌握从 Vite 架构中可借鉴的工具构建方法论 :::
18.1 中间件栈模式
18.1.1 模式描述
Vite 的开发服务器使用 Connect 中间件栈处理 HTTP 请求。每个中间件是一个函数,按顺序执行,可以选择处理请求或将其传递给下一个中间件。
flowchart LR
A["HTTP 请求"] --> B["CORS 中间件"]
B --> C["Proxy 中间件"]
C --> D["Base 中间件"]
D --> E["HMR 中间件<br/>(WebSocket upgrade)"]
E --> F["Transform 中间件<br/>(JS/CSS 转换)"]
F --> G["静态文件中间件"]
G --> H["HTML 中间件"]
H --> I["404 处理"]
style F fill:#fff3e0
18.1.2 设计要点
中间件栈模式的核心优势在于:
关注点分离:每个中间件只负责一种类型的请求处理。CORS 中间件只管跨域头,Proxy 中间件只管代理转发,Transform 中间件只管代码转换。它们之间通过 next() 函数实现松耦合。
顺序敏感的处理管线:中间件的注册顺序决定了处理优先级。例如 Proxy 中间件必须在 Transform 中间件之前,否则代理目标的请求会被错误地当作本地模块处理。
短路退出:当某个中间件已经处理了请求(发送了响应),后续中间件不再执行。这避免了不必要的计算。
可组合性:中间件可以自由添加和移除。Vite 允许用户通过 server.middlewareMode 关闭内置中间件,或通过配置 Hook 添加自定义中间件。
18.1.3 适用场景
中间件栈模式适用于任何需要"多步骤、有序处理"的场景:
- HTTP 请求处理(最经典的应用)
- 日志管线(格式化 -> 过滤 -> 输出)
- 数据验证管线(类型检查 -> 范围检查 -> 业务规则检查)
- 图像处理管线(解码 -> 滤镜 -> 编码)
18.1.4 实现要点
// 简化的中间件栈实现
class MiddlewareStack<Context> {
private middlewares: Array<(ctx: Context, next: () => Promise<void>) => Promise<void>> = []
use(middleware: (ctx: Context, next: () => Promise<void>) => Promise<void>) {
this.middlewares.push(middleware)
}
async handle(ctx: Context) {
let index = 0
const next = async () => {
if (index < this.middlewares.length) {
await this.middlewares[index++](ctx, next)
}
}
await next()
}
}
18.2 基于图的失效传播
18.2.1 模式描述
Vite 的 HMR 系统建立在模块依赖图之上。当一个文件变更时,系统沿着依赖图的反向边(importers)传播失效信号,直到找到能够自我接受更新的模块(HMR boundary)或到达根节点(触发完整刷新)。
graph BT
A["变更文件: utils.ts"] -->|"importers"| B["Component.vue<br/>(import.meta.hot.accept)"]
A -->|"importers"| C["helper.ts"]
C -->|"importers"| D["App.vue<br/>(import.meta.hot.accept)"]
C -->|"importers"| E["index.ts<br/>(入口,无 HMR)"]
B --> F["HMR 更新:<br/>重新加载 Component.vue"]
D --> G["HMR 更新:<br/>重新加载 App.vue"]
E --> H["Full Reload"]
style A fill:#ffcdd2
style B fill:#e8f5e9
style D fill:#e8f5e9
style E fill:#fff3e0
18.2.2 设计要点
双向边:模块图中每个节点同时维护 importedModules(依赖了谁)和 importers(被谁依赖)。这种双向关系使得失效传播能够高效地进行,无需遍历整个图。
边界检测:传播在 HMR boundary(声明了 import.meta.hot.accept 的模块)处停止。这是一种"最小化影响范围"的策略 -- 只重新加载真正需要更新的模块子树。
时间戳去重:每个模块节点维护 lastHMRTimestamp,防止同一次文件变更触发重复的更新传播。
环隔离:在 Vite 6 的多环境架构中,失效传播只在单个环境的模块图内进行,不会跨环境。客户端的文件变更不会影响 SSR 环境的模块状态(除非显式配置)。
18.2.3 通用化抽象
这个模式可以推广为一种通用的"变更传播"机制:
graph TB
subgraph "通用失效传播模式"
A["变更源"] --> B["依赖图"]
B --> C["反向遍历 (importers)"]
C --> D{"是否到达边界?"}
D -->|"是"| E["触发边界处的更新操作"]
D -->|"否"| C
D -->|"到达根节点"| F["触发全量操作"]
end
subgraph "应用实例"
G["Vite HMR: 文件变更 -> 模块重新加载"]
H["React Fiber: state 变更 -> 组件重新渲染"]
I["构建系统: 源文件变更 -> 增量重编译"]
J["缓存系统: 数据变更 -> 缓存失效"]
end
适用于任何具有"依赖关系"且需要"增量更新"的系统:
- 构建系统(Make, Bazel):源文件变更触发依赖它的目标重新构建
- 响应式系统(Vue, MobX):数据变更触发依赖它的计算和视图更新
- 缓存失效(CDN, Redis):源数据变更触发依赖它的缓存键失效
18.2.4 实现要点
// 通用的图失效传播
interface GraphNode<T> {
id: string
data: T
dependencies: Set<string> // 我依赖谁
dependents: Set<string> // 谁依赖我
isBoundary: boolean // 是否为传播边界
}
function propagateInvalidation<T>(
graph: Map<string, GraphNode<T>>,
changedId: string,
): { boundaries: GraphNode<T>[]; needsFullRefresh: boolean } {
const boundaries: GraphNode<T>[] = []
const visited = new Set<string>()
const queue = [changedId]
while (queue.length > 0) {
const id = queue.shift()!
if (visited.has(id)) continue
visited.add(id)
const node = graph.get(id)
if (!node) continue
if (node.isBoundary && id !== changedId) {
boundaries.push(node)
continue // 不再向上传播
}
if (node.dependents.size === 0 && id !== changedId) {
return { boundaries: [], needsFullRefresh: true }
}
for (const depId of node.dependents) {
queue.push(depId)
}
}
return { boundaries, needsFullRefresh: false }
}
18.3 插件 Hook 管线
18.3.1 模式描述
Vite 的插件系统定义了一系列 Hook,每个 Hook 在构建管线的特定阶段被调用。多个插件可以为同一个 Hook 提供实现,它们按照插件注册顺序和 enforce 优先级依次执行。
flowchart LR
subgraph "Hook 管线示例: resolveId"
A["插件 A (enforce: 'pre')"] --> B["插件 B"]
B --> C["插件 C"]
C --> D["插件 D (enforce: 'post')"]
end
subgraph "执行策略"
E["first: 第一个非 null 结果胜出"]
F["sequential: 按序执行,结果串联"]
G["parallel: 并行执行"]
end
style A fill:#e8f5e9
style D fill:#fff3e0
18.3.2 Hook 分类
Vite/Rolldown 的 Hook 按照执行策略分为三类:
| 策略 | 行为 | 典型 Hook |
|---|---|---|
first | 第一个返回非 null 值的插件胜出 | resolveId, load |
sequential | 依次执行,前一个的输出作为后一个的输入 | transform, renderChunk |
parallel | 所有插件并行执行 | buildStart, buildEnd |
18.3.3 设计要点
解耦与组合:每个插件是独立的功能单元,通过 Hook 管线组合在一起。CSS 处理、TypeScript 编译、Vue SFC 解析可以各自作为独立的插件开发、测试和发布。
优先级控制:enforce: 'pre' | undefined | 'post' 提供三级优先级。内部插件通常使用 pre 或 post,用户插件默认在中间执行。这在用户需要在 Vite 内部插件之前或之后介入时非常有用。
环境过滤:applyToEnvironment Hook 允许插件声明自己适用于哪些环境,避免在不相关的环境中执行不必要的逻辑。
惰性注册:perEnvironmentPlugin 允许根据环境动态创建不同的插件实现,甚至通过返回 false 完全跳过某个环境。
18.3.4 适用场景
Hook 管线模式适用于:
- 编译器:词法分析 -> 语法分析 -> 语义分析 -> 代码生成,每个阶段可以有多个插件介入
- CI/CD 系统:构建 -> 测试 -> 打包 -> 部署,每个阶段可以有自定义步骤
- 编辑器:代码补全、错误检查、格式化各自作为插件,通过统一的 Hook 接口协作
graph TB
subgraph "通用 Hook 管线"
A["定义 Hook 类型和执行策略"]
B["注册多个处理器 (插件)"]
C["按策略和优先级执行"]
D["处理结果聚合"]
end
subgraph "Vite 实例"
E["resolveId (first): alias -> resolve -> external"]
F["transform (sequential): vue -> ts -> css"]
G["generateBundle (parallel): manifest + license + ssr-manifest"]
end
18.4 按需转换架构
18.4.1 模式描述
Vite 开发服务器的核心创新是"按需转换" -- 只有当浏览器请求某个模块时,才对其进行解析和转换。这与传统打包工具(Webpack dev server)的"全量预构建"模式形成鲜明对比。
sequenceDiagram
participant Browser as 浏览器
participant Server as Vite Dev Server
participant Transform as 转换管线
participant FS as 文件系统
Browser->>Server: GET /src/App.vue
Server->>Transform: transformRequest('/src/App.vue')
Note over Transform: 仅在此时才处理 App.vue
Transform->>FS: 读取文件
FS-->>Transform: 文件内容
Transform->>Transform: 解析 + 编译 + 转换
Transform-->>Server: 转换结果(带缓存)
Server-->>Browser: 200 OK (转换后的 JS)
Browser->>Server: GET /src/utils.ts
Note over Server: 只有 App.vue 中 import 的模块<br/>才会被请求和处理
18.4.2 设计要点
零前置开销:项目启动时只需要启动 HTTP 服务器和依赖预打包。源代码的解析和转换被推迟到实际请求时,使得大型项目的启动时间与项目规模解耦。
天然的死代码排除:未被任何入口引用的模块永远不会被请求和处理。在开发模式下,这相当于自动的 Tree Shaking(虽然机制不同)。
缓存友好:每个模块独立处理和缓存。文件变更只需要使直接变更的模块及其 HMR 传播路径上的模块失效,其他模块的缓存保持有效。
302 重定向协作:当浏览器请求的 URL 需要规范化(如裸模块标识符 lodash -> /node_modules/.vite/lodash.js)时,通过 302 重定向而非内部重写来处理。这使得浏览器缓存能正确工作。
18.4.3 权衡
按需转换也有其局限性:
- 首次加载慢:第一次请求需要"冷编译",对于依赖链较深的页面可能产生请求瀑布流
- HTTP/2 依赖:大量的小模块请求在 HTTP/1.1 下性能较差
- 预打包互补:对
node_modules中的依赖进行预打包(esbuild/rolldown),将数百个小文件合并为少数大文件,减少请求数
Vite 通过"依赖预打包 + 源码按需转换"的组合策略,平衡了这些权衡:
graph TB
subgraph "依赖 (node_modules)"
A["esbuild/rolldown 预打包"]
B["少量大文件"]
C["强缓存 (304 / 文件名 hash)"]
end
subgraph "源码 (src/)"
D["按需转换"]
E["大量小文件"]
F["协商缓存 (ETag + 304)"]
end
A --> B --> C
D --> E --> F
style A fill:#e3f2fd
style D fill:#e8f5e9
18.4.4 适用场景
按需转换模式适用于"工作集远小于全集"的场景:
- IDE 语言服务器:只分析当前打开的文件及其直接依赖
- 增量测试框架:只运行与变更文件相关的测试
- 虚拟滚动列表:只渲染视口内的条目
- 按需编译的 JIT 编译器:只编译实际执行到的函数
18.5 多环境隔离
18.5.1 模式描述
Vite 6 的 Environment API 实现了"同一个配置管理器,多个独立的执行环境"。每个环境拥有独立的模块图、插件容器和依赖优化器,通过共享的顶层配置保持协调。
graph TB
subgraph "共享层"
A["顶层配置 (ResolvedConfig)"]
B["文件系统监听器 (FSWatcher)"]
C["HTTP 服务器"]
end
subgraph "隔离层"
D["client 环境"]
E["ssr 环境"]
F["rsc 环境"]
end
A --> D
A --> E
A --> F
B --> D
B --> E
C --> D
C --> E
D --> D1["ModuleGraph"]
D --> D2["PluginContainer"]
D --> D3["DepsOptimizer"]
E --> E1["ModuleGraph"]
E --> E2["PluginContainer"]
E --> E3["DepsOptimizer"]
style A fill:#fff3e0
style D fill:#e3f2fd
style E fill:#e8f5e9
18.5.2 设计要点
Proxy 配置覆盖:环境配置通过 JavaScript Proxy 实现零拷贝的选择性覆盖。环境特定的属性直接返回,未覆盖的属性透明地回退到顶层配置。
类型层次保护:PartialEnvironment -> BaseEnvironment -> DevEnvironment/BuildEnvironment/ScanEnvironment 的继承层次确保每个环境类型只暴露其合理拥有的能力。UnknownEnvironment 迫使开发者使用正向模式检查。
WeakMap 状态管理:perEnvironmentState 使用 WeakMap<Environment, State> 管理跨 Hook 的环境级状态,自动处理垃圾回收。
环境感知的插件过滤:applyToEnvironment 和 perEnvironmentPlugin 允许插件根据环境特性动态调整行为或完全跳过。
18.5.3 适用场景
多环境隔离模式适用于:
- 跨平台应用框架:React Native 同时处理 iOS 和 Android 的不同编译配置
- 微前端系统:不同子应用可能需要不同的构建配置但共享基础设施
- 多租户系统:同一服务器进程为不同客户提供隔离的运行环境
- 测试框架:为不同测试环境(Node、Browser、JSDOM)提供不同的模块解析策略
18.6 缓存策略
18.6.1 多层次缓存体系
Vite 在多个层次上实现了缓存:
graph TB
subgraph "L1: 浏览器缓存"
A["依赖: 强缓存<br/>(Cache-Control: max-age=31536000)"]
B["源码: 协商缓存<br/>(ETag + 304 Not Modified)"]
end
subgraph "L2: 模块图缓存"
C["transformResult: 转换结果缓存"]
D["resolveId 缓存: 路径解析缓存"]
end
subgraph "L3: 依赖预打包缓存"
E["文件系统缓存<br/>(.vite/deps/)"]
F["hash 校验<br/>(lockfile + config hash)"]
end
subgraph "L4: 插件级缓存"
G["WorkerOutputCache: Worker 产物缓存"]
H["packageCache: package.json 缓存"]
I["perEnvironmentState: 环境状态缓存"]
end
18.6.2 缓存失效策略
不同层次的缓存使用不同的失效策略:
| 缓存层 | 失效触发 | 失效粒度 |
|---|---|---|
| 浏览器缓存 | URL 变更(文件名 hash) | 单个文件 |
| 转换结果缓存 | HMR 失效传播 | 单个模块及其上游 |
| 依赖预打包缓存 | lockfile 或配置变更 | 全部依赖 |
| Worker 产物缓存 | watchChange 检测 | 受影响的 Worker bundle |
| package.json 缓存 | 进程生命周期 | 无主动失效 |
18.6.3 设计原则
按变更频率分层:变更频率高的缓存使用快速失效策略(如 ETag 协商),变更频率低的缓存使用持久化策略(如文件系统缓存 + hash 校验)。
确定性优先:缓存键必须能确定性地反映内容。依赖预打包使用 lockfile + config 的 hash 作为缓存键,确保配置变更后缓存自动失效。
渐进式失效:HMR 的失效传播是渐进式的 -- 只失效变更文件的直接和间接依赖者,而非整个模块图。这在大型项目中带来了量级差异。
18.7 惰性初始化与可选依赖
18.7.1 模式描述
Vite 广泛使用惰性初始化来推迟开销较大的操作:
// Terser: 惰性创建 Worker 池
let worker: ReturnType<typeof makeWorker>
// ...
worker ||= makeWorker()
// Terser: 惰性加载可选依赖
let terserPath: string | undefined
function loadTerserPath(root: string) {
if (terserPath) return terserPath
const resolved = nodeResolveWithVite('terser', undefined, { root })
if (resolved) return (terserPath = resolved)
throw new Error('terser not found...')
}
18.7.2 设计要点
零成本抽象:如果某个功能不被使用,它不应该产生任何运行时开销。Terser 不被使用时,既不会被 import 也不会创建 Worker。
友好的错误提示:当惰性加载的依赖不存在时,提供清晰的错误信息,告诉用户需要安装什么以及为什么需要安装。
缓存求解结果:第一次惰性求值的结果被缓存(如 terserPath),后续调用直接返回缓存值。
18.7.3 WorkerWithFallback 模式
这是惰性初始化的一个高级变体 -- 不仅惰性初始化,还在初始化失败时提供降级策略:
graph TB
A["任务请求"] --> B{"Worker 是否已创建?"}
B -->|"否"| C["惰性创建 Worker 池"]
B -->|"是"| D{"参数可序列化?"}
C --> D
D -->|"是"| E["分发到 Worker 线程"]
D -->|"否"| F["主线程执行 (Fake Worker)"]
E --> G["返回结果"]
F --> G
这种"尽力并行,必要时降级"的策略在性能关键路径上非常实用。
18.8 声明式过滤
18.8.1 模式描述
Vite 的插件系统使用声明式过滤来优化 Hook 调用频率:
// 通过 filter 对象声明过滤条件
transform: {
filter: {
id: /\.vue$/, // 只处理 .vue 文件
code: 'import.meta', // 只处理含 import.meta 的代码
},
handler(code, id) {
// 只有同时满足两个条件时才会被调用
},
}
18.8.2 设计要点
减少不必要的调用:没有过滤器时,每个 transform 插件都会被每个模块调用。通过声明式过滤,Vite 可以在调用插件之前进行快速匹配,跳过不相关的模块。
Rolldown 优化:声明式过滤不仅在 JavaScript 层面减少调用,还允许 Rolldown 的 Rust 内核在更底层进行过滤,避免 JS/Rust 边界的序列化开销。
组合过滤:id 和 code 过滤器可以同时使用,构成 AND 逻辑。ID 过滤先于代码过滤执行(文件名检查比内容检查更快)。
18.8.3 适用场景
声明式过滤适用于任何"多处理器、单管线"的系统:
- 事件系统:监听器声明自己关心的事件类型,避免收到无关事件
- 消息队列:消费者声明自己的路由键,只接收匹配的消息
- 日志系统:处理器声明自己的日志级别和来源过滤条件
18.9 占位符与延迟替换
18.9.1 模式描述
Vite 在多个场景中使用占位符(placeholder)模式:在代码生成阶段插入占位符字符串,在后续阶段(如 renderChunk)用实际值替换。
flowchart LR
A["代码生成阶段"] -->|"WORKER_ASSET_PLACEHOLDER"| B["中间产物"]
B --> C["renderChunk 阶段"]
C -->|"./assets/worker-xyz.js"| D["最终产物"]
style A fill:#e3f2fd
style C fill:#fff3e0
Vite 中的占位符实例:
| 占位符 | 用途 | 替换阶段 |
|---|---|---|
__VITE_WORKER_ASSET__hash__ | Worker 文件 URL | renderChunk |
__VITE_PRELOAD__ | Module Preload 依赖列表 | renderChunk |
__VITE_IS_MODERN__ | 现代/遗留浏览器标志 | renderChunk |
__VITE_ASSET__hash__ | 静态资源 URL | renderChunk |
__VITE_WASM_INIT__hash__ | WASM 文件路径 | renderChunk |
18.9.2 设计要点
解耦生成与引用:在 load 或 transform 阶段,模块的最终文件名和路径尚不确定(取决于内容 hash)。占位符允许在"信息尚不完整"的阶段生成代码,将实际值的确定推迟到"信息完整"的阶段。
确定性替换:占位符使用唯一的 hash 或 ID,确保替换的准确性。正则表达式 /__VITE_WORKER_ASSET__([a-z\d]{8})__/g 精确匹配,不会误伤用户代码。
多输出适配:同一个占位符在不同的输出格式中可能被替换为不同的值。例如,资源路径在 ESM 中可能使用 import.meta.url 相对路径,在 CJS 中可能使用 __dirname 相对路径。
18.10 构建你自己的开发工具
18.10.1 核心架构清单
基于对 Vite 架构的深入分析,构建开发工具时应考虑以下架构决策:
graph TB
A["开发工具架构决策"] --> B["请求处理模型"]
A --> C["扩展机制"]
A --> D["缓存体系"]
A --> E["增量更新"]
A --> F["多目标支持"]
B --> B1["中间件栈 vs 路由表 vs 事件驱动"]
C --> C1["插件 Hook 管线 vs 接口继承 vs Mixin"]
D --> D1["多层缓存 + 分层失效"]
E --> E1["依赖图 + 边界检测 + 最小化传播"]
F --> F1["环境隔离 + Proxy 配置 + 类型层次"]
18.10.2 模式选择矩阵
| 场景 | 推荐模式 | Vite 中的实例 |
|---|---|---|
| 多步骤有序处理 | 中间件栈 | Connect 中间件链 |
| 多方扩展同一流程 | 插件 Hook 管线 | Rolldown 插件系统 |
| 增量更新传播 | 基于图的失效传播 | HMR 模块图 |
| 减少不必要计算 | 按需转换 + 惰性初始化 | Dev Server 转换 |
| 多目标适配 | 多环境隔离 | Environment API |
| 跨阶段引用 | 占位符延迟替换 | Worker URL 占位符 |
| 性能优化 | 多层缓存 | 浏览器/模块图/FS 缓存 |
| 减少调用开销 | 声明式过滤 | transform filter |
18.10.3 关键权衡
灵活性 vs 性能:插件 Hook 管线提供了极大的灵活性,但每个 Hook 调用都有额外开销。声明式过滤和 applyToEnvironment 是缓解这一矛盾的手段。
隔离性 vs 共享效率:多环境隔离确保了正确性,但独立的模块图和插件容器意味着更多的内存使用。sharedConfigBuild 和 sharedPlugins 选项允许在两者之间灵活权衡。
按需 vs 预计算:按需转换减少了启动时间,但可能增加首次请求的延迟。依赖预打包是一种"提前预计算高频访问数据"的策略,弥补了按需模式的首次加载劣势。
简单性 vs 正确性:ssrTransform 中的 (0, expr) this 解绑、Object.seal exports 密封、循环依赖的三级检测,这些"过度设计"的细节保证了极端情况下的正确性,但增加了代码复杂度。在工具软件中,正确性通常优先于简单性。
18.10.4 从 Vite 学到的架构原则
-
ESM 优先:拥抱标准而非发明轮子。Vite 的核心创新在于将浏览器原生的 ESM 能力变成开发体验的优势。
-
选择性介入:只在需要控制的地方自己实现,其他地方交给原生机制。依赖预打包而非完全打包,外部化
node_modules而非全量转换。 -
渐进式优化:从最简单的实现开始,在性能瓶颈处针对性优化。按需转换是最简单的模型,缓存和预打包是在此基础上的优化。
-
分层抽象:配置层(PartialEnvironment)-> 能力层(BaseEnvironment)-> 实现层(DevEnvironment)。每一层增加一组能力,不跨层越权。
-
确定性输出:相同的输入必须产生相同的输出。
sortObjectKeys排序、内容 hash 文件名、lockfile 校验,这些细节确保了构建的可复现性。 -
错误友好:当事情出错时,提供准确的文件位置、代码帧、修复建议。
generateCodeFrame在多个插件中被复用,提供一致的诊断体验。
18.11 小结
本章从 Vite 源码中提炼了七大可迁移的设计模式,每一种都有明确的适用场景和实现要点:
-
中间件栈模式通过有序的处理链实现关注点分离和请求路由,适用于任何多步骤有序处理场景。
-
基于图的失效传播通过依赖关系的反向遍历和边界检测实现最小化影响范围的增量更新,是 HMR 系统的理论基础,可推广到任何具有依赖关系的增量更新场景。
-
插件 Hook 管线通过定义标准化的扩展点和执行策略,实现了"多方协作、松耦合、可组合"的扩展机制。
first/sequential/parallel三种执行策略覆盖了绝大多数插件协作需求。 -
按需转换架构将"全量预计算"转变为"请求驱动的惰性计算",在工作集远小于全集的场景中带来量级提升。依赖预打包作为互补策略弥补了冷启动的不足。
-
多环境隔离通过类型层次、Proxy 配置覆盖和 WeakMap 状态管理,在共享基础设施的同时保持环境间的独立性。
-
多层缓存体系在浏览器、模块图、文件系统和插件四个层次上实现缓存,通过分层失效策略确保一致性。
-
占位符延迟替换解耦了代码生成和引用解析的时序依赖,使得在信息不完整的阶段也能生成可用的中间产物。
这些模式不是 Vite 独创的 -- 它们是软件工程中久经验证的设计智慧在构建工具领域的精彩应用。理解这些模式的动机和权衡,将帮助你在构建自己的开发工具时做出更明智的架构决策。