vite

71 阅读9分钟

Vite 的核心工作原理

Vite(法语,意为“快”)是一种新型的前端构建工具,它从根本上改变了开发阶段的构建模式,从而实现了惊人的速度提升。其工作原理可以分为两大核心部分:开发服务器 和 生产构建

1. 开发服务器(vite dev)- 革命性的核心

这是 Vite 速度的秘密所在,它摒弃了传统构建工具(如 Webpack)“先打包,再启动”的模式。

传统 Webpack 开发模式的问题:

  1. 启动慢:启动开发服务器前,Webpack 必须从入口文件开始,遍历整个项目依赖图,将所有模块(JS, CSS, Vue/React组件等)打包(bundle)成一个多个文件。项目越大,这个过程越慢,等待时间可能从几十秒到几分钟。
  2. 更新慢(HMR) :当你修改一个文件时,Webpack 需要重新计算相关模块的依赖,并重新生成打包后的 chunk。虽然有热模块替换(HMR),但在大型项目中,这个过程依然有明显的延迟。

Vite 的解决方案:基于原生 ES Modules (ESM) 的按需服务

Vite 充分利用了现代浏览器对原生 ES Modules 的支持。它的工作流程是这样的:

步骤一:启动服务器,无需打包

  • 当你运行 vite 命令时,Vite 会立即启动一个本地开发服务器。它不会对你的源代码进行任何打包。启动速度极快,几乎是瞬时的。

步骤二:浏览器发起请求

  • 当你在浏览器中访问 http://localhost:xxxx 时,浏览器会请求入口的 index.html 文件。
  • Vite 服务器接收到请求,返回 index.html

步骤三:按需编译和提供模块

  • 浏览器解析 index.html,发现一个 <script type="module" src="/src/main.ts"> 标签。这里的 type="module" 是关键,它告诉浏览器要使用 ESM 的方式来加载这个脚本。
  • 浏览器接着会向 Vite 服务器发起对 /src/main.ts 的请求
  • Vite 服务器接收到这个请求,**此时才实时地**对 main.ts 进行编译(例如,将 TypeScript 编译成 JavaScript),然后将编译后的内容返回给浏览器。
  • 在 main.ts 文件中,可能会有 import App from './App.vue'。浏览器解析到这行代码,会再次向服务器发起对 /src/App.vue 的请求。
  • Vite 服务器接收到对 .vue 文件的请求,会使用 @vitejs/plugin-vue 将其即时编译成 JavaScript 和 CSS,然后返回给浏览器。

这个过程会像瀑布一样,浏览器根据 import 语句不断发起新的请求,Vite 服务器则按需实时地编译并提供每一个被请求的模块。

总结开发服务器的核心特点:

  • 无打包(No-bundle) :源代码不进行打包,保持原始的模块化结构。
  • 按需编译(On-demand Compilation) :只有当浏览器请求某个模块时,Vite 才会去编译它。这意味着你永远不会编译那些在当前页面没有被访问到的代码。
  • 利用原生 ESM:将模块解析和依赖管理的“脏活累活”交给了浏览器自己去完成。

一个重要的优化:依赖预构建(Dependency Pre-bundling)

你可能会问:“如果我引入了像 Lodash 这样有几百个小文件的库,浏览器岂不是要发起几百个 HTTP 请求?这性能不是更差吗?”

Vite 考虑到了这一点,并通过“依赖预构建”来解决。在首次启动开发服务器时,Vite 会:

  1. 扫描依赖:自动扫描 package.json 中的 dependencies
  2. 使用 esbuild 进行预打包:esbuild 是一个用 Go 语言编写的极速打包器。Vite 用它将你的第三方依赖(通常是 CommonJS 或 UMD 格式,且包含大量小文件)打包成少数几个大型的、浏览器友好的 ESM 模块。
  3. 缓存结果:这个预构建的结果会被缓存在 node_modules/.vite 目录下。只要你的依赖没有变化,后续启动服务器时会直接使用缓存,所以这个过程也只在第一次或依赖更新后发生。

这么做的好处:

  • 格式兼容:将 CommonJS/UMD 模块转换为 ESM。
  • 性能优化:将有成百上千个小文件的库(如 lodash-es)合并成一个单一模块,大大减少了浏览器的 HTTP 请求数量。
2. 生产构建(vite build)

尽管 Vite 在开发时采用了无打包的模式,但在生产环境中,为了获得最佳的加载性能(减少网络请求、代码压缩等),Vite 依然会进行打包

  • 默认使用 Rollup:Vite 使用了社区久经考验的打包工具 Rollup 来进行生产构建。
  • 全面优化:它会进行 Tree Shaking代码压缩代码分割、CSS 提取等一系列传统构建工具所做的优化,以确保最终产物的性能。

简单来说,Vite 的哲学是:在开发环境,优先考虑开发者的体验和速度;在生产环境,优先考虑用户的加载性能。


Vite 比 Webpack 的优势在哪里?

基于以上原理,Vite 的优势非常突出和明确:

1. 极速的开发服务器启动(The Killer Feature)
  • Webpack: 冷启动需要打包整个应用,大型项目可能需要数十秒甚至数分钟。
  • Vite: 几乎是瞬间启动。因为它只需要启动一个 Web 服务器,并进行依赖预构建(有缓存时也极快)。

