Vite
Vite 的目的
Vite 是一种面向现代前端开发的构建工具,它的核心目标不是“先把整个项目打包好再启动开发服务”,而是尽可能利用浏览器原生的 ES Module 能力,在开发阶段按需提供源码,从而显著缩短冷启动时间和热更新反馈时间。和 webpack 这类传统打包器相比,Vite 更强调开发体验,生产环境则通过 Rollup 完成高质量打包。
它解决的核心问题可以概括为两类:
- 开发阶段启动慢、改动反馈慢。
- 项目变大后,频繁完整打包带来的等待成本高。
Vite 的开发流程
Vite 在开发环境下采用“按需编译、即时响应”的方式工作。启动时,它会先启动一个本地开发服务器,但不会像 webpack 那样先把整个应用打成一个巨大的 bundle,而是把源码文件按需返回给浏览器。
1. 启动开发服务器
Vite 启动后会先读取 vite.config.js,合并命令行参数和配置项,然后创建开发服务器。此时不会进行完整打包,而是准备好文件读取、插件管线和依赖预构建流程。
2. 入口 HTML 与模块脚本
使用 Vite 创建项目时,入口通常会写在 index.html 里,并通过下面这种方式引用脚本:
<script type="module" src="/src/main.js"></script>这里的 type="module" 是Vite构建项目时写入的。Vite 会把 index.html 当作应用入口来处理,并在开发和构建过程中识别这种模块脚本写法。
type="module" 的作用是告诉浏览器:这个脚本要按照 ES Module 的规则去加载和执行。这样浏览器才能继续解析 import / export,并按依赖关系递归请求后续模块。
3. 浏览器按需请求源码
开发模式下,浏览器通过原生 import 加载模块。Vite 会拦截这些请求,对源文件做必要的转换,比如:
- 将 TypeScript、JSX、Vue、React 等代码转换为浏览器可执行的 JavaScript。
- 处理 CSS、图片、字体等静态资源。
- 注入开发所需的辅助逻辑。
4. 依赖预构建
对 node_modules 里的第三方依赖,Vite 会先使用 esbuild 做预构建和转换,减少大量小文件请求,并把 CommonJS / UMD 依赖尽量转成浏览器更容易处理的形式。
这个步骤的目的主要有两个:
- 提升浏览器首次打开页面时的模块加载效率。
- 降低浏览器在开发阶段处理大量依赖文件时的开销。
这一步的实现大致如下:
- Vite 启动开发服务器后,会先扫描项目中的入口文件和静态导入,找出需要预处理的裸模块依赖(非相对路径)。
- 对这些依赖,Vite 调用 esbuild 进行快速打包和格式转换,把多个分散的小模块合并成少量可直接被浏览器加载的预构建产物。
- 预构建结果会写入本地缓存目录,常见位置是
node_modules/.vite,下次启动时可以直接复用,避免重复构建。 - 如果依赖内容、配置或锁文件发生变化,Vite 会根据 hash 重新触发预构建,保证缓存和实际依赖一致。
所以这里并不是把整个应用打包一遍,而是只把第三方依赖先“整理好”,让浏览器在开发阶段能够更快、更稳定地加载这些模块。
5. 插件与转换
Vite 的插件体系基于 Rollup 的插件接口扩展而来,开发阶段同样可以在请求模块时介入转换过程。常见用途包括:
- 处理框架语法,例如 Vue SFC、React JSX。
- 注入环境变量。
- 修改 HTML、静态资源和构建产物。
6. 生产环境构建
当执行构建命令时,Vite 会切换到生产模式,使用 Rollup 对模块进行打包、代码分割和资源优化,最终输出可部署的静态资源。
Vite 中的 esbuild 和 Rollup
Vite 之所以启动快、构建体验好,核心就在于它把不同阶段的工作交给了最适合的工具:开发阶段更多依赖 esbuild,生产构建主要依赖 Rollup。
esbuild
esbuild 是一个基于 Go 实现的超快打包与转换工具,Vite 主要把它用在开发阶段和部分预处理场景中。它的特点是速度很快,适合做高频、轻量的转换工作。
在 Vite 里,esbuild 主要负责:
- 依赖预构建,把
node_modules中的依赖提前处理成更适合浏览器加载的形式。 - 转换部分源码语法,比如 TypeScript、JSX 等。
- 在开发阶段承担一些快速转译工作,减少冷启动时间。
esbuild 的优势是快,但它并不是 Vite 生产构建的主力打包器,所以在复杂的插件生态、细粒度打包控制方面不如 Rollup 灵活。
Rollup
Rollup 是一个更偏向“打包成品”的工具,Vite 在生产环境构建时会调用它来完成真正的模块打包。它擅长把 ES Module 进行静态分析,进而完成代码分割、Tree Shaking 和最终资源输出。
在 Vite 里,Rollup 主要负责:
- 生产环境打包。
- 代码分割和 chunk 生成。
- Tree Shaking 和静态分析。
- 通过插件体系处理构建阶段的资源输出。
可以简单理解为:esbuild 负责“快”,Rollup 负责“完整且灵活”。Vite 把开发和构建拆开处理,正是为了同时兼顾开发速度和最终产物质量。
Rollup 和 webpack打包进行对比
| 特性 | Rollup | webpack |
|---|---|---|
| 定位与核心目标 | 专注于 ES Module 的打包器,致力于生成小巧干净的代码。 | 处理所有的前端资源的极客级别、功能强大的模块打包器。 |
| 适用场景 | 类库 / 工具库开发(例如 Vue、React 等底层源码和组件库打包)。 | 复杂的单页应用 / 多页业务应用 (SPA/MPA) 开发。 |
| 代码输出质量 | 输出代码非常纯粹、极致扁平化(作用域提升 Hoisting),不注入多余的运行时代码。 | 会注入较多自定义的打包与模块依赖加载代码 (runtime / manifest),产物略显臃肿。 |
| Tree Shaking | 原生具备优秀的基于 ESM 的静态分析,Tree Shaking 效果好且彻底。 | 同样支持 Tree Shaking,但在处理复杂或混用的 CommonJS/ESM 模块时可能略逊一筹。 |
| 资源加载与处理 | 侧重于 JS/TS 的打包,处理 CSS、图片等非代码资源需依赖各式插件,不够开箱即用。 | 一切皆模块,借助其强大的 Loader 机制,CSS、图片、字体文件等万物皆可直接 import 处理。 |
| 代码分割与动态加载 | 支持代码分割功能,但体系较为基础,体验不够webpack完善。 | 提供原生且成熟度极高的动态分包 (Code Splitting) 和按需加载能力。 |
| 开发体验与 HMR | 传统意义上作为纯构建工具,原生并没有很强的 DevServer 以及热模块替换(HMR)体系。 | 拥有极其强大、成熟的本地开发服务器 (webpack-dev-server) 以及完善的热更新(HMR)体系。 |
Vite 的热重载过程
Vite 的热更新依赖浏览器端的原生模块边界和 WebSocket 通信。相比 webpack 的 HMR,Vite 在开发阶段通常更轻量,更新路径也更短。
- 当源文件发生变化时,Vite 监听到文件改动。
- 服务端根据变更模块重新转换对应文件,并判断它是否能被 HMR 接受。
- Vite 通过 WebSocket 向浏览器发送更新消息,通知哪些模块发生了变化。
- 浏览器端根据消息请求新的模块代码,并尝试局部替换。
- 如果某个模块无法热替换,Vite 会退化为整页刷新。
Vite 的 HMR 之所以快,关键在于它通常只处理受影响的模块,而不是对整个应用重新打包。
如何使用 Vite 来优化前端性能
依赖预构建
Vite 会在开发阶段使用 esbuild 预构建依赖,这能显著减少重复解析和请求数量。对大型项目来说,这一步往往能明显改善启动速度。
按需加载
和 webpack 一样,Vite 也支持通过动态导入实现代码分割。
const SomeComponent = () => import('./SomeComponent.vue');对于路由级别、功能级别的模块,按需加载可以降低首屏负担,避免一次性加载过多代码。
静态资源优化
Vite 会对图片、字体、媒体文件等静态资源做统一处理。体积较小的资源可能被内联,大资源则以单独文件形式输出,便于缓存和按需加载。
构建压缩
在生产构建中,Vite 通过 Rollup 完成打包,并配合压缩插件对 JavaScript、CSS 进行压缩,以减小最终产物体积。
代码分割
Vite 支持基于动态导入和 Rollup 配置的代码分割。你可以把不常用的页面、组件、业务模块拆成独立 chunk,减少首屏阻塞。
预加载与预取
Vite 在生产构建中会为动态导入生成相应的模块预加载逻辑,帮助浏览器提前准备后续需要的模块。对于更细粒度的预取策略,通常需要结合路由方案或第三方插件实现。
Vite 和 webpack 的区别
- webpack 更偏向“先打包再服务”,Vite 更偏向“按需服务、按需编译”。
- webpack 开发阶段依赖较重,Vite 开发阶段借助原生 ESM 和 esbuild 预构建,启动通常更快。
- webpack 的 HMR 体系更传统,Vite 的 HMR 更接近直接更新单个模块。
- webpack 的构建核心更统一,Vite 的开发和生产分别由不同机制承担,开发用原生 ESM 和插件转换,生产用 Rollup 打包。