面试官:“你说vite快,那vite为什么快?”😧😧😧

201 阅读8分钟

前言

鼠鼠这几天在完成开源脚手架对Vite的适配,但是对vite的印象只停留在八股文背的只停留在表面的答案,面试官经常问到vite为什么快的问题,也只是背死ai给的答案经不起一点拷打。所以只是为了完成一个小需求,我们就顺便好好的学一下Vite为什么快!!

为什么我们需要Vite

当我们构建应用时,当项目的规模越来越大时,所需要处理的JS代码量也是指数级增长,包含数千个模块的情况也是很常见的,所以基于JS开发的工具时一定会出现性能瓶颈的:通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。

所以Vite使用一些机制,能够更快的提升我们构建速度。

Vite为什么快呢?

开发阶段

当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。

Vite 通过在一开始将应用中的模块区分为 依赖源码 两类,改进了开发服务器启动时间。

依赖 大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS)。

Vite 将会使用 esbuild 预构建依赖。esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。

什么是预购建依赖?

-依赖预构建 : 在开发模式启动时,使用 esbuild 把你项目中的第三方依赖(如 react, lodash, vue 等)提前打包成浏览器可理解的 原生ES Module(ESM) 格式,并缓存下来。

为什么需要预购建依赖

1.大部分第三方包都不是使用ESM写的,而是CommonJs写的。而浏览器无法执行这些文件

2.如果不预构建依赖,每次启动都要从头解析+转换依赖,非常慢,使用预构建依赖,转换一次成ESM后就缓存到node_modules/vite里,下次在启动直接使用缓存的文件就行

3.浏览器对vite生成的依赖文件可以进行缓存(带hash)只要依赖没变,浏览器就不会重复请求,热更新速度快

4.统一依赖格式,避免奇怪的导入错误

对于预购建依赖,还有几点补充:

现在很多的npm包(尤其是现代ESM包)都并非是打包成为一个文件,而是拆成很多小的模块,比如说:

import { debounce } from 'lodash-es';

在 lodash-es 中,debounce 只是其中一个模块,但它可能还依赖其它 10+ 个模块,每个模块又再导入别的模块……

如果不预构建,就会产生很糟糕的问题:

lodash-es 是一个按模块分拆的包,比如:

  • lodash-es/debounce.js 会导入:
    • lodash-es/isObject.js
    • lodash-es/now.js
    • lodash-es/root.js
    • ... 以此类推

这些模块彼此嵌套导入,最终浏览器要发出几百个 HTTP 请求

在浏览器中你可能会看到如下内容:

/node_modules/lodash-es/debounce.js
/node_modules/lodash-es/isObject.js
/node_modules/lodash-es/now.js
...

共计600+的请求

但是有Vite和esbuild预购建之后会发生什么呢?

Vite 会在你第一次 npm run dev 时:

  • 自动用 esbuild 把 lodash-es 的所有模块打包成一个文件
  • 缓存在 .vite 文件夹中 生成后的 lodash-es 看起来像这样(模拟):
// .vite/deps/lodash-es.js
function debounce(...) { ... }
function isObject(...) { ... }
function now(...) { ... }
...
export { debounce, isObject, now };

这样,浏览器就只会发出一个请求:

/node_modules/.vite/deps/lodash-es.js   ✅ 仅 1 个请求

通过预购建我们的开发会更加的流畅:

  • 模块预打包是 提前做的
  • 热更新时不需要重复处理依赖模块
  • Vite 会缓存打包结果(除非你改了依赖)

为什么esbuild快?

  • esbuild语言为go语言,并非node.js

  • 因为node.js是单线程语言,有性能瓶颈。 Go 是编译型语言,拥有高性能并发模型。 编译后 esbuild 是一个原生可执行文件(esbuild CLI),性能更接近底层工具。

  • Webpack、Babel 构建流程是“多个阶段多个插件”,每个插件做一部分。

  • esbuild 使用单阶段编译器,一步完成解析、转换、生成,没有插件链性能损耗。

说完对依赖的处理,我们再来看看对源码的处理:

源码 通常包含一些并非直接是 JavaScript 的文件,需要转换(例如 JSX,CSS 或者 Vue/Svelte 组件),时常会被编辑。同时,并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)。

