Skip to content
On this page

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 相关模块

预构建工作原理

首次运行

  1. Vite 扫描源代码中的依赖导入
  2. 识别需要预构建的依赖
  3. 使用 esbuild 将它们转换为 ES 模块
  4. 将结果缓存到 node_modules/.vite 目录

后续运行

  1. 检查依赖是否已缓存
  2. 如果依赖未改变,直接使用缓存
  3. 如果依赖改变,重新预构建

配置预构建

基本配置

// 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 的开发体验优势。