Vite 的核心工作原理
Vite(法语,意为“快”)是一种新型的前端构建工具,它从根本上改变了开发阶段的构建模式,从而实现了惊人的速度提升。其工作原理可以分为两大核心部分:开发服务器 和 生产构建。
1. 开发服务器(vite dev)- 革命性的核心
这是 Vite 速度的秘密所在,它摒弃了传统构建工具(如 Webpack)“先打包,再启动”的模式。
传统 Webpack 开发模式的问题:
- 启动慢:启动开发服务器前,
Webpack必须从入口文件开始,遍历整个项目的依赖图,将所有模块(JS, CSS, Vue/React组件等)打包(bundle)成一个或多个文件。项目越大,这个过程越慢,等待时间可能从几十秒到几分钟。 - 更新慢(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 会:
- 扫描依赖:自动扫描
package.json中的dependencies。 - 使用
esbuild 进行预打包:esbuild 是一个用 Go 语言编写的极速打包器。Vite 用它将你的第三方依赖(通常是 CommonJS 或 UMD 格式,且包含大量小文件)打包成少数几个大型的、浏览器友好的 ESM 模块。 - 缓存结果:这个
预构建的结果会被缓存在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 的发展趋势。这使得它的实现更轻量、更优雅。
总结对比
| 特性 | Vite | Webpack |
|---|---|---|
| 核心理念 | 开发时利用原生 ESM,按需服务;生产时打包 | 一切皆模块,先打包再服务 |
| 开发服务器启动 | 极快(毫秒级) | 较慢,随项目体积增大而变慢(秒级到分钟级) |
| HMR 速度 | 极快(毫秒级) ,与项目体积无关 | 较慢,随项目体积增大而变慢 |
| 编译方式 | 按需编译,浏览器请求什么才编译什么 | 全量编译,启动时编译所有模块 |
| 配置 | 非常简洁,开箱即用 | 复杂,学习曲线陡峭 |
| 生产构建工具 | Rollup | Webpack 自身 |
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是一个完备的大模块。(模块太大,要考虑分割)