体验差异:这是最直观的感受。用 Vite,你按下回车,服务器就好了。用 Webpack,你可能得去泡杯咖啡。

2. 闪电般的热模块替换(HMR)
  • Webpack: 当你修改一个文件时,Webpack 需要重新构建受影响的模块链,并推送更新。项目越大,这个过程越慢。
  • Vite: HMR 是在原生 ESM 之上执行的。当一个文件被修改,Vite 只需要精确地让浏览器重新请求这一个被修改的模块,然后浏览器会自动处理模块的替换。这个过程与项目的大小无关,所以即使在大型项目中,HMR 也能保持毫秒级的响应速度

体验差异:在 Vite 中,你保存代码,页面几乎是同步更新,感觉不到任何延迟。

3. 真正的按需编译
  • Webpack: 即使你只访问了一个页面,它在启动时也已经把所有页面的代码都处理了一遍。
  • Vite: 只有当你通过路由访问某个页面时,与该页面相关的模块才会被浏览器请求,进而被 Vite 编译。这使得 Vite 在处理大型多页面应用时,启动和运行都非常轻快。
4. 更简洁的配置
  • Webpack: 配置复杂是其著名的痛点。你需要配置 loader、plugin、devServer、optimization 等大量选项,才能搭建一个现代化的开发环境。
  • Vite: 开箱即用。它为 TypeScript、JSX、CSS Modules、Sass/Less 等提供了内置支持,大部分项目几乎零配置即可启动。其配置文件 vite.config.js 也比 webpack.config.js 简洁直观得多。
5. 拥抱未来和标准
  • Vite 的核心是基于浏览器原生支持的 ESM,这是一种 Web 标准。它没有创造复杂的模块运行时(runtime),而是顺应了 Web 的发展趋势。这使得它的实现更轻量、更优雅。

总结对比

特性ViteWebpack
核心理念开发时利用原生 ESM,按需服务;生产时打包一切皆模块,先打包再服务
开发服务器启动极快(毫秒级)较慢,随项目体积增大而变慢(秒级到分钟级)
HMR 速度极快(毫秒级) ,与项目体积无关较慢,随项目体积增大而变慢
编译方式按需编译,浏览器请求什么才编译什么全量编译,启动时编译所有模块
配置非常简洁,开箱即用复杂,学习曲线陡峭
生产构建工具RollupWebpack 自身

Webpack 还有用吗?

当然有。Webpack 拥有一个极其庞大和成熟的生态系统,它的 loader 和 plugin 几乎可以解决任何你能想到的工程化问题。对于一些历史悠久、构建流程极其复杂的项目(例如某些微前端架构),Webpack 的灵活性和生态优势可能仍然是必要的。

没有依赖预构建的vite会怎么样

是的,如果没有预构建(pre-bundling),会发起非常多的请求,而这正是 Vite 引入“依赖预构建”的核心原因。

下面我给你讲得特别清楚👇


✅ 什么情况下会发起大量请求?

当依赖是 ESM(例如 lodash-es、date-fns、three.js 等)时

比如你 import:

import { debounce } from 'lodash-es';

lodash-es 是基于 ESM 的,意味着:

  • 每一个 lodash 工具函数是一个独立文件
  • 每一个工具函数还 import 几个其他文件
  • 这些依赖还 import 依赖...

最终会导致:

lodash-es/map.js
lodash-es/_arrayMap.js
lodash-es/_baseMap.js
lodash-es/_isObject.js
lodash-es/_isFunction.js
lodash-es/_getTag.js
...

浏览器会对每个 import 发一次 HTTP 请求
可能是几十到几百个请求。


🧨 为什么?

因为:

Vite dev 阶段是基于原生 ESModule 的按需请求,不打包

浏览器看到一个 import,就会请求一个模块。

👉 没有预构建 = 每个小模块都走 HTTP 请求


✅ 那预构建(pre-bundling)是干啥的?

Vite 会用 ESBuild 把这些“碎片化的依赖” 合并成一个或者少数几个大文件,例如:

node_modules/.vite/deps/lodash-es.js

这一步会极大减少模块数量:

状态请求数量
❌ 没有预构建几十~上百个请求
✔ 有预构建1 个请求

🔧 Vite 默认会自动做预构建

你第一启动 Vite dev server,会看到:

Pre-bundling dependencies:
  lodash-es
  vue
  axios
  ...

这就是在合并依赖文件,减少请求数量。


❓那什么情况下不会预构建?

只有你:

  • 手动关闭预构建
  • 或使用“裸模块导入”但路径异常
  • 或依赖本身没有被识别为需要预构建

例如你写错:

import lodash from './node_modules/lodash-es'; 

这不会触发预构建。


🔥 总结一句话

没有预构建时,浏览器会对依赖进行大量按模块的原生 ESM 请求,数量可能非常大。预构建就是为了减少这些请求,提高开发服务器性能。

开发阶段对比webpack

3. 为什么 Webpack 不需要 type="module"?

因为 Webpack 打包输出的 main.js 是一个 普通 script,里面已经没有 import/export 了。

浏览器只是执行一个普通 JS 文件即可。这个js是一个完备的大模块。(模块太大,要考虑分割)