一、核心思想:为什么 Vite 快?
Vite 快的根本原因是用原生 ES Modules 替代了 Bundle-first 的思路。传统工具(Webpack)启动时要把整个项目打包成一个 bundle 才能运行;Vite 开发期间根本不打包,浏览器直接按需请求模块。---
二、Vite 开发服务器底层原理
Vite Dev Server 的核心由三个子系统组成,协同工作:三大子系统说明:
① 依赖预构建(Dep Pre-bundling):用 esbuild(Go 写的,比 Babel 快 10-100x)将 node_modules 里的 CJS/UMD 包转成 ESM,并把有大量内部模块的包(如 lodash-es)合并成单文件,避免浏览器发出数百个请求。结果缓存在 node_modules/.vite/deps/。
② 插件转换管道:完全复用 Rollup 的插件接口,每个请求进来后按 resolveId → load → transform 顺序流转,官方插件 @vitejs/plugin-vue 就挂在这里把 .vue SFC 拆成 <script>、<template>、<style> 三块分别处理。
③ HMR 引擎:chokidar 监听文件系统变化 → 查找模块依赖图找到受影响的模块边界 → 通过 WebSocket 推送 update 事件 → 浏览器端运行时拉取新模块、执行 HMR API 回调,整个过程不刷页面。
三、一次 HTTP 请求的完整生命周期
浏览器加载 main.ts 之后,每遇到一个 import 就向 Vite Dev Server 发请求,下面是单个请求的处理链路:关键细节:
- 裸模块重写:浏览器不认识
import vue from 'vue',Vite 把它改成import vue from '/@modules/vue.js'再返回给浏览器。 .vueSFC 处理:一个App.vue文件实际上会被拆成多次请求:App.vue(script block)、App.vue?type=template(模板编译成 render 函数)、App.vue?type=style&index=0(样式注入),这些都是按需懒编译的。- 协商缓存:Vite 对每个模块加
etag,文件不变则 304,避免重复传输。
四、HMR 热更新的精确流程
HMR 是 Vite 最核心的体验,原理并不是"整页刷新",而是外科手术式地替换变更模块:HMR 边界(Boundary)是关键概念:Vite 从变更文件开始,沿模块依赖图向上遍历 importers,找到第一个调用了 import.meta.hot.accept() 的模块——这就是更新边界。@vitejs/plugin-vue 为每个 .vue 文件自动注入这段代码,所以修改一个 Vue 组件,只有该组件的模块被替换,父组件、路由、全局 store 都不受影响。
五、生产构建:Rollup 打包流程
开发用 ESM 按需加载,生产必须打包——Vite 用 Rollup 做生产构建(不用 esbuild,因为 Rollup 的 tree-shaking、代码分割、chunk 策略更成熟):构建关键机制:
- Tree Shaking:Rollup 基于 ES Module 静态导入分析,凡是没有被引用的 export 都会被丢弃。注意
sideEffects: false要在package.json中正确配置,否则带副作用的模块不会被摇掉。 - Chunk 分割策略:默认把动态
import()分割成独立 chunk,vendor中的第三方库单独打包(利于长效缓存)。可通过build.rollupOptions.output.manualChunks自定义分组。 - 资源指纹:所有产物文件名带内容 hash,如
index.a1b2c3d4.js,浏览器可设置极长Cache-Control,内容变了 hash 变了,缓存自动失效。 - CSS 处理:开发时 CSS 被转成 JS 模块注入;生产时通过
vite:css-post插件提取成独立的.css文件,避免 FOUC(无样式闪烁)。
六、开发 vs 生产的核心差异对比
| 维度 | 开发模式(Dev) | 生产模式(Build) |
|---|---|---|
| 模块处理 | 浏览器原生 ESM,按需加载 | Rollup 全量打包 |
| 编译器 | esbuild(极快,不压缩) | Rollup + esbuild/Terser(压缩优化) |
| Tree Shaking | 不做 | Rollup 静态分析 |
| 代码分割 | 不做 | 动态 import / manualChunks |
| Source Map | 内联,便于调试 | 可选独立 .map 文件 |
| CSS 处理 | JS 注入,支持 HMR | 提取独立 CSS 文件 |
| 启动速度 | 毫秒级(只预构建 deps) | 秒到分钟(视项目大小) |
七、插件系统与扩展点
Vite 的插件格式是 Rollup 插件的超集,新增了几个 Vite 独有的钩子:
export default function myPlugin(): Plugin {
return {
name: 'my-plugin',
// ① Rollup 通用钩子
resolveId(id, importer) { /* 自定义模块解析 */ },
load(id) { /* 自定义模块内容加载 */ },
transform(code, id) { /* 代码转换 */ },
// ② Vite 独有钩子(Dev Server)
configureServer(server) {
// 往 Connect 实例添加自定义中间件
server.middlewares.use((req, res, next) => { ... })
},
handleHotUpdate(ctx) {
// 自定义 HMR 行为,可过滤或追加受影响模块
return ctx.modules.filter(m => !m.id.includes('ignore'))
},
// ③ 构建钩子
generateBundle(options, bundle) {
// 操作最终产物,如注入 license 注释
}
}
}
enforce: 'pre' / enforce: 'post' 控制插件在管道中的执行顺序;apply: 'serve' / apply: 'build' 控制插件仅在开发或生产时生效。
总结
Vite 的设计哲学是把正确的工具用在正确的场景:
- 开发期:信任浏览器原生 ESM,用 esbuild 做极速编译,不打包、按需转换,换来毫秒级 HMR 体验。
- 生产期:回归 Rollup 做严肃的代码优化,tree-shaking + chunk 分割 + 资源 hash,产物质量与 Webpack 持平甚至更优。
这两套路径都基于同一套插件系统,让你写一个插件、同时覆盖两个阶段——这是 Vite 区别于其他工具的最精妙设计。