工作原理
Vite 是面向现代浏览器的前端构建工具:开发阶段利用原生 ES 模块与按需编译;生产构建由 Rollup 完成。对比见 Webpack 与 Vite、工程化概览。
下面从开发服务器、预构建、插件与 HMR 四方面说明核心机制。
一、开发服务器原生 ESM 架构
传统的 Webpack 等打包工具在开发模式下需要构建整个应用的依赖图,启动慢、热更新慢。Vite 将开发模式下的模块处理交给了现代浏览器。
1. 利用浏览器原生 ES 模块
Vite 启动开发服务器后直接提供源码。浏览器通过 <script type="module"> 加载入口,遇到 import 再请求对应模块,由 Vite 拦截并返回编译后的结果。
- 无需将整个应用打包成一个 bundle,浏览器自行管理模块加载。
- 开发服务器只处理当前请求的模块,极大的冷启动速度。
2. 按需编译:只编译请求的模块
当浏览器请求某个 .vue、.jsx、.ts 文件时,Vite 才会对该文件进行实时编译(转换 TS、编译 JSX、解析 Vue SFC 等)。这意味着项目中有大量文件时,不会在启动时全部编译,只编译请求的模块,启动时间与项目规模解耦。
3. 依赖预构建(Pre-bundling)的作用
虽然 Vite 提倡使用原生 ESM,但 node_modules 中的依赖通常有两种问题:
- 多模块依赖:许多 CommonJS 或 UMD 库无法直接在浏览器中使用。
- 大量内部模块:一个依赖可能包含成百上千个模块(如
lodash-es),直接引入会导致请求瀑布。
预构建(pre-bundling)使用 esbuild 将依赖统一打包成单个或少量 ESM 模块,然后提供给浏览器。好处:
- 兼容性:将 CommonJS/UMD 转换成 ESM。
- 性能:将多个内部模块合并,减少请求数量。
- 缓存:预构建产物写入
node_modules/.vite/deps,下次启动直接使用。
二、预构建机制详解
1. 预构建触发条件
- 首次启动:自动扫描
index.html中的<script type="module">以及入口中的optimizeDeps.entries配置,收集依赖并预构建。 - 手动强制:使用
server.force配置或vite --force命令。 - 依赖变更:当
package.json中的依赖版本变化或锁定文件变化,Vite 会重新预构建(通过_metadata.json的哈希判断)。 - 未预构建的依赖被导入:如果某些依赖未被预构建,Vite 也会在运行时触发预构建。
2. _metadata.json 缓存机制
node_modules/.vite/deps/_metadata.json 文件记录了预构建的依赖列表、文件哈希、配置哈希等信息。Vite 在启动时对比当前依赖和缓存的哈希,若一致则直接使用缓存,否则重新预构建。
3. 预构建产物存放位置与手动清除
产物存放在 node_modules/.vite/deps/ 目录下,文件名包含内容哈希。如果需要强制重新构建,可以删除该目录或执行 vite --force。
4. 自定义 optimizeDeps.include / exclude
某些第三方库可能因为动态导入或特殊的 CommonJS 导出导致预构建失败,可以通过配置强制包含或排除:
// vite.config.js
export default {
optimizeDeps: {
include: ["some-package"], // 强制预构建
exclude: ["@vue/runtime-core"], // 排除,让浏览器直接加载原始 ESM
},
};
三、插件架构与 hooks 执行顺序
Vite 的插件体系兼容 Rollup 插件,并扩展了一些 Vite 特有的钩子。插件生命周期主要分为三个阶段:解析 → 加载 → 转换 → HTML 转换 → 生成 / 写入。
1. 插件关键 hooks
| 钩子 | 作用 | 执行时机 |
|---|---|---|
resolveId | 解析模块 ID(如 import 'vue' 转为真实路径) | 每个模块导入语句 |
load | 加载模块内容(返回源码字符串) | 在 resolve 之后,transform 之前 |
transform | 转换加载后的模块内容(编译 TS、JSX 等) | 每个模块加载后 |
transformIndexHtml | 转换 index.html 内容 | 开发服务器请求 HTML 时或构建打包时 |
generateBundle | 在 bundle 生成后、写入前修改产物 | 构建阶段,Rollup 特有 |
writeBundle | bundle 写入后执行 | 构建阶段 |
2. 插件执行顺序
通过 enforce 属性控制:
enforce: 'pre'→ 在 Vite 核心钩子之前执行(如alias)。- 无
enforce→ 正常顺序。 enforce: 'post'→ 在 Vite 核心钩子之后执行(如压缩、校验)。
同一组内按插件注册顺序执行。
// 示例插件结构
const myPlugin = {
name: 'my-plugin',
enforce: 'pre', // 可选 'pre'、'post'
resolveId(source) { ... },
load(id) { ... },
transform(code, id) { ... },
};
3. Vite 插件与 Rollup 插件的兼容性
绝大多数 Rollup 插件可以在 Vite 中直接使用,因为 Vite 的生产构建就是基于 Rollup。但需要注意:
- 某些 Rollup 插件假设在打包阶段(如
generateBundle),在开发阶段可能不会调用。 - Vite 开发环境会调用
resolveId、load、transform等钩子,因此耗时插件可能影响开发性能。 - 可以通过
apply: 'build' | 'serve'限定插件只在特定模式生效。
四、HMR(热模块替换)原理
Vite 的 HMR 基于原生 ES 模块,利用 WebSocket 实现增量更新,更新速度快且精确。
1. 基于 WebSocket 的模块更新通知
- 开发服务器启动后,会建立 WebSocket 连接。
- 当文件被编辑时,Vite 会判断受影响的模块链,并向客户端发送更新消息(包含模块 ID 及更新类型)。
- 客户端接收消息后,重新请求对应的模块,并在运行时处理替换逻辑。
2. 精确的 HMR 边界:import.meta.hot API
Vite 暴露了 import.meta.hot 对象,允许框架和模块定义自定义热更新行为:
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// 模块更新后执行,可以重新渲染
});
import.meta.hot.dispose(() => {
// 模块废弃前的清理工作
});
}
对于无 HMR 适配的模块,Vite 会通过 fallback 进行页面重载。
3. 与框架(Vue/React)的深度集成
@vitejs/plugin-vue:针对 Vue 单文件组件实现了精细的 HMR。当修改<template>或<style>时,只更新对应部分,保留组件状态;修改<script>时,会触发组件重新加载。@vitejs/plugin-react:利用 React Refresh(官方 HMR 方案),保持组件状态。它会在编译时将组件转换为支持热更新的代码,并注入import.meta.hot逻辑。
这些框架插件通过分析文件变更范围,将更新粒度缩小到组件级别,从而保留状态。
五、总结
| 核心机制 | 关键点 | 收益 |
|---|---|---|
| 原生 ESM 开发服务器 | 浏览器直接加载模块,按需编译 | 冷启动极快,无打包开销 |
| 依赖预构建 | esbuild 打包依赖,合并模块、转 ESM | 减少请求,兼容 CommonJS |
| 插件体系 | 兼容 Rollup 钩子 + enforce 顺序 | 强扩展性,复用 Rollup 生态 |
| HMR | WebSocket 推送 + import.meta.hot API | 模块级热更新,保有状态 |
Vite 通过充分利用浏览器原生能力、智能预构建和精细化的热更新,为现代前端开发提供了极致的开发体验。在生产环境下,它通过 Rollup 打包输出优化后的产物,兼顾运行性能。
参考文献
以下链接在编写时均可正常访问:
相关文章
部署实践
Vite 项目的 CI/CD、多环境、CDN、Legacy 与体积预算等工程化要点。环境变量见 env;构建见 生产构建。
插件开发
Vite 插件兼容 Rollup 插件,并扩展 config、configureServer、transformIndexHtml 等钩子。原理见 工作原理。
进阶配置
Vite 进阶:server.proxy、resolve.alias、SSR、多页面、库模式、vite preview 等。环境变量见 env。
生产构建
vite build 使用 Rollup 打包并应用内置优化。开发阶段原理见 工作原理。
create-vite
pnpm create vite 实际执行的是 npm 包 create-vite(与 vite 本体不同)。工程化背景见 工程化概览、工作原理。
工作原理
Webpack 是静态模块打包器:从入口递归解析依赖,经 Loader 转换后输出一个或多个 bundle。与 Vite 对比见 Webpack 与 Vite。
Series
vite
6 / 6