用惯了 Webpack 的人第一次跑起 Vite,最大的感受就是"快得不像话": 一个大型项目冷启动只要一两秒,改一行代码热更新几乎无感。 这种快不是简单的优化,而是构建工具范式的转变。

Webpack 的打包思维

Webpack 的核心思路是先打包,再运行: 启动 dev server 时,它会从入口文件出发,沿着 import 依赖图把整个项目的所有模块 (成千上万个文件)全部解析、转换、打包成 bundle,浏览器才能真正开始加载页面。

# 项目越大,Webpack 启动越慢的原因
入口文件 → 递归解析所有 import → 全部转换打包 → 浏览器才能加载
# 依赖图规模 = O(整个项目),与"你当前在改哪个文件"无关

所以 Webpack 冷启动慢是结构性的:无论你只改了 1 个文件,启动时它都要处理整个项目。 项目从 100 个模块增长到 10000 个模块,启动时间跟着线性增长。

Vite 的两个关键转变

1. 开发环境:原生 ESM + 按需编译

现代浏览器已经原生支持 ES Module(<script type="module">)。 Vite 在开发环境不做打包,而是把项目里每个源文件当作独立的 ESM 模块, 直接交给浏览器加载:

// 浏览器直接请求按路径拆分的模块
<script type="module" src="/src/main.ts"></script>

# 浏览器请求 → Vite 只转换这一个文件 → 返回
GET /src/main.ts      → 转换后直接返回
GET /src/App.vue      → 按需编译 SFC
GET /node_modules/... → 命中预构建缓存

启动时 Vite 只启动了一个轻量的 dev server,不扫描、不打包任何源文件; 浏览器请求到哪个模块,才转换哪个模块。这就是"按需编译"——启动时间与项目规模基本解耦, 无论项目多大,冷启动都是秒级。

2. 依赖预构建:用 esbuild 搞定 node_modules

node_modules 里的第三方依赖数量庞大且是 CommonJS 格式,逐个发给浏览器不现实。 Vite 的做法是用 Go 写的 esbuild 在启动时把依赖预打包成 ESM: esbuild 比基于 JS 的打包器快 10–100 倍,所以预构建阶段也几乎无感。

HMR 为什么也更快

Webpack 的 HMR 在修改文件后,需要重新打包受影响的部分再推送;模块多了链路就长。 Vite 的 HMR 是基于 ESM 的精确失效:只对修改的文件做一次转换, 浏览器端通过 import 链精确替换那个模块,其他模块完全不动。 改一个小组件,更新范围就真的是那一个组件。

生产构建呢?

开发环境追求速度用"不打包",但生产环境仍然需要打包—— 浏览器对几百个小请求的加载效率远低于一个大文件。Vite 生产构建使用 Rollup(可换成 esbuild),做 tree-shaking、代码分割、压缩等优化。 所以"Vite 快"主要指开发体验,生产构建与其他工具相比各有千秋, 选型时不必神话它。

什么时候还需要 Webpack?

  • 需要深度定制的复杂 loader / plugin 生态,且没有 Vite 对应方案;
  • 需要支持 IE 等老浏览器(原生 ESM 在开发环境下无法降级);
  • 存量项目迁移成本高于收益。

对新项目而言,Vite 已是社区的主流默认选择,这也是构建工具进化的自然结果。

总结:Webpack 是"打包完再跑",Vite 是"跑起来再按需编译"。前者启动时间随项目线性增长,后者把启动成本降到了常数级——这就是 Vite 快的本质。