背景
近期,我开发的钉钉小程序有一个线上故障,表现为用户进入活动页无法生成海报,排查后发现开源项目 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 包管理器用来锁定项目依赖版本的重要文件,以下是它的必要性:
- 确保一致性:
-
yarn.lock
文件锁定了项目中所有依赖的具体版本,确保了在不同开发者、不同环境(如开发环境、测试环境、生产环境)中安装的依赖是完全相同的,避免了因环境差异导致的依赖版本不一致问题。
- 提高性能:
-
- 由于
yarn.lock
文件的存在,Yarn 可以跳过重复的依赖树解析过程,直接根据yarn.lock
文件中的信息安装依赖,这可以显著提高安装速度。
- 由于
- 避免版本冲突:
-
- 在多人协作的项目中,不同的开发者可能会安装不同版本的依赖,
yarn.lock
文件确保了所有开发者使用的依赖版本一致,避免了版本冲突。
- 在多人协作的项目中,不同的开发者可能会安装不同版本的依赖,
- 简化版本管理:
-
- 当依赖库更新时,
yarn.lock
文件可以帮助开发者明确哪些依赖被更新了,以及更新的具体版本,方便版本控制和回溯。
- 当依赖库更新时,
- 支持离线安装:
-
- 由于
yarn.lock
文件的存在,Yarn 可以缓存已经下载的依赖包,这样即使在没有网络连接的情况下,也可以通过缓存来安装依赖。
- 由于
- 优化网络使用:
-
- Yarn 会根据
yarn.lock
文件中的信息,只下载必要的依赖更新,减少了不必要的网络请求和数据传输。
- Yarn 会根据
- 支持确定性构建:
-
- 在持续集成/持续部署(CI/CD)环境中,
yarn.lock
文件确保了构建过程的确定性,即在不同的构建机器上,只要yarn.lock
文件相同,构建结果就会相同。
- 在持续集成/持续部署(CI/CD)环境中,
- 便于问题追踪:
-
- 当项目出现问题时,
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
包的依赖项及其版本要求。
故障后 我们做了如下修改:
- 将 package.json 中的 qrcode 改成固定版本,即从 ^1.5.3 改成 1.5.3
- 将 qrcode@1.5.3 添加到 package.json 文件的 resolutions 字段中,确保项目中所有需要 qrcode 的地方都安装 1.5.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。