Appearance
Vite 依赖预构建
Vite 在开发环境下会自动预构建项目依赖,这是一个重要的性能优化机制,解决了传统打包工具的一些痛点。
预构建的目的
1. CommonJS 和 UMD 兼容性
Vite 基于原生 ES 模块,但许多 npm 包使用 CommonJS 或 UMD 格式。预构建将这些依赖转换为 ES 模块:
// 例如,lodash 使用 CommonJS 格式
const _ = require('lodash') // 需要转换为 ES 模块
// 预构建后可以正常使用
import _ from 'lodash'
2. 性能优化
预构建将多个小模块合并为单个模块,减少 HTTP 请求数量:
// 没有预构建 - 需要大量请求
node_modules/
├── vue/
│ ├── index.js
│ ├── dist/
│ │ ├── runtime.js
│ │ └── compiler.js
│ └── shared/
│ ├── constants.js
│ └── utils.js
└── lodash/
├── index.js
├── array.js
└── object.js
// 预构建后 - 合并为单个文件
node_modules/.vite/deps/
└── vue.js # 合并了所有 vue 相关模块
预构建工作原理
首次运行
- Vite 扫描源代码中的依赖导入
- 识别需要预构建的依赖
- 使用 esbuild 将它们转换为 ES 模块
- 将结果缓存到 node_modules/.vite 目录
后续运行
- 检查依赖是否已缓存
- 如果依赖未改变,直接使用缓存
- 如果依赖改变,重新预构建
配置预构建
基本配置
// vite.config.js
export default {
optimizeDeps: {
include: ['vue', 'vue-router'], // 强制包含某些依赖
exclude: ['some-big-lib'], // 排除某些依赖
force: true // 强制预构建(忽略缓存)
}
}
包含特定依赖
// vite.config.js
export default {
optimizeDeps: {
include: [
'lodash-es', // 直接指定包名
'@babel/parser', // 作用域包
'my-package/dist/esm' // 包的特定子模块
]
}
}
排除依赖
// vite.config.js
export default {
optimizeDeps: {
exclude: [
'some-big-lib', // 排除大体积库
'dev-only-package' // 排除仅开发时使用的包
]
}
}
预构建触发时机
自动扫描
Vite 会自动扫描以下文件中的依赖:
.js,.ts,.jsx,.tsx,.vue,.svelte等源文件index.html中的<script>标签
手动触发
// 通过命令行强制预构建
npm run dev -- --force
# 或
vite --force
高级配置
自定义 esbuild 选项
// vite.config.js
export default {
optimizeDeps: {
esbuildOptions: {
// 传递给 esbuild 的选项
define: {
global: 'globalThis'
},
plugins: [
// esbuild 插件
]
}
}
}
处理特殊情况
// vite.config.js
export default {
optimizeDeps: {
include: [
// 对于深层嵌套的依赖
'monaco-editor/esm/vs/editor/editor.main.js'
],
// 强制某些依赖不被预构建
exclude: [
// 对于已经 ES 模块化的依赖
'my-esm-only-lib'
],
// 自定义依赖解析
entries: [
// 指定额外的入口点
'src/preload.js'
]
}
}
预构建缓存
缓存位置
预构建的依赖存储在 node_modules/.vite 目录中:
node_modules/.vite/
├── deps/
│ ├── vue.js
│ ├── vue-router.js
│ └── _metadata.json
└── package-lock.json_timestamp
清除缓存
# 删除 .vite 缓存目录
rm -rf node_modules/.vite
# 或重新安装依赖
rm -rf node_modules
npm install
常见问题和解决方案
1. 依赖未正确预构建
// 问题:某些依赖无法正常工作
// 解决:手动包含到预构建列表
// vite.config.js
export default {
optimizeDeps: {
include: [
'problematic-package',
'another-package/esm/entry'
]
}
}
2. 预构建时间过长
// 解决:排除不需要的依赖
// vite.config.js
export default {
optimizeDeps: {
exclude: [
'dev-only-package',
'big-lib-not-used-in-dev'
]
}
}
3. 动态导入问题
// 问题:动态导入的依赖未预构建
// 解决:使用 import 全名或预声明
// vite.config.js
export default {
optimizeDeps: {
include: [
'my-dynamic-import-pkg' // 即使未直接导入也包含
]
}
}
// 或在代码中预声明
const modules = {
vue: await import('vue'),
react: await import('react')
}
与生产构建的区别
开发环境
- 预构建依赖以提高开发服务器启动速度
- 缓存预构建结果以加快后续启动
- 依赖在运行时按需加载
生产环境
- 依赖通过 Rollup 正常打包
- 不使用预构建结果
- 所有代码被优化和压缩
性能优化建议
1. 合理配置预构建列表
// vite.config.js
export default {
optimizeDeps: {
include: [
// 列出项目中使用的主要依赖
'vue',
'vue-router',
'pinia',
'axios',
'lodash-es'
],
exclude: [
// 排除仅在生产环境使用的大型库
'heavy-server-lib'
]
}
}
2. 使用 .env 文件优化
# .env.development
VITE_FORCE_PREBUNDLE=true # 强制预构建
javascript
// vite.config.js
export default {
optimizeDeps: {
force: process.env.VITE_FORCE_PREBUNDLE === 'true'
}
}
3. 监控预构建性能
// vite.config.js
export default {
plugins: [
{
name: 'prebundle-logger',
config(config, { command }) {
if (command === 'serve') {
const start = Date.now()
return {
optimizeDeps: {
...config.optimizeDeps,
// 预构建完成后记录时间
onPositive: () => {
console.log(`预构建耗时: ${Date.now() - start}ms`)
}
}
}
}
}
}
]
}
最佳实践
1. 明确声明依赖
// 明确导入需要预构建的依赖
import { createApp } from 'vue'
import { createRouter } from 'vue-router'
// 避免动态导入重要依赖
2. 定期清理缓存
# 当遇到奇怪的依赖问题时,尝试清理缓存
rm -rf node_modules/.vite
npm run dev
3. 使用适当的预构建配置
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig(({ mode }) => {
const isDev = mode === 'development'
return {
optimizeDeps: {
include: [
'vue',
'vue-router',
...(isDev ? ['@vitejs/plugin-vue'] : [])
],
force: isDev && process.env.FORCE_PREBUNDLE === 'true'
}
}
})
通过理解 Vite 的依赖预构建机制,您可以更好地优化项目性能,解决常见的依赖问题,并充分利用 Vite 的开发体验优势。