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

参考资料

相关推荐
cipher2 分钟前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
UrbanJazzerati4 分钟前
非常友好的Vue 3 生命周期详解
前端·面试
AAA阿giao6 分钟前
从零构建一个现代登录页:深入解析 Tailwind CSS + Vite + Lucide React 的完整技术栈
前端·css·react.js
兆子龙1 小时前
像 React Hook 一样「自动触发」:用 Git Hook 拦住忘删的测试代码与其它翻车现场
前端·架构
兆子龙2 小时前
用 Auto.js 实现挂机脚本:从找图点击到循环自动化
前端·架构
SuperEugene2 小时前
表单最佳实践:从 v-model 到自定义表单组件(含校验)
前端·javascript·vue.js
昨晚我输给了一辆AE862 小时前
为什么现在不推荐使用 React.FC 了?
前端·react.js·typescript
不会敲代码12 小时前
深入浅出 React 闭包陷阱:从现象到原理
前端·react.js
不会敲代码12 小时前
React性能优化:深入理解useMemo和useCallback
前端·javascript·react.js
Dilettante2582 小时前
我的 Monorepo 实践经验:从基础概念到最佳实践
前端·前端工程化