Vite 构建中的两个典型问题:代码分割命名与循环依赖

前言

在使用 Vite + Vue 3 构建项目时,我们遇到了两个典型的构建问题。

问题一:共享组件被错误命名为第一个入口点

问题描述

在生产环境构建后,发现 Organization 组件被打包成了 VisitList.v-10eaf360b4.js,但实际上这个文件包含的是 Organization 组件的代码。这导致运行时出现错误:

arduino 复制代码
Uncaught (in promise) SyntaxError: The requested module './VisitList.v-10eaf360b4.js' does not provide an export named 'O'

这里不得不吐槽,因为一些原因,我司CDN上没有不允许出现这种同名文件,哪怕哈希值不一样也不行,会默认读第一个文件,其实就算被打包出来多个文件,哈希值不一样的话,正常是不会受影响的。

问题分析

Rollup 的代码分割策略

当使用函数形式的 manualChunks 配置时,Rollup 会根据以下规则进行代码分割:

  1. 共享模块被多个入口点使用:如果共享模块被 3 个或更多入口点使用,Rollup 会创建一个独立的 chunk
  2. 共享模块只被 2 个动态导入使用:如果共享模块只被 2 个动态导入的路由组件使用,Rollup 会将其合并到第一个入口点的 chunk 中,并使用第一个入口点的名称

具体场景

在我们的项目中:

  • Organization 组件位于 src/components/Organization/Organization.jsx
  • VisitListBindClientList 两个动态导入的路由组件使用
  • Rollup 将其命名为 VisitList.v-xxx.js(第一个入口点的名称)

为什么其他组件没问题?

对比其他组件:

  • DateSelect:被 4 个组件使用(包括 1 个静态导入),正确命名为 DateSelect
  • LocationSheet:被 6 个动态导入的路由组件使用,正确命名为 LocationSheet

这说明 Rollup 的默认行为是:只有当共享模块只被 2 个动态导入使用时,才会使用第一个入口点的名称

解决方案

通过显式指定共享组件的 chunk 名称来解决:

javascript 复制代码
manualChunks(id) {
  // 优先处理 src/components/ 下的共享组件
  // 避免只被 2 个动态导入路由组件使用的共享组件被错误命名为第一个入口点名称
  if (id.includes('/src/components/') || id.includes('\\src\\components\\')) {
    // 提取组件名称(例如:src/components/Organization/Organization.jsx -> Organization)
    // 匹配模式:components/ComponentName/ComponentName.jsx
    const match = id.match(/[/\\]components[/\\]([^/\\]+)[/\\]\1\.(jsx?|vue)$/);
    if (match) {
      return match[1]; // 返回组件名称作为 chunk 名称
    }
  }
  
  // ... 其他配置
}

问题二:循环依赖导致的组件解析失败

问题描述

在生产环境运行时,应该跳转页面,但是在加载组件时报错:

javascript 复制代码
SurveyPopup.jsx:26 TypeError: Cannot read properties of undefined (reading '__vccOpts')
at vue-router.mjs:2215:55

错误发生在 router.push 尝试加载 SurveyImgSurveyVideo 路由组件时。

问题分析

查看代码结构,代码中形成了循环依赖链

为什么构建时没报错?

  1. 开发环境:Vite 的 HMR 机制可能掩盖了这个问题
  2. 构建过程:Rollup 在构建时可能没有完全解析这个循环依赖
  3. 运行时:当 Vue Router 尝试动态加载组件时,由于循环依赖导致组件解析失败

__vccOpts 错误的含义

__vccOpts 是 Vue 3 编译后的组件选项的内部属性。当组件解析失败时,resolvedComponentundefined,访问 undefined.__vccOpts 就会报错。

解决方案

将常量提取到单独的文件中,打破循环依赖

为什么之前没问题?

  1. 构建配置变更 :函数形式的 manualChunks 改变了 Rollup 的代码分割行为,对循环依赖的处理更加严格
  2. Vite 版本升级:新版本的 Vite/Rollup 对循环依赖的检测和处理更加严格

最佳实践建议

1. 避免循环依赖

  • 分离常量和组件:将常量、工具函数等提取到独立文件
  • 使用 barrel exports 时注意index.js 只用于导出,不要包含业务逻辑
  • 检查工具 :可以使用工具检测循环依赖,如 madgedependency-cruiser

2. 显式控制代码分割

  • 使用 manualChunks:对于重要的共享组件,显式指定 chunk 名称
  • 监控构建产物:定期检查构建后的 chunk 文件,确保命名正确
  • 文档化:记录哪些组件需要特殊处理

3. 升级时的注意事项

  • 测试构建产物:升级构建工具后,务必测试生产环境构建
  • 检查变更日志:关注构建工具的变更日志,了解行为变化
  • 渐进式升级:不要一次性升级多个依赖

总结

这两个问题都反映了现代前端构建工具的复杂性:

  1. 代码分割策略:需要理解 Rollup/Vite 的默认行为,并在必要时显式控制
  2. 循环依赖:虽然构建工具可能不会报错,但会在运行时导致问题
  3. 版本升级:工具升级可能改变默认行为,需要充分测试

参考资料

相关推荐
ZC跨境爬虫2 分钟前
3D 地球卫星轨道可视化平台开发 Day5(简介接口对接+规划AI自动化卫星数据生成工作流)
前端·人工智能·3d·ai·自动化
毛骗导演2 分钟前
Claude Code Agent 实现原理深度剖析
前端·架构
星晨雪海6 分钟前
若依框架原有页面功能进行了点位管理模块完整改造(3)
开发语言·前端·javascript
morethanilove21 分钟前
新建vue3 + ts +vite 项目
前端·javascript·vue.js
GISer_Jing22 分钟前
微软AI战略全景:从基础设施到智能体生态
前端·人工智能·microsoft
发际线向北24 分钟前
0x03 单元测试与Junit
前端·单元测试
忆往wu前24 分钟前
搞懂 SPA 再学路由!Vue Router 从0到完善 + 嵌套路由一次性梳理
前端·vue.js
Aliex_git25 分钟前
前端监控笔记(三)
前端·笔记·学习
M ? A27 分钟前
Vue Suspense 组件在 React 中,VuReact 会如何实现?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
天才熊猫君31 分钟前
通用 Loading 状态管理器
前端·javascript·vue.js