Skip to main content
Vite 快在开发阶段按需编译、只在浏览器请求时才处理模块,生产环境它照样打包。分清这两件事,很多问题就顺了。 延伸方向: Vite 团队正在用 Rust 编写 Rolldown 打包器,目标是统一开发与生产环境的构建链路,目前仍在推进稳定化。[4]

Q1: Vite 为什么比 Webpack 快?

1

2020年: ESM 已成浏览器标配

现代浏览器原生支持 ES Modules(<script type="module">)。浏览器可以直接解析 import 语句并按需加载模块,不需要额外的打包步骤。
2

致命痛点: Webpack 的全量打包

Webpack 的开发服务器启动前,必须从 entry 开始递归解析所有依赖,用 Loader 转换每个文件,打包成 bundle 后才能启动。项目越大,启动越慢(线性增长)。
3

2020年: Vite 利用浏览器原生 ESM

Vite 的核心创新:不打包,直接启动开发服务器。浏览器请求哪个模块,Vite 就按需转换哪个模块。启动时间是常数级的,不随项目大小增长。
4

两个关键技术支撑

  1. esbuild(Go 语言编写):用于预构建 node_modules 中的 CommonJS 依赖为 ESM,比 JS 打包器快 10-100 倍。
  2. 浏览器原生 ESM:模块解析由浏览器完成(C++ 实现),而不是 Node.js(JavaScript 实现)。
5

关键权衡: 开发快 vs 生产稳

Vite 开发环境追求极致的启动速度(按需编译),生产环境追求极致的包体积和兼容性(用 Rollup 打包)。两者使用不同的策略,各取所长。
Vite 解决的核心问题:
Vite 把模块解析的工作从 Node.js(慢)转移到了浏览器(快),开发环境只在请求时按需编译,而不是启动时全量打包。

🚫 没有 Vite (Webpack 模式)

你开了一家快递站,每次有新包裹进来,你必须先把仓库里所有包裹都检查一遍(递归解析依赖),整理好(打包),才能开始派送。仓库越大,等待越久。

✅ 引入 Vite (按需编译模式)

你开了一家快递站,包裹来了直接派送(启动服务器)。哪个包裹需要拆包检查(编译),你才拆哪个(按需编译)。仓库大小不影响你开门营业的速度。
Webpack 的开发模式就像预制宴席——客人还没来,厨房先把所有菜都做好摆上桌(全量打包),菜越多准备时间越长。Vite 就像自助餐厅——厨房只备好食材(启动服务器),客人想吃什么才现场做(按需编译),100 道菜和 1000 道菜的开门速度是一样的。

Q2: Vite 的预构建(Pre-bundling)是什么?为什么需要它?

Vite 的本质是利用浏览器原生 ESM 按需加载模块,但 node_modules 中的模块通常不是 ESM 格式。预构建解决了这个矛盾。核心逻辑只有 3 步:
预构建的结果缓存在 node_modules/.vite 目录中。
  • 何时触发重新预构建:package.json 变更、vite.config.js 的 optimizeDeps 变更、lockfile 变更。
  • 手动强制重新预构建:删除 node_modules/.vite 目录,或执行 vite --force。
  • 手动指定预构建:通过 optimizeDeps.include 强制预构建某些依赖(如 monorepo 中的本地包)。
预构建就像餐厅的食材预处理。买回来的蔬菜(node_modules 中的 CommonJS 模块)不能直接上桌(浏览器无法解析),需要先洗切好(转为 ESM)。切好的食材按类别放好(合并零散模块),用保鲜膜包好放冰箱(缓存到 .vite 目录)。下次做菜直接拿,不用重新洗切。

预构建的缓存机制

预构建的结果缓存在 node_modules/.vite 目录中,下次启动时直接读取缓存:

手动配置预构建的场景

当 Vite 的自动预构建漏掉某些依赖时,需要手动配置:

Q3: Vite 和 Webpack 的本质区别是什么?

基础梳理

专家视角深度剖析

