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. 版本升级:工具升级可能改变默认行为,需要充分测试

参考资料

相关推荐
海石4 小时前
📱随时随地大小编:TraeSolo 移动端初体验
前端·ai编程·trae
爱滑雪的码农6 小时前
详细说说React大型项目结构以及日常开发核心语法
前端·javascript·react.js
七牛开发者6 小时前
HTML is the new Markdown:来自 Claude Code 团队的实践
前端·人工智能·语言模型·html
@大迁世界7 小时前
43.HTML 事件处理和 React 事件处理有什么区别?
前端·javascript·react.js·html·ecmascript
CloneCello7 小时前
AI时代程序员认知调整指南
前端
ZC跨境爬虫7 小时前
跟着 MDN 学 HTML day_38:(DocumentFragment 文档片段接口详解)
前端·javascript·ui·html·音视频
@大迁世界8 小时前
41.ShadCN 是什么?它如何和 Tailwind CSS 集成,从而更容易构建可访问且可自定义的 React 组件?
前端·javascript·css·react.js·前端框架
千叶风行9 小时前
Text-to-SQL 技术设计与注意事项
前端·人工智能·后端
软件开发技术深度爱好者9 小时前
HTML5+JavaScript读取DOCX 文档完整内容
前端·html5
幽络源小助理9 小时前
苹果CMS V10 MXPro V4.5模版下载, 自适应视频主题源码, 幽络源源码
前端·开源·源码·php源码