Vite manualChunks循环依赖导致生产环境ReferenceError解决方案
原文地址: https://88box.top 生成时间: 2026-05-19 16:33:04
开发正常但生产异常的 Bug:Vite manualChunks 循环依赖导致 ReferenceError - hey99 知识搜索引擎
精选文章
开发正常但生产异常的 Bug:Vite manualChunks 循环依赖导致 ReferenceError
环境配置:vue3+vite+element-plus+pinia 一、问题现象 项目在开发环境(npm run dev)运行一切正常,但构建部署到生产环境后,浏览器控制台报出以下错误: 错误指向 e
更新于 2026-05-19 08:17
前端
前端框架
Vite
环境配置:vue3+vite+element-plus+pinia
一、问题现象
项目在开发环境(
npm run dev
)运行一切正常,但构建部署到生产环境后,浏览器控制台报出以下错误:
Uncaught
ReferenceError
:
Cannot
access
'buildProp'
before initialization
错误指向
element-plus-CLZbZCGX.js
第 627 行:
如果需要展示打包后能看的代码。需要在vite.config.js中配置这个是代码混淆,既然将打包后的代码编译为较难读的情况,比如将buildProp变量名该为El
defineConfig
({
build
: {
minify
:
false
,
//该选择默认使用esbuild
},
});
const
_prop =
buildProp
({
type
:
definePropType
(
Boolean
),
default
:
null
,
});
二、排查过程
2.1 寻找引起报错的代码
因为在项目中上一个版本代码是正常运行,但这次改动后部署就报错,最终定位到新增的一段权限校验逻辑
actions
:{
query
(
){
return
new
Promise
(
async
(resolve, reject) => {
const
result =
await
api1
();
const
res =
await
api2
()
if
(!result.
data
) {
ElMessageBox
({
message
:
"您无权限访问"
,
type
:
"warning"
,
closeOnClickModal
:
false
,
closeOnPressEscape
:
false
,
callback
:
(
val, action
) =>
{
if
(val ===
"confirm"
) {
router.
back
();
}
},
});
}
})
}
}
这段代码中,如果换成
Elmessage
就不会报错
2.2 定位报错变量来源
buildProp
在
element-plus
chunk 中被大量使用,但并未在该文件内定义。查看文件头部导入:
// element-plus-CLZbZCGX.js 第1行
import
{ p
as
buildProp, q
as
definePropType, y
as
buildProps, ... }
from
"./vue-vendor-vAf0kzTJ.js"
;
buildProp
是从
vue-vendor
chunk 导入的。
2.3 发现反向依赖
继续检查
vue-vendor
的头部导入,发现一个关键的反向引用:
// vue-vendor-vAf0kzTJ.js 第2行
import
{ c
as
componentSizes }
from
"./element-plus-CLZbZCGX.js"
;
vue-vendor
竟然从
element-plus
导入了
componentSizes
!这形成了循环依赖:
vue-vendor ──
import
{ componentSizes }──▶ element-plus
▲ │
└───────
export
{ buildProp }──────────────┘
2.4 追问:vue-vendor 为什么会依赖 element-plus?
vue-vendor
中有一段看似"不该出现"的代码:
// vue-vendor-vAf0kzTJ.js 第8787行
const
isValidComponentSize
= (
val
) => [
""
, ...componentSizes].
includes
(val);
这段代码来自
element-plus/es/utils/vue/validator.mjs
,它引用了
componentSizes
(定义在
element-plus/es/constants/size.mjs
)。
为什么 Element Plus 的工具函数会跑到
vue-vendor
chunk 里?
答案就在分包配置中。
2.5 什么是循环依赖
循环依赖就是a引用的b的变量,b引用a的变量导致的问题,报错一般是
Cannot access 'buildProp' before initialization
这是es6基础语法const、let关键字报错,就是TDZ(暂时性区域)
console
.
log
(a);
//报错,TDZ
const
a =
1
;
而循环依赖为
// A.js
import
b
from
"./B.mjs"
;
// export const a = "Hello from A";
console
.
log
(b);
// 这里使用 B 导出的内容
const
a =
"Hello from A"
;
export
default
a;
//B.js
import
a
from
"./A.mjs"
;
// export const b = "Hello from B";
const
b =
"b"
;
export
default
b;
console
.
log
(a);
// 这里使用 A 导出的内容
三、根因分析
3.1 问题配置
vite.config.js
中的
manualChunks
配置如下:
manualChunks
:
(
id
) =>
{
if
(id.
includes
(
"node_modules"
)) {
// Vue 核心库
if
(
id.
includes
(
"vue"
) ||
id.
includes
(
"pinia"
) ||
id.
includes
(
"vue-router"
)
) {
return
"vue-vendor"
;
}
// Element Plus
if
(id.
includes
(
"element-plus"
)) {
return
"element-plus"
;
}
// ...
}
};
3.2 匹配顺序导致的误判
关键问题在于
id.includes("vue")
排在
id.includes("element-plus")
前面
。
Element Plus 的工具模块路径为
element-plus/es/utils/vue/
,这些路径同时包含
"vue"
和
"element-plus"
两个关键词。由于
includes("vue")
先被判断,以下模块全部被错误地归入了
vue-vendor
chunk:
模块路径
includes("vue")
includes("element-plus")
实际被分到
element-plus/es/utils/vue/props/runtime.mjs
✅
✅
vue-vendor
(先匹配到
"vue"
)
element-plus/es/utils/vue/validator.mjs
✅
✅
vue-vendor
(先匹配到
"vue"
)
element-plus/es/utils/vue/icon.mjs
✅
✅
vue-vendor
(先匹配到
"vue"
)
element-plus/es/utils/vue/install.mjs
✅
✅
vue-vendor
(先匹配到
"vue"
)
element-plus/es/constants/size.mjs
❌
✅
element-plus
element-plus/es/components/...
❌
✅
element-plus
3.3 循环依赖的形成
源码中的依赖关系:
validator.
mjs
→
import
{ componentSizes }
from
'../../constants/size.mjs'
打包后,由于
validator.mjs
被分到了
vue-vendor
,而
size.mjs
被分到了
element-plus
,就变成了:
vue-vendor chunk(含 validator.
mjs
) →
import
{ componentSizes }
from
element-plus chunk(含 size.
mjs
)
element-plus chunk(含组件代码) →
import
{ buildProp }
from
vue-vendor chunk(含 runtime.
mjs
)
循环依赖形成。
3.4 为什么开发环境不报错?
Vite 开发模式使用的是
原生 ESM 按需加载
,每个模块都是独立的文件,浏览器通过 HTTP 请求按需加载,模块之间的依赖关系由浏览器原生处理,不存在"chunk 合并"的概念,因此不会产生循环依赖问题。
而生产环境经过 Rollup 打包后,多个模块被合并到同一个 chunk 文件中,模块的执行顺序由 chunk 文件内的代码书写顺序决定。当循环依赖存在时,某个变量可能在被导入方使用时尚未执行到定义语句,触发 TDZ(暂时性死区)错误。
3.5 报错的执行时序
1.
浏览器开始加载 vue-vendor chunk
2.
vue-vendor 第
2
行:
import
{ componentSizes }
from
"element-plus"
→ 触发加载 element-plus chunk
3.
浏览器开始加载 element-plus chunk
4.
element-plus 第
1
行:
import
{ buildProp }
from
"vue-vendor"
→ vue-vendor 已在加载中,获取其未完成的导出
5.
element-plus 第
627
行:执行
const
_prop =
buildProp
({...}) → buildProp 尚未初始化!
❌
Uncaught
ReferenceError
:
Cannot
access
'buildProp'
before initialization
buildProp
定义在
vue-vendor
的第 8684 行,但
vue-vendor
在第 2 行就触发了
element-plus
的加载。此时
vue-vendor
还没执行到
buildProp
的定义处,
element-plus
拿到的是一个处于 TDZ 的
const
变量,访问即报错。
3.6 为什么Elmessage不报错?
因为使用了Elmessage,它不使用isValidComponentSize方法,不使用,构建打包的结果就是不会在vue-vendor当中有引入element-plus当中的componentSizes
整体逻辑为
//element-plus
import
{isValidComponentSize}
from
'vue-vendor'
const
componentSizes = [
""
,
"default"
,
"small"
,
"large"
];
.....
...
const
_sfc_main =
defineComponent
({
name
:
"ElMessageBox"
,
props
: {
buttonSize
: {
type
:
String
,
validator
: isValidComponentSize
},
}
})
//vue-vendor
import
{componentSizes}
from
'element-plus'
const
isValidComponentSize
= (
val
) => [
""
, ...componentSizes].
includes
(val);
因为在vendor当中完全不使用isValidComponentSize方法,所以将isValidComponentSize直接合并到element-plus文间当中最优,而element-plus/es/utils/vue/validator.mjs当中有isValidComponentSize,但因为带vue导致送到的vue-vendor。
四、修复方案
4.1 核心改动
element-plus
匹配提到
vue
前面
,防止
element-plus/es/utils/vue/
路径被
includes("vue")
抢走
vue
匹配更精确
,用
@vue/
或路径分隔符限定,避免宽泛匹配
4.2 修复代码
// vite.config.js
build
: {
rollupOptions
: {
output
: {
manualChunks
:
(
id
) =>
{
if
(id.
includes
(
"node_modules"
)) {
// Element Plus 必须优先匹配
// 避免 element-plus/es/utils/vue/ 被误分到 vue-vendor
if
(id.
includes
(
"element-plus"
)) {
return
"element-plus"
;
}
// Vue 核心库 — 精确匹配,避免误伤其他包含 "vue" 字符串的包
if
(
id.
includes
(
"@vue/"
) ||
id.
includes
(
"vue/dist"
) ||
id.
includes
(
"pinia/"
) ||
id.
includes
(
"vue-router/"
)
) {
return
"vue-vendor"
;
}
// ECharts
if
(id.
includes
(
"echarts"
)) {
return
"echarts"
;
}
// 工具库
if
(
id.
includes
(
"axios"
) ||
id.
includes
(
"lodash"
) ||
id.
includes
(
"dayjs"
)
) {
return
"utils"
;
}
// 图标库
if
(id.
includes
(
"@element-plus/icons"
)) {
return
"icons"
;
}
// 其他第三方库
return
"vendor"
;
}
},
},
},
},
4.3 修复后的依赖关系
vue-vendor chunk(仅含 vue/pinia/vue-router) ← 不再包含 element-plus 的任何模块
▲
│
element-plus chunk(含所有 element-plus 代码,包括 utils/vue/ 和 constants/)
循环依赖被打破,
buildProp
和
componentSizes
都在同一个
element-plus
chunk 内部,不再存在跨 chunk 的循环引用。
五、总结与反思
5.1 经验教训
要点
说明
includes()
是宽泛匹配
id.includes("vue")
会匹配到任何路径中包含
"vue"
的模块,包括
element-plus/es/utils/vue/
manualChunks
的匹配顺序很重要
先匹配的规则先生效,应将更具体的规则放在前面
开发正常 ≠ 生产正常
Vite 开发模式使用原生 ESM,不存在 chunk 合并;生产模式经过 Rollup 打包,模块合并后可能暴露循环依赖
循环依赖的 TDZ 错误具有隐蔽性
报错位置往往不是问题根源,需要沿导入链追溯
5.2 预防措施
分包规则应精确匹配
:使用路径分隔符(如
@vue/
、
/vue/dist
)代替宽泛的字符串包含
优先匹配更具体的包
:
element-plus
等包含子路径的包应排在通用规则之前
构建后验证
:可在 CI 流程中加入循环依赖检测,如使用
rollup-plugin-circular-dependencies
或
madge
关注构建警告
:Rollup 打包时如果存在循环依赖,通常会输出警告信息,不应忽略
后评
本文根据我询问ai,让ai阅读源码得以解决,当中包括读取到node_modules,不得不感叹ai的强大之处,阅读源码时效率非常高,能快速找到问题出处。
当然,从发现问题到解决问题,不是一次询问,写一次提示词就解决的,不过ai给定的报错存在循环依赖这个判断是没有问题的,但刚开始给的解决方法是有问题的
查看原文
🏷 标签: Vite, Vue3, Element Plus, 构建优化, 循环依赖