很多人能说出”Vite 用原生 ESM,所以快”,但实际更在意的是:Vite 的 HMR 有没有上限?什么时候 Vite 反而比 Webpack 慢? Vite 的 HMR 有一个隐含的性能瓶颈:深层依赖链的瀑布式请求。当一个模块被修改后,浏览器需要重新请求这个模块。如果这个模块被 100 个文件 import,浏览器会发出 100 个 HTTP 请求。而 Webpack 的 HMR 只需要下载一个 update chunk。 在大型 monorepo 项目中,模块间的依赖关系极其复杂,Vite 的 HMR 可能比 Webpack 更慢。解决方案是通过 optimizeDeps.include 手动预构建这些高频依赖,减少运行时请求数。
Webpack 的模块解析就像全文翻译——拿到一整本书(项目),从第一页翻到最后一页(递归解析所有依赖),全部译完才能开始阅读(启动服务器)。Vite 的模块解析就像同声传译——翻译员(浏览器)听到一句翻一句(按需编译),你马上就能听懂(即时响应),不用等整本书翻完。Vite 快的根本原因是把翻译工作从”人工翻译”(Node.js/JavaScript)交给了”同声传译设备”(浏览器/C++)。

ESM 为什么快?浏览器原生模块解析的优势

ESM 快的根本原因是模块解析从 Node.js(JavaScript)转移到了浏览器(C++):

HMR 原理对比:Vite vs Webpack

Vite 和 Webpack 的本质区别是模块解析的时机:Webpack 在 Node.js 中用 JavaScript 全量解析,Vite 把解析交给浏览器(C++ 实现),开发环境只在请求时按需编译。

Q4: Vite 生产环境为什么用 Rollup 不用 esbuild?

esbuild 的局限:
Rollup 的优势:
Vite 的未来计划:Rolldown(Rust 编写的 Rollup 替代品)
Vite 的 build.rollupOptions 可以精细控制生产打包策略。manualChunks 用于手动拆分 chunk(如把 react、lodash 拆到独立 chunk),output.format 控制输出格式(es 用于现代浏览器,cjs 用于 SSR)。
esbuild 就像快餐厨师——速度极快(Go 语言写的),翻台率高,但菜品花样有限(代码分割弱、CSS 处理弱)。Rollup 就像米其林大厨——每道菜都精雕细琢(Tree-shaking 最激进、输出最干净),但上菜慢(打包速度慢)。Vite 生产环境选 Rollup 就像请米其林大厨做正式宴席——客人等得起,但味道必须好。

Rolldown:Vite 的未来打包器

Rolldown 是 Vite 团队用 Rust 编写的打包器,目标是统一开发和生产环境:

Vite 的 Plugin 系统设计

Vite 的 Plugin 系统借鉴了 Rollup,但扩展了开发服务器特有的钩子:
esbuild 快但功能不全(代码分割弱、无持久化缓存、CSS 处理弱),Rollup 功能全但慢。Rolldown(Rust)正在弥合这个差距,目标是兼容 Rollup 的 API 与插件生态。

🔍 底层源码:Vite 的模块转换链路

Vite 的开发服务器基于 Connect(Node.js HTTP 框架),每个模块请求经过一条 Plugin 管道(详见 Vite 源码:packages/vite/src/node/server/transformRequest.ts):
每个步骤都是由 Vite 的 Plugin 系统驱动的。Vite 内置的 vite:esbuild 负责 TS/JSX 的语法转换,@vitejs/plugin-react 在此基础上叠加 Babel 插件实现 Fast Refresh,vite:css 在 transform 阶段处理 CSS Modules。 Vite 的 HMR 基于模块粒度的更新,而不是 Webpack 的 chunk 粒度(详见 Vite 源码:packages/vite/src/node/server/hmr.ts):

十、及格回答 vs 满分回答

问题:“说说 Vite 和 Webpack 的区别”

