Vite 默认不支持 CommonJS,需通过预构建(optimizeDeps.include)、插件转换(@rollup/plugin-commonjs)和 TS 配置(module: "ESNext")三层协同解决;特殊情况用动态导入兜底。

Vite 本身不直接处理 CommonJS(CJS)模块的运行时转换,它默认只支持原生 ES 模块(ESM)。当项目中引入了仅提供 CommonJS 格式的第三方包(如某些老旧工具库),就会出现 require is not defined 或 Cannot resolve 'xxx' 等错误。解决的关键在于分阶段干预:预构建阶段做 ESM 转换,运行时阶段补足 CJS 兼容逻辑。
预构建阶段:让 CJS 包被 Vite 正确解析
Vite 的 optimizeDeps 会将 node_modules 中的依赖提前转为 ESM 形式供开发服务器使用。但默认只处理 .js/.ts 文件,且对混合模块(含 require/module.exports)识别有限。
你需要显式声明哪些 CJS 包必须参与预构建:
export default defineConfig({
optimizeDeps: {
include: ['lodash', 'pica', 'tinycolor2'] // 列出已知 CJS 包名
}
})⚠️ 注意:如果包内含 .vue、.html 或其他非 JS 资源,Vite 无法预构建它们,此时需排除——否则报 No loader is configured for ".vue" 错误:
optimizeDeps: {
exclude: ['some-cjs-package-with-vue-files']
}运行时阶段:用插件补全 CJS 执行能力
被 exclude 掉的 CJS 包,或预构建后仍残留 require 调用的代码,需要在构建流程中动态转换。推荐使用 @rollup/plugin-commonjs:
import commonjs from '@rollup/plugin-commonjs'
export default defineConfig({
plugins: [
commonjs({
transformMixedEsModules: true, // 允许同时存在 import/export 和 require/module.exports
include: /node_modules\/(lodash|moment|some-cjs-pkg)\// // 精确匹配路径,避免全量扫描
})
]
})✅ 它会在 Rollup 打包阶段(生产构建)或 Vite 插件链中,把 require('x') 替换为等效的 ESM 动态导入或静态导入,再注入必要的 require shim(仅限浏览器环境模拟)。
TypeScript 编译层:避免生成 CJS 语法
TS 配置不当也会“制造” CJS 问题。确保 tsconfig.json 中:
-
module设为"ESNext"(不是"CommonJS") -
moduleResolution设为"Bundler"(适配 Vite/Rollup 的解析逻辑) - 启用
allowJs: true时,务必配合checkJs: true,防止 JS 文件里混用require未被检查
这样 TS 编译输出的是标准 ESM,不会额外引入 require,从源头减少兼容负担。
特殊情况:纯 CJS 包 + 动态导入兜底
若某个包完全不可预构建、插件也无法转换(如含 __dirname、fs 等 Node.js API),唯一安全方式是动态导入 + 条件判断:
// 在组件或工具函数中
if (typeof window !== 'undefined') {
const { default: someCjsLib } = await import('some-cjs-only-lib')
someCjsLib.doSomething()
}利用 await import() 触发 Vite 的异步加载机制,绕过静态分析限制,且只在浏览器环境执行。
Vite 对 CommonJS 的兼容不是开箱即用的魔法,而是靠预构建 + 插件 + TS 配置三层协同完成的。核心思路是:能预构建的尽量预构建,不能的交给插件转换,实在不行就动态加载。


















