在一次线上故障后学习 yarn.lock

308 阅读5分钟

背景

近期,我开发的钉钉小程序有一个线上故障,表现为用户进入活动页无法生成海报,排查后发现开源项目 qrcode@1.5.4 没有定义 TextEncoder 对象,绘制海报中的二维码时 js 有报错——ReferenceError:“TextEncoder”未定义。

确定了故障点,再来追溯故障引入源。头一天钉钉小程序有发版,这次发版隐性的将 qrcode 从 1.5.3 更新到 1.5.4,这一变化体现在 yarn.lock 文件里。询问同事,我们最终确定他使用了错误的方式去解决 yarn.lock 文件的冲突,即:删除原来的 yarn.lock,再运行 yarn 命令重新生成 yarn.lock。

解决 yarn.lock 的冲突

我们的 yarn.lock 有 1 万 6 千多行代码,当发生冲突时,逐个解决冲突实在麻烦,一个简便的方式是,用目标分支的内容覆盖冲突,再运行 yarn 命令更新 yarn.lock,最后检查 yarn.lock 符合预期。比如:现在需要将 feature 合入 master ,合并时出现冲突,我们在 feature 上解决冲突,简便的方式是,用 master 上的 yarn.lock 覆盖 feature 上的 yarn.lock,然后运行 yarn 命令。

下面介绍 yarn.lock 的作用及其字段含义帮助大家理解为什么不能用删除 yarn.lock 的方式去解决代码冲突。

npm 包版本号规则

npm 采用语义版本规范来管理代码包的版本,其版本号的基本格式为:主版本号.次版本号.修订号,比如:1.0.2。

  • 主版本号:当你做了不兼容的 API 修改时,增加主版本号。
  • 次版本号:当你添加了向下兼容的功能时,增加次版本号。
  • 修订号:当你做了向下兼容的问题修正时,增加修订号。

在 package.json 文件中除了固定版本,还有版本范围匹配符,比如 ~ 和 ^。

  • ^主版本.次版本.修订号:安装主版本号不变,次版本号和修订号更新的版本。比如 ^1.2.3,npm 最终安装的版本 >= 1.2.3 < 2.0.0
  • ~主版本.次版本.修订号:安装主版本号和次版本号不变,修订号更新的版本。比如 ~1.2.3,npm 最终安装的版本 >= 1.2.3 < 1.3.0。

yarn.lock 的必要性

yarn.lock 文件是 Yarn 包管理器用来锁定项目依赖版本的重要文件,以下是它的必要性:

  1. 确保一致性
    • yarn.lock 文件锁定了项目中所有依赖的具体版本,确保了在不同开发者、不同环境(如开发环境、测试环境、生产环境)中安装的依赖是完全相同的,避免了因环境差异导致的依赖版本不一致问题。
  1. 提高性能
    • 由于 yarn.lock 文件的存在,Yarn 可以跳过重复的依赖树解析过程,直接根据 yarn.lock 文件中的信息安装依赖,这可以显著提高安装速度。
  1. 避免版本冲突
    • 在多人协作的项目中,不同的开发者可能会安装不同版本的依赖,yarn.lock 文件确保了所有开发者使用的依赖版本一致,避免了版本冲突。
  1. 简化版本管理
    • 当依赖库更新时,yarn.lock 文件可以帮助开发者明确哪些依赖被更新了,以及更新的具体版本,方便版本控制和回溯。
  1. 支持离线安装
    • 由于 yarn.lock 文件的存在,Yarn 可以缓存已经下载的依赖包,这样即使在没有网络连接的情况下,也可以通过缓存来安装依赖。
  1. 优化网络使用
    • Yarn 会根据 yarn.lock 文件中的信息,只下载必要的依赖更新,减少了不必要的网络请求和数据传输。
  1. 支持确定性构建
    • 在持续集成/持续部署(CI/CD)环境中,yarn.lock 文件确保了构建过程的确定性,即在不同的构建机器上,只要 yarn.lock 文件相同,构建结果就会相同。
  1. 便于问题追踪
    • 当项目出现问题时,yarn.lock 文件可以帮助开发者快速定位到具体的问题依赖版本,便于问题的追踪和修复。

提示:npm 包版本号规则和 yarn.lock 的必要性涉及的内容源于 kimi。

分析 yarn.lock 内容

故障前 qrcode 在 yarn.lock 中的版本信息和依赖关系,如下:

  • qrcode@^1.5.3, qrcode@^1.4.4:表明本项目对 qrcode 有两个版本要求,qrcode@^1.5.3 写在钉钉小程序项目的package.json 中,qrcode@^1.4.4 是 dingtalk-miniapp-opensdk 的依赖。
  • version "1.5.3":yarn 最终下载的版本是 1.5.3,该版本符合 ^1.5.3 和 ^1.4.4 的版本匹配要求。
  • resolved "registry.npmmirror.com/qrcode/-/qr…:qrcode 包 1.5.3 版本的下载地址。
  • integrity sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==:qrcode@1.5.3 的完整性校验和,用于验证下载的文件是否完整且未被篡改
  • dependencies:qrcode 包的依赖项及其版本要求。

故障时 qrcode 在 yarn.lock 中的版本信息和依赖关系,如下:

  • qrcode@^1.5.3, qrcode@^1.4.4:表明本项目对 qrcode 有两个版本要求,qrcode@^1.5.3 写在钉钉小程序项目的package.json 中,qrcode@^1.4.4 是 dingtalk-miniapp-opensdk 的依赖。
  • version "1.5.4":yarn 最终下载的版本是 1.5.4,这是发生线上故障前夕 qrcode 的最新版本,1.5.4 能满足 ^1.5.3 和 ^1.4.4 的版本要求
  • resolved "registry.yarnpkg.com/qrcode/-/qr…:qrcode 包 1.5.4 版本的下载地址。
  • integrity sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==:qrcode@1.5.4 的完整性校验和,用于验证下载的文件是否完整且未被篡改
  • dependencies:qrcode 包的依赖项及其版本要求。

故障后 我们做了如下修改:

  1. 将 package.json 中的 qrcode 改成固定版本,即从 ^1.5.3 改成 1.5.3
  2. 将 qrcode@1.5.3 添加到 package.json 文件的 resolutions 字段中,确保项目中所有需要 qrcode 的地方都安装 1.5.3,包括间接依赖和直接依赖。
  3. 修改 yarn.lock 的内容,使最终下载的 qrcode 版本为 1.5.3。

总结

以 qrcode 为例,如果 yarn.lock 中已经存在 qrcode 的版本记录,除非项目开发者采取主动措施更新 yarn.lock,那么 yarn 将始终下载 yarn.lock 标记的 qrcode 版本,即 1.5.3。如果 yarn.lock 不存在 qrcode 的版本记录,那么 yarn 将下载满足 package.json 版本要求的最新版本 。

我们的钉钉小程序 1 年前引入了 qrcode,当时 qrcode 的最新版本为 1.5.3,^1.5.3 能匹配到的最新版本是 1.5.3,该版本号被写入 yarn.lock 中,之后的每一次 yarn 都下载 qrcode@1.5.3。半年前,qrcode 发布了 1.5.4,当 yarn.lock 不存在时,package.json 对 qrcode 的版本要求为 ^1.5.3 的情况下,yarn 将下载 qrcode@1.5.4,并将版本号写入 yarn.lock。