Vite原生ESM方式给浏览器提供源码,代替浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码。只有当屏幕中真正使用了这个源码,服务器才会去请求。这个方面也体现了Vite的快。

讲述完开发阶段vite做了什么变快,我们来看看具体如何运行的:

步骤:

  1. 启动本地开发服务器——Vite启动后开启一个基于Koa的本地服务器
  2. 遇到模块请求(例如src/main.ts)——Vite不打包,而是直接加载源码。
  3. esbuild 实时将 TS/JSX/Vue 转译成浏览器能运行的 JS :速度极快,因为 esbuild 是用 Go 写的,比 Babel 快几十倍。
  4. 浏览器加载模块 + HMR 更新——每个模块都是独立请求,且支持热更新

对比webpack和vite的开发阶段,为什么vite要快人一步呢?

webpack

首先webpack他的开发阶段模式是:Bundle based dev server基于打包的开发产物 其核心是:

在开发时也先进行一次完整的打包构建,再启动开发服务器(如 webpack-dev-server)为浏览器提供资源。

工作流程

1.读取入口文件(如 src/index.js)

2.Webpack 或其他打包工具会:

  • 解析所有依赖(递归查找 import/require)

  • 把模块打包成浏览器能识别的 JS 文件(CommonJS → ESM,TS → JS,SASS → CSS 等)

3.将结果保存在内存中

  • webpack-dev-server 在内存里维护一个虚拟文件系统,浏览器请求资源时从内存里读取

4.浏览器访问 localhost:3000,加载打包好的文件

这种模式下:

  • 启动慢(由于首次启动需要完整打包,项目大的情况下比较糟糕)
  • 热更新慢(改动任何模块,都要重新打包依赖图的一部分甚至全部)

官方文档中对于Bundle based dev server解释的图:

image.png

vite

vite与webpack不同,其采取的是:Native ESM based dev server原生 ESM 模块开发服务器,其是Vite的核心特性:

开发环境下不再进行“打包构建”,而是直接利用浏览器对 ES 模块(ESM, import/export)的原生支持,按需加载模块。

工作流程

1️. 浏览器请求入口文件 /src/main.js Vite 返回一个经过轻量转译的 ESM(通常只处理 alias / TS / JSX 等)

2️. 浏览器遇到 import x from './foo.js',会自动发起请求 /src/foo.js

  • Vite 截获请求,动态返回模块内容

  • 无需提前打包整个依赖图,只处理当前请求的模块

3️.浏览器递归请求依赖模块,模块按需加载

这种模式下:

  • 启动速度快(没有预打包整个应用,直接启动)
  • 热更新速度快(修改一个模块只需热更新那个模块)
  • 按需处理模块(不打包所有依赖,只处理你当前浏览器请求的模块)

官方文档中对于Native ESM based dev server的图:

image.png


构建阶段

构建阶段下,vite在一些情况也会比webpack要快:

Vite 使用的是 Rollup(+ esbuild)作为打包器,并做了额外的性能优化:

优化项Vite 优势
打包引擎使用 Rollup,模块分析更彻底,Tree-shaking 更强
压缩方式使用 esbuild 替代 Terser,速度提升 10-40 倍
插件系统插件更轻,按需使用,没有复杂 loader 链
输出策略默认使用现代浏览器特性(如 ES2015 输出)更精简
缓存机制多次构建可复用缓存,避免重复处理

虽然 Vite 通常很快,但并非总是碾压 Webpack:

  1. 极大型项目(几千个模块)
    Rollup 是单线程分析模块图,构建时间可能比 webpack 更长

  2. 需要复杂构建策略(如 SSR + 多入口)
    Webpack 的插件/loader 更成熟,更好控制构建粒度

  3. 打包非 JS 资源(如 WebAssembly, 多语言切包)
    某些 loader 和工具链 Webpack 支持更丰富

尾言

📌 总结一句话:

Vite 的快不是玄学,是通过:

  • esbuild 预构建依赖 + 缓存
  • 原生 ESM + 按需请求 + 高效热更新
  • 使用 Rollup + esbuild 的现代构建链

三个层面实现的。

相比传统打包器,它在现代 Web 开发中带来了真正的提速体验。