Yarn 安装耗时过长与偶发性 MODULE_NOT_FOUND
问题背景
最近,我的一个 PC 项目在线上 CI/CD 环境中遇到了两个棘手的问题:
- 构建速度极慢:本地构建(
build
)只需要不到 5 分钟,但在线上环境却需要近 20 分钟才能完成。 - 构建偶尔失败:流水线会不定时地因为
MODULE_NOT_FOUND
错误而中断,提示找不到一个名为boolbase
的模块。
问题分析和解决方案
为什么线上构建这么慢?
通过查阅线上构建日志,我发现了两个主要的耗时阶段:
-
依赖安装阶段 (
yarn install
):- 耗时:
Done in 526.82s
(约 8 分 47 秒)。 - 原因:日志中明确提示
无可用缓存
。这意味着 CI 流程没有命中任何缓存,每次都需要完整地重新下载、安装所有依赖,这是主要的时间瓶颈。 - 解决方案:排查并修复 CI 的缓存机制。
- 检查缓存 Key:确认 CI 脚本中生成缓存 Key 的逻辑是否稳定。Key 通常应该基于
yarn.lock
文件的哈希值生成。如果 Key 包含了构建号、时间戳等易变因素,会导致缓存永远无法命中。 - 检查缓存读写: 查看上一次构建的日志,确认“保存缓存 (Save Cache)”步骤是否成功执行。如果从未成功保存,自然也就无法恢复。
- 检查 CI Agent 的一致性:如果缓存是存储在构建服务器本地,需要确保后续构建任务被分配到同一台或同一组拥有该缓存的服务器上。
- 检查缓存 Key:确认 CI 脚本中生成缓存 Key 的逻辑是否稳定。Key 通常应该基于
- 耗时:
-
编译打包阶段 (
build
):- 耗时:
Done in 663.78s
(约 11 分 4 秒)。 - 原因:这个时间远超本地的 4 分多钟。主要是由于 CI 服务器的硬件性能(CPU、磁盘I/O)不如本地开发机,并且可能因为多任务并行导致资源紧张。
- 解决方案:虽然根本解决需要升级硬件,但修复好依赖安装的缓存后,这部分时间占比会显著下降,问题也就没那么突出了。
- 耗时:
为什么会偶尔 MODULE_NOT_FOUND
?
这个错误的核心是“偶尔”发生,这通常指向了依赖管理的不确定性。
-
错误信息:
Error: Cannot find module 'boolbase'
。 -
错误来源:通过
requireStack
(调用栈) 可以看到,boolbase
并非项目的直接依赖,而是被深层嵌套的nth-check
包所依赖。 -
核心原因:Yarn v1 的依赖提升 (Hoisting) 机制不确定性。在复杂的 monorepo 项目中(本项目使用了
workspaces
),Yarn v1 的提升算法可能导致同一个间接依赖在不同构建中被放置在node_modules
树的不同位置。偶尔,boolbase
模块没有被提升到nth-check
能够找到它的路径上,从而引发了该错误。 -
解决方案:
-
短期修复:既然是
nth-check
找不到boolbase
,那就想办法让 Yarn 每次都把nth-check
和它的依赖稳定地安装好。在package.json
中显式声明这个间接依赖,可以给 Yarn 一个更强的“提示”,使其 Hoisting 结果更稳定。- 在本地找到
nth-check
的正确版本:1yarn list nth-check
- 在对应的
workspace
中显式安装该版本:1yarn workspace centralWms add nth-check@<版本号> -D
- 在本地找到
-
长期方案:Yarn v1 的 Hoisting 问题是其固有缺陷。为了彻底解决这类问题,最好迁移到更现代的包管理器,如 pnpm。pnpm 使用符号链接的方式组织
node_modules
,依赖结构清晰、确定,从根本上解决了 Hoisting 不确定的问题。
-
总结
经过排查,两个看似独立的问题都指向了 Yarn v1 在 CI 环境下的依赖管理缺陷。
- 构建慢:主要是由于 CI 缓存失效 导致
yarn install
耗时过长。 - 偶发性失败:由 Yarn v1 的 Hoisting 不确定性 导致深层依赖偶尔找不到。
通过修复 CI 缓存策略和在 package.json
中显式声明间接依赖,可以快速解决当前问题。但为了项目的长期稳定和更高的构建效率,最好将项目从 Yarn v1 迁移到 pnpm。