1. 为什么需要关注 CommonJS 转换问题
在 Vite 项目中处理 CommonJS 模块是个看似简单实则容易踩坑的话题。作为从 Webpack 迁移到 Vite 的老手,我深刻体会到这个配置项的重要性。现代前端工具链正在全面转向 ESM,但现实情况是我们仍需要与大量 CommonJS 模块共存。
Vite 的构建流程基于 Rollup,而 Rollup 原生只支持 ES Modules。这就意味着所有使用 require() 和 module.exports 的代码都需要被转换。@rollup/plugin-commonjs 就是这个转换器,而 build.commonjsOptions.include 就是控制它工作范围的关键开关。
2. 必须配置 include 的三种典型场景
2.1 处理非 node_modules 中的遗留代码
我最近接手的一个老项目就遇到了这个问题。项目中有个 lib/ 目录存放着五年前写的工具函数,全部使用 CommonJS 规范。当用 Vite 构建时,控制台不断报错 module.exports is not defined。
解决方案:
javascript复制// vite.config.js
export default defineConfig({
build: {
commonjsOptions: {
include: [
/lib\/.*\.js$/,
// 也可以明确指定文件路径
path.resolve(__dirname, 'lib/legacy-utils.js')
]
}
}
})
经验之谈:对于老项目迁移,建议先用
/** @type {import('vite').UserConfig} */注释来获取类型提示,避免配置错误。
2.2 处理 node_modules 中的问题依赖包
上周在集成一个日期处理库时就踩了坑。这个库的 package.json 同时声明了 module 和 main 字段,但实际导出方式很诡异。构建后运行时出现 formatDate is not a function 错误。
诊断步骤:
- 在浏览器调试工具中检查打包后的代码
- 发现该库的导出被包裹在奇怪的
commonjsRequire函数中 - 确认是 CommonJS 转换不彻底导致
修正方案:
javascript复制include: [
/node_modules\/problematic-date-library/,
// 使用更宽松的匹配规则
/node_modules\/(lib1|lib2)/
]
2.3 大型项目的性能优化
在超过 500 个模块的电商后台项目中,我发现构建时 CommonJS 自动检测会消耗约 800ms。通过精确指定需要转换的路径,构建时间减少了 300ms。
优化配置示例:
javascript复制include: [
// 只转换已知的 CommonJS 模块
/node_modules\/(lodash|moment|old-plugin)/,
/src\/legacy/
]
3. 深入理解转换原理
3.1 Rollup 的模块处理流程
当 Rollup 遇到一个模块时:
- 先尝试作为 ES 模块解析
- 如果失败且该模块路径匹配 include 规则
- 调用
@rollup/plugin-commonjs进行转换 - 生成符合 ESM 规范的临时文件
- 继续后续的打包流程
3.2 常见转换问题分析
我整理了几个典型错误及其根源:
| 错误信息 | 可能原因 | 解决方案 |
|---|---|---|
X is not exported |
转换后导出名称不一致 | 检查 namedExports 配置 |
require is not defined |
转换未生效 | 确保 include 包含该文件 |
Circular dependency |
循环引用处理不当 | 启用 dynamicRequireTargets |
4. 高级配置技巧
4.1 混合模块处理
在 Vue 2 项目中经常会遇到这种情况:
javascript复制// 一个文件中同时存在
import Vue from 'vue'
const utils = require('./utils')
需要启用混合模式:
javascript复制commonjsOptions: {
transformMixedEsModules: true,
// 对于 Vue 2 特别重要
include: [/node_modules\/vue/]
}
4.2 排除特定模块
有些现代库虽然放在 node_modules 但已经是 ESM 格式,强制转换反而会出错:
javascript复制commonjsOptions: {
include: [/node_modules/],
exclude: [/node_modules\/lodash-es/]
}
4.3 调试配置
当配置不生效时,我常用的调试方法:
- 在
vite.config.js中添加console.log - 使用
--debug参数运行构建 - 检查 Rollup 的转换日志
5. 实战案例解析
5.1 案例一:Monorepo 项目
假设有这样的结构:
code复制packages/
shared/ # CommonJS
app/ # ESM
配置方案:
javascript复制include: [
/packages\/shared\/.*\.js$/,
// 处理符号链接
/node_modules\/@project\/shared/
]
5.2 案例二:混合技术栈
最近改造的一个项目包含:
- 主应用(Vue 3)
- 微前端子应用(React + CommonJS)
关键配置:
javascript复制include: [
/subapp\/.*\.js$/,
/node_modules\/(react|react-dom)/
]
6. 性能与兼容性平衡
经过多个项目实践,我总结出以下经验:
- 最小化原则:只包含确实需要的路径,避免全局匹配
- 分层配置:
javascript复制include: [ // 第一层:明确知道的 CommonJS 模块 /node_modules\/(libA|libB)/, // 第二层:遗留代码目录 /src\/legacy\//, // 第三层:兜底规则(慎用) // /node_modules/ ] - 监控变化:定期检查依赖是否已更新到 ESM 版本
7. 常见问题排查指南
7.1 配置不生效怎么办?
检查顺序:
- 确认文件路径确实匹配正则表达式
- 检查 Vite 版本(2.7+ 有行为变化)
- 查看 Rollup 的警告信息
7.2 如何知道一个模块是否需要转换?
我的判断方法:
bash复制# 快速检查模块格式
cat node_modules/some-lib/package.json | grep type
7.3 动态 require 的处理
对于 require(someVar) 这种动态引入:
javascript复制commonjsOptions: {
dynamicRequireTargets: [
'./src/plugins/*.js'
]
}
8. 未来演进趋势
随着生态发展,我观察到:
- 主流库都在转向 ESM
- Webpack 5 也优化了 ESM 支持
- Node.js 正在减少对 CommonJS 的依赖
建议策略:
- 新项目坚持纯 ESM
- 老项目逐步迁移,而非全量转换
- 关注 Vite 的更新日志,及时调整配置
9. 我的个人实践心得
经过十几个项目的实战,总结出以下经验:
- 配置要文档化:在团队文档中明确说明每个 include 规则的原因
- 渐进式迁移:可以先用宽松规则保证构建通过,再逐步优化
- 性能监控:对比配置前后的构建速度差异
- 版本控制:将 vite.config.js 的修改与依赖升级关联提交
最后提醒:虽然这个问题看起来很小,但在大型项目迁移时可能成为主要障碍。建议在项目初期就规划好模块规范策略,避免后期出现兼容性问题。