Vite 双引擎与 ESM 魔法
Vite 双引擎与 ESM 魔法
写在前面
上一篇讲了 esbuild 的速度革命。这一篇讲 Vite——它站在 esbuild 的肩膀上,加上 Rollup 的成熟生产打包,成为新一代前端工具的事实标准。
本篇会讲清楚 Vite 的"双引擎"设计、ESM 为什么能颠覆开发体验、HMR 是怎么做到 50ms 的,以及 Vite 在生产环境的局限。
一、Vite 是什么
1.1 一句话定义
Vite 是新一代前端构建工具,开发环境用 esbuild + 原生 ESM,生产环境用 Rollup。
由 Vue.js 作者尤雨溪 2020 年底发布,核心目标:解决开发体验的痛点。
1.2 Vite 的口号
Vite = French word for "fast", pronounced /vit/
1.3 性能数据
| 工具 | 冷启动 | HMR |
|---|---|---|
| webpack 5 | 30-90s | 5-30s |
| Vite | 200ms | 50ms |
| 提升 | 100-450x | 100-600x |
冷启动从 90 秒降到 200ms——这是质变。
二、Vite 解决的问题
2.1 webpack 时代的痛点
// 项目规模:中型 React 应用
node_modules: 2GB+
src: 500 文件
冷启动 60 秒——每次拉代码、装新依赖、CI 跑测试,都要等。
HMR 5-30 秒——改一行 CSS 都要等,严重打散心流。
2.2 痛点的根源
webpack 必须先打包整个应用才能启动 dev server:
src/app.js + 1000 个模块 → 打包成 bundle.js → 启动 dev server
模块越多,打包越慢。O(n) 的复杂度。
2.3 Vite 的解法
Vite 在开发环境不打包:
src/app.js → 直接作为 ESM 提供给浏览器
浏览器收到 app.js,看到里面的 import,自己发请求:
// 浏览器看到的代码
import { foo } from './foo.js'; // 浏览器:GET /src/foo.js
import Bar from './Bar.vue'; // 浏览器:GET /src/Bar.vue
import _ from 'lodash'; // 浏览器:GET /node_modules/.vite/lodash.js
开发服务器只做一件事:按需转译 + 响应请求。
三、原生 ESM 的关键
3.1 浏览器原生支持 ESM
现代浏览器都支持 ES Module:
<script type="module" src="/src/main.js"></script>
// main.js
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
浏览器自己处理 import,不需要打包。
3.2 对比 CommonJS
// CommonJS - 运行时才知道依赖
const foo = require('./foo');
// ESM - 静态结构,浏览器提前知道依赖
import foo from './foo';
ESM 的静态性让浏览器可以做并行加载、tree shaking、循环依赖检测。
3.3 HTTP/2 多路复用
现代服务器都用 HTTP/2,一个连接多请求,1000 个小文件并发加载没问题。
webpack 的 bundle.js 一次性下载,在 HTTP/1.1 时代有优势;HTTP/2 时代,Vite 的多文件反而更快。
四、Vite 双引擎
4.1 开发环境:esbuild + 原生 ESM
浏览器请求 main.js
↓
Vite 拦截,转译(main.js 改动少,直接返)
↓
浏览器请求 App.vue
↓
Vite 拦截,转译(用 esbuild 处理 .vue)
↓
浏览器请求 lodash
↓
Vite 拦截,看是否在依赖缓存(node_modules/.vite)
↓
已缓存:返
未缓存:用 esbuild 转译并缓存
关键:依赖只预打包一次(冷启动时),应用代码按需转译。
4.2 生产环境:Rollup
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue'],
'utils': ['lodash-es'],
},
},
},
},
};
生产环境用 Rollup 打包——生态成熟,产物稳定。
4.3 为什么开发用 esbuild,生产用 Rollup
| 维度 | esbuild | Rollup |
|---|---|---|
| 速度 | 极快 | 中等 |
| 插件生态 | 早期 | 成熟 |
| 输出产物质量 | 一般 | 优秀 |
| 代码分割 | 简单 | 精细 |
| Tree Shaking | 中等 | 优秀 |
开发要速度,生产要质量——Vite 把两者分开。
五、HMR 原理
5.1 webpack HMR 的痛点
webpack 的 HMR 流程:
文件改动 → 重新编译这个文件 + 依赖图重新计算
→ 通知客户端:哪些模块失效
→ 客户端拉取新模块
→ 重新执行依赖链
问题:改一个文件,可能要重新编译 100 个依赖它的模块。
5.2 Vite HMR
Vite 利用 ESM 的静态结构:
文件 App.vue 改动
↓
Vite 知道 App.vue 的依赖边界(只 import 什么)
↓
精确地告诉浏览器:这个模块变了
↓
浏览器只重载这个模块,父模块通过 ESM 重新 import
// Vite 客户端代码(简化)
if (import.meta.hot) {
import.meta.hot.accept('./App.vue', (newModule) => {
// 重新挂载
createApp(newModule.default).mount('#app');
});
}
50ms 完成 HMR,因为只更新一个文件。
5.3 框架集成
Vue:
// Vue SFC 自动支持 HMR
// vite-plugin-vue 注入 import.meta.hot.accept
React:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
export default Counter;
// main.js
import Counter from './Counter';
function App() {
return <Counter />;
}
// react-refresh 处理 HMR
if (import.meta.hot) {
import.meta.hot.accept();
}
六、依赖预打包
6.1 为什么需要预打包
node_modules 里的依赖大多是 CommonJS,浏览器不认识:
// lodash 里的代码
module.exports = ...;
Vite 在冷启动时用 esbuild 把所有 CommonJS 转成 ESM,缓存到 node_modules/.vite/。
6.2 预打包的副产品
多个小文件 → 一个大文件:
// 原来 1000 个小文件
import _ from 'lodash'; // 触发 1000 个请求
// 预打包后 1 个文件
import _ from '/node_modules/.vite/lodash.js'; // 1 个请求
冷启动快 5-10 倍。
6.3 自动检测
Vite 自动扫描 import 语句,发现新依赖就触发预打包:
// dev server 控制台
[vite] new dependencies optimized: vue-router, pinia
七、插件系统
7.1 Vite 插件本质
Vite 插件兼容 Rollup 插件 API:
// 一个简单的 Vite 插件
export default function myPlugin() {
return {
name: 'my-plugin',
// Rollup hook
transform(code, id) {
if (id.endsWith('.vue')) {
return code.replace(/foo/g, 'bar');
}
},
// Vite 特有的 hook
configureServer(server) {
server.middlewares.use((req, res, next) => {
// 自定义中间件
next();
});
},
};
}
7.2 常用插件
| 插件 | 用途 |
|---|---|
@vitejs/plugin-vue |
Vue SFC |
@vitejs/plugin-react |
React Fast Refresh |
@vitejs/plugin-legacy |
兼容旧浏览器 |
vite-plugin-pwa |
PWA |
vite-plugin-svg-icons |
SVG 雪碧图 |
unplugin-vue-components |
按需引入组件 |
unplugin-auto-import |
自动 import API |
vite-plugin-mock |
Mock 数据 |
7.3 实战:自动 import API
// vite.config.js
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
export default {
plugins: [
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
dts: 'src/auto-imports.d.ts',
}),
Components({
dts: 'src/components.d.ts',
}),
],
};
<!-- 不用 import,直接用 -->
<script setup>
const count = ref(0); // 自动从 vue 导入
const route = useRoute(); // 自动从 vue-router 导入
</script>
八、生产构建
8.1 配置
// vite.config.js
export default {
build: {
outDir: 'dist',
sourcemap: false,
minify: 'esbuild', // 或 'terser'
target: 'es2020',
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
}
},
chunkFileNames: 'assets/[name].[hash].js',
entryFileNames: 'assets/[name].[hash].js',
},
},
chunkSizeWarningLimit: 1500,
},
};
8.2 构建产物分析
// 安装 rollup-plugin-visualizer
import { visualizer } from 'rollup-plugin-visualizer';
export default {
plugins: [
visualizer({
filename: './dist/stats.html',
open: true,
}),
],
};
8.3 多页面应用
export default {
build: {
rollupOptions: {
input: {
main: '/index.html',
admin: '/admin.html',
},
},
},
};
九、Vite 的局限
9.1 生态还在追赶
老项目迁移 Vite 不容易——很多 webpack 插件没有 Vite 版本。
9.2 大量小文件
如果项目有几千个文件,首次加载慢(浏览器要请求几千个 HTTP)。
但 HTTP/2 + 浏览器缓存 + 按需路由,只有第一次慢。
9.3 老浏览器兼容
Vite 默认目标浏览器要支持原生 ESM(Chrome 61+、Firefox 60+、Safari 11+)。
老浏览器要用 @vitejs/plugin-legacy:
import legacy from '@vitejs/plugin-legacy';
export default {
plugins: [
legacy({
targets: ['ie >= 11'],
}),
],
};
会大幅增加产物体积。
9.4 生产构建还是 Rollup
Rollup 比 esbuild 慢,生产构建速度不如 esbuild。
公司项目:开发用 Vite,生产构建 30-60 秒,不算快但可以接受。
十、SSR / SSG 支持
Vite 支持 SSR 和 SSG:
// vite.config.js
export default {
build: {
ssr: 'src/entry-server.ts',
},
};
// entry-server.ts
import { renderToString } from 'vue/server-renderer';
import { createApp } from './main';
export async function render(url, manifest) {
const app = createApp();
const html = await renderToString(app);
return html;
}
实际项目更常用 Nuxt 3 / Next.js 这些上层框架——它们已经用 Vite 作为底层。
十一、Vite vs webpack vs Rspack
11.1 对比表
| 维度 | Vite | webpack | Rspack |
|---|---|---|---|
| 开发体验 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 生产产物 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 启动速度 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| HMR 速度 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 生态成熟度 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| webpack 兼容 | ❌ | ✅ | ✅ |
| 配置复杂度 | ⭐⭐(简单) | ⭐(复杂) | ⭐⭐(接近 webpack) |
11.2 选型建议
新项目 + 追求开发体验 → Vite
存量 webpack 改造 → Rspack / Rsbuild(无痛迁移)
库代码打包 → Rollup(精细 tree shaking)
十二、本篇小结
| 主题 | 关键点 |
|---|---|
| 双引擎 | 开发 esbuild + 生产 Rollup |
| 原生 ESM | 浏览器处理 import,免打包 |
| 依赖预打包 | node_modules 一次性转 ESM |
| HMR | 50ms,基于 ESM 静态边界 |
| 插件 | 兼容 Rollup 插件 API |
下一篇讲 Rspack / Rsbuild Rust 系的崛起——webpack 兼容方案的速度革命。