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

参考资料

相关推荐
VaJoy1 小时前
Cocos Creator Shader 入门 (21) —— 高斯模糊的高性能实现
前端·cocos creator
前端加油站1 小时前
使劲折腾Element Plus的Table组件
前端·javascript·vue.js
ze_juejin1 小时前
Angular的Service创建多个实例的总结
前端
十五喵1 小时前
智慧物业|物业管理|基于SprinBoot+vue的智慧物业管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·毕设·智慧物业管理系统
特级业务专家1 小时前
React vs Vue 调度机制深度剖析:从源码到事件循环的完整解读
前端
ze_juejin1 小时前
Angular中懒加载模块的加载顺序总结
前端
天蓝色的鱼鱼1 小时前
写Tailwind CSS像在写屎山?这锅该不该它背
前端·css
#做一个清醒的人1 小时前
【Electron】IpcMainEvent 参数使用总结
前端·electron
月弦笙音2 小时前
【包管理器】pnpm、npm、cnpm、yarn 深度对比
前端