及格回答(60-70分):
“Vite 用浏览器原生 ESM,按需编译,启动快。Webpack 先打包再启动。Vite 生产环境用 Rollup。”
问题出在:知道结论但不理解原理。 满分回答(90-100分):
“Vite 和 Webpack 的本质区别是模块解析的时机和执行环境。Webpack 在 Node.js 中用 JavaScript 递归解析所有依赖并打包,启动时间随项目大小线性增长。Vite 把模块解析交给浏览器(C++ 实现),开发环境只在请求时按需编译,启动时间是常数级。[1]”
“Vite 用两个关键技术支撑:esbuild(Go 语言)负责预构建 node_modules 中的 CommonJS 依赖为 ESM,结果缓存在 .vite 目录中。浏览器原生 ESM 负责运行时的模块解析和依赖发现。HMR 时 Vite 只发送变更模块的新代码,不需要重新打包。[2]”
“但 Vite 的 HMR 有上限:深层依赖链会导致浏览器发出大量 HTTP 请求(瀑布式请求)。在大型 monorepo 中可能比 Webpack 更慢。解决方案是通过 optimizeDeps.include 手动预构建高频依赖。生产环境用 Rollup 打包,因为 esbuild 的代码分割和 Tree-shaking 不如 Rollup 成熟。未来的 Rolldown(Rust 编写)将统一开发和生产环境。[3]“

常见追问

答题要点: 说出预构建的两个目的和手动配置的场景。高分回答: “预构建做两件事:1) 把 node_modules 中的 CommonJS 模块转为 ESM(浏览器只能解析 ESM);2) 合并零散模块减少 HTTP 请求数(如 lodash-es 的 600+ 个文件合并为 1 个)。需要手动配置 optimizeDeps.include 的场景:依赖是 CommonJS 格式且不在 node_modules 中(如 monorepo 中的本地包)、依赖的模块数量很多(如大型 UI 库)、Vite 的自动预构建漏掉了某些依赖。不配置的话,浏览器会发起大量请求,导致页面加载慢。”
答题要点: 对比 esbuild 和 Rollup 在生产环境的关键差异。高分回答: “esbuild 虽然快,但有几个关键缺陷:1) 代码分割支持不完善,无法精确控制 chunk 粒度;2) 没有持久化缓存,每次全量构建;3) CSS 处理能力弱,不支持 CSS Modules 等高级特性;4) 插件生态不成熟。Rollup 的优势:Tree-shaking 最激进(基于 ESM 静态分析)、代码分割更精细(支持 manualChunks)、输出更干净(几乎没有运行时开销)、插件生态丰富。未来 Vite 计划用 Rolldown(Rust 编写的 Rollup 替代品)统一开发和生产环境。”
答题要点: 从更新粒度和实现机制两个维度对比。高分回答: “核心区别是更新粒度。Webpack 的 HMR 基于 chunk——文件变更后,重新编译受影响的模块链,生成 update chunk(包含所有变更模块的新代码 + manifest),客户端下载整个 chunk 并替换。Vite 的 HMR 基于模块——文件变更后,只向浏览器发送单个变更模块的新代码,浏览器用新代码替换旧模块并重新执行。Vite 的 HMR 更快是因为没有打包开销。但代价是:如果一个模块被 100 个文件 import,浏览器需要发起 100 个 HTTP 请求来更新所有引用者。Webpack 的 chunk 模式只需要下载一个文件。在深层依赖链场景下,Vite 的 HMR 可能反而更慢。”
答题要点: 从设计理念和实现机制两个维度对比。高分回答: “Vite 的 Plugin 系统设计上借鉴了 Rollup,但扩展了开发服务器特有的钩子。核心区别:
  1. 钩子不同:Webpack 的 Plugin 基于 Tapable,钩子覆盖编译的每个阶段(compile、make、seal、emit 等)。Vite 的 Plugin 钩子更简洁,分为 Rollup 兼容钩子(resolveId、load、transform)和 Vite 独有钩子(configureServer、transformIndexHtml)。
  2. 执行环境不同:Webpack 的 Plugin 在 Node.js 中执行。Vite 的 Plugin 同时在服务端和客户端执行——开发时在 Node.js 中执行,构建时在 Rollup 中执行。
  3. 扩展方式不同:Webpack 用 Tapable 的 tap/tapAsync 注册回调。Vite 用对象的属性定义钩子函数,更直观。”
