跳转至

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, 构建优化, 循环依赖