NPM 和 YARN 是两个不同的包管理系统, 其中 NPM 生成 package-lock.json, YARN 生成 yarn.lock, 这两个文件记录当前项目所依赖的各个包的版本。
最安全的做法是在每次依赖关系发生变化时生成并提交它们。 但是, 这可能很麻烦, 或者两个文件可能不同步。所以现在的一般做法是只保留其中一个文件, 忽略另外一个文件, 这取决于当前使用的包管理系统。
推荐使用 YARN, 即保留 yarn.lock, 而且每次变动需要提交该文件。
官方对 yarn.lock
文件的说明如下:
为了跨机器安装得到一致的结果, Yarn 需要比你配置在 package.json 中的依赖列表更多的信息。 Yarn 需要准确存储每个安装的依赖是哪个版本。
为了做到这样, Yarn 使用一个你项目根目录里的 yarn.lock 文件。这可以媲美其他像 Bundler 或 Cargo 这样的包管理器的 lockfiles。它类似于 npm 的 npm-shrinkwrap.json, 然而他并不是有损的并且它能创建可重现的结果。
需要注意的是: 所有 yarn.lock
文件应该被提交到版本控制系统。
还原一下我出过的 Case 项目里原本有个依赖 foo:
foo@^1.0.1
1.0.1
同学 A 是负责 foo 这个库的开发, 一次发版后, 到项目里升级这个依赖到 1.1.0, 但是提交代码时, 只变更了 package.json, 没有更新 yarn.lock
foo@^1.0.1``foo@^1.1.0
1.0.1
然后大家每次拉新代码并安装依赖后, 本地总有个烦人的 yarn.lock 文件变更, 大家心想应该是有人升级依赖的时候忘记提交 yarn.lock 了于是同学 B 行动了:
1.1.2
, 跟 package.json 里定义的 ^1.1.0
差了两个版本, 不能保证线上是 1.1.0
, 因为每次上线, 都会去找符合 ^1.1.0
这个 version range 里的最新版本1.1.2
1.1.2
然后过了一天, 拉群了
1.1.2
版本有 bug, 修复后发布了 1.1.3
1.1.2
, 锁住了 bug锁定唯一版本!
^1.0.0
1.0.0
里面都是一块一块的, 每一块大概长下面这样:
core-js-compat@^3.0.0:
version "3.14.0"
resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.14.0.tgz#b574dabf29184681d5b16357bd33d104df3d29a5"
integrity sha1-tXTavykYRoHVsWNXvTPRBN89KaU=
dependencies:
browserslist "^4.16.6"
semver "7.0.0"
第一行的 core-js-compat@^3.0.0
是依赖的 identifier。和 package.json 里对应的包名和版本区间, 用 @
连接。这边的标题里带了 (s)
, 是因为多个 Identifier 最终可能都指向同一个版本 (具体例子可以看下文 ### dependencies
里给出的例子)
第二行 version 是实际安装的版本。通常是满足版本区间里的一个版本, 比如上一行 identifier 里版本区间是 ^3.0.0, 这里实际安装的是 3.14.0, 符合要求。但是为什么要说是 “通常” 呢, 因为有例外, 在后文 ### resolutions 部分会讲到。
第三行 resolved 的是一个链接, 是下载这个包的地址。这个 url 里的域名部分跟项目里配置的 .npmrc 或你本地的 npm 配置的 registry 有关。
第四行 integrity 是对 resolved 下载下来的文件进行完整性校验。如果出现 diff, 说明同一个下载链接对应的文件被修改过。
第五行 dependencies 是这个包自己的依赖。如这里依赖的 browserslist "^4.16.6"
, 你想看下实际安装的哪个版本, 就可以把它拼成 Identifierbrowserslist@^4.16.6"
, 以此为关键字在 yarn.lock 中搜索, 就能找到对应的 “块” 了。
browserslist@4.16.6, browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.6, browserslist@^4.3.6, browserslist@^4.6.2, browserslist@^4.6.4, browserslist@^4.7.2, browserslist@^4.9.1:
version "4.16.6"
resolved "https://https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2"
integrity sha1-15ASd6WojlVO0wWxg+ybDAj2b6I=
dependencies:
caniuse-lite "^1.0.30001219"
colorette "^1.2.2"
electron-to-chromium "^1.3.723"
escalade "^3.1.1"
node-releases "^1.1.71"
上面这个例子第一行有多个 Identifiers, 最终都指向第二行的 version "4.16.6"
, 可以检查下 4.16.6
版本满足上面所有 Identifiers 里的版本区间: 4.16.6
、^4.0.0
…
yarn.lock 是自动生成的, 你不应该去手动的修改。
比如我们的常规操作, 都会自动更新 package.json 和 yarn.lock
yarn add
yarn upgrade
更多可参考: https://classic.yarnpkg.com/en/docs/managing-dependencies
假如你的项目依赖了 foo,foo 依赖了 bar@^1.0.0
。假设 bar 现在有两个版本 1.0.0 和 1.1.0。很不幸, bar 在发布 1.1.0 的时候没有做好向后兼容。导致 foo 和 [email protected]
不能搭配使用。如果你可以等:
那如果你等不了呢, 你已知 foo 和 [email protected]
可以正常工作。如果你能锁住 foo 对 bar 的依赖就好了, 但是这定义在 foo 的 packge.json 里, 你总不能去改 node_modules/foo/package.json
吧? 这不合适。resolutions 可以解决你的问题, 只要在你自己项目的 package.json 里定义:
"resolutions": {
"foo/bar": "1.0.0"
}
这里的 key"foo/bar" 表示 foo 的直接依赖 bar, 把版本区间重写成 1.0.0。如果 foo 不是直接依赖的 bar(foo -> ... -> bar)
, 我还需要把中间的链路都捋清楚吗? 不用那么麻烦!
"resolutions": {
"foo/**/bar": "1.0.0"
}
如果你的项目里有很多依赖直接 /
间接的依赖了 bar, 每个定义的版本区间可能有差别, 你知道某个版本可以让他们都能正常工作, 而不用安装多个版本。也可以不用声明前缀部分, 只写包名 bar。这样不管是哪里依赖到了 bar 都会指向你声明的哪个版本。
"resolutions": {
"bar": "1.0.0"
}
执行 yarn install 后, 在 yarn.lock 里搜索 bar@
:
bar@^1.0.0 [email protected] bar@^2.0.0:
version "1.0.0"
...
可以看到, resolutions 可以违背版本区间的限制, 比如上例中 Identifiers 里的 [email protected]``bar@^2.0.0
。
只改动 package.json, 忘记提交 yarn.lock
执行 yarn install 后, yarn.lock 有变更
可以看到出现问题再解决还是很棘手的, 而且有一定赌的成分, 所以我们最好预防。
即使现在项目是好的, 我们是不是也应该防患于未然!
开发的同学 &&CR 共同把关
阻塞构建 (有以下几种方案可选)
yarn install --frozen-lockfile
等价于 npm ci, 但是在测试过程中发现几个问题:
✅yarn install && git diff--exit-codeyarn.lock
正常执行 install 命令安装依赖, 再检查 lock file 有无 diff
当你更新了某个依赖后, 发现项目跑不起来了, 推测可能是依赖的问题。有没有尝试过把 yarn.lock + node_modules 都删了重新安装, 幸运的话可能 “问题就解决了”(解决了, 但是好像没完全解决, 反正项目跑起来了)
把 yarn.lock 删掉后, 原本锁住的版本都放开了, 执行 yarn install 的时候会根据 package.json 里定义的版本区间去找最新版。所以, 可能会造成你预期外的依赖也被更新了, 不幸的话可能会引入 bug。
可以单独搞一个依赖 empty-lock-lock:
在项目中安装依赖 yarn add [email protected] --dev
, yarn.lock 里会锁定版本为 1.0.0。然后准备一个陷阱:
^1.0.0
[email protected]
也替换成 empty-lock-lock@^1.0.0
修改后提交, 可以再执行 yarn install 验证下, yarn.lock 没有 diff, 证明我们手动修改后的 package.json 和 yarn.lock 仍然是 match 的。等小白鼠上钩, 如果把 yarn.lock 整个删掉了, 再执行 yarn install, 安装到 empty-lock-lock 的时候, 会根据 package.json 里定义的 ^1.0.0 版本区间里找最新的, 这时候会找到 1.0.1 版本, 下载后触发 postinstall 就报错啦!