答题要点: 说出 Vite 内置的 CSS 处理能力和零配置优势。高分回答: “Vite 对 CSS 的处理有几个优势:
  1. 开箱即用:内置支持 CSS Modules(*.module.css)、PostCSS、CSS 预处理器(Sass/Less/Stylus),不需要额外配置 Loader。
  2. 开发环境:CSS 通过 <style> 标签注入,HMR 时只更新变化的 CSS,不需要重新加载页面。
  3. 生产环境:CSS 自动提取为独立文件,支持代码分割。
  4. CSS Modules:自动识别 *.module.css 文件,生成唯一的类名,避免样式冲突。
对比 Webpack:Webpack 需要配置 css-loader、style-loader、MiniCssExtractPlugin 等多个 Loader/Plugin。”
答题要点: 说出 Vite SSR 的基本原理和使用场景。高分回答: “Vite 的 SSR 支持通过 ssrLoadModule API 实现。核心原理:
  1. 开发环境:Vite 的开发服务器同时支持客户端和服务端。服务端直接加载 ESM 模块,不需要打包。
  2. 生产环境:用 Rollup 打包两份代码——客户端 bundle 和服务端 bundle。
  3. SSR 流程:服务端执行组件渲染函数 → 生成 HTML → 发送给客户端 → 客户端 hydrate(激活交互)。
Vite SSR 的优势:开发时不需要打包,启动快;HMR 时服务端代码也会热更新。Nuxt 3、Astro、SvelteKit 等框架都基于 Vite SSR。”
答题要点: 说出 Vite 的环境变量加载机制和类型安全。高分回答: “Vite 的环境变量通过 .env 文件管理:
  1. 加载优先级:.env → .env.local → .env.[mode] → .env.[mode].local
  2. 暴露规则:只有 VITE_ 前缀的变量才会暴露给客户端代码(import.meta.env.VITE_API_URL)
  3. 类型安全:在 vite-env.d.ts 中声明环境变量类型
对比 Webpack:Webpack 需要 DefinePlugin 手动定义环境变量,Vite 自动加载 .env 文件。”
答题要点: 从预构建、HMR、构建三个维度回答。高分回答: “Vite 在大型项目中可能遇到的性能问题和解决方案:
  1. 启动慢:node_modules 中的 CommonJS 模块太多,预构建耗时。解决方案:用 optimizeDeps.include 手动指定需要预构建的依赖。
  2. HMR 慢:深层依赖链导致浏览器发出大量 HTTP 请求。解决方案:用 optimizeDeps.include 预构建高频依赖,减少运行时请求数。
  3. 构建慢:Rollup 打包大型项目耗时。解决方案:用 build.rollupOptions.output.manualChunks 手动拆分 chunk,减少单次打包的模块数。
  4. 内存不足:Node.js 默认内存限制 1.5GB。解决方案:用 NODE_OPTIONS=--max-old-space-size=4096 增加内存限制。”
答题要点: 从定位、性能、生态三个维度对比。高分回答: “Vite 和 Turbopack 的定位不同:
  1. Vite:通用的前端构建工具,支持所有框架(Vue、React、Svelte 等)。开发环境用浏览器原生 ESM,生产环境用 Rollup。
  2. Turbopack:Next.js 团队用 Rust 编写的打包器,专为 Next.js 优化。开发和生产都用 Turbopack。
性能对比:Turbopack 在大型项目中比 Vite 更快(Rust 实现,增量计算)。但 Turbopack 只支持 Next.js,生态不如 Vite 广泛。选择标准:用 Next.js 就选 Turbopack,用其他框架就选 Vite。”

📚 延伸阅读与引用参考

  • 🔗 Vite 官方文档:Why Vite:Vite 作者尤雨溪亲自解释 Vite 的设计动机和技术原理——“bundling is necessary for production but a hindrance during development”。 [1]
  • 📖 Vite 源码:Vite 的完整源码,理解 Plugin 系统、HMR、预构建的底层实现。 [2]
  • 🟢 esbuild 官方文档:了解 esbuild 的性能优势和局限性,理解为什么 Vite 生产环境不用 esbuild。 [3]
  • 🟢 Rolldown 官方仓库:Vite 团队用 Rust 编写的下一代打包器,目标是统一 Vite 的开发和生产环境。 [4]
  • 🔗 尤雨溪 Vite 设计文档:尤雨溪公开的 Vite 下一代构建工具设计过程文档,展示从 esbuild + Rollup 到 Rolldown 的演进思路。 [5]