什么是lock file
lock file文件描述了整颗依赖树,包含了特定版本的传递依赖(依赖嵌套)关系。npm中用的是package-lock.json,yarn中用的是yarn.lock。
package-lock.json如下所示:
yarn.lock如下所示:
lockfile包含的关键信息包括
- 安装每个依赖的实际版本
- 每个依赖的依赖(即传递依赖)
- 包的校验和(以验证包的完整性)
使用lockfile的方法如下:
npm ci # will install exactly what's in the package-lock.json
yarn install --frozen-lock-file # will install exactly what's in yarn.lock without updating it
什么时候使用lockfile
当我们在构建一个web应用时,建议使用上述的命令,来执行构建和发布。包括在CI中使用上述命令。这样我们可以确保每个开发验证人员、构建系统/CI系统使用完全相同的依赖项。
因此,在yarn和npm的文档里都有建议,在提交代码的时候,把lockfile也提交进GIT仓作为代码的一部分。
这也有利于可重复构建。对外交付时,任何时候都可以通过相同的代码构建出一致的包,客户也易于理解与验收。
什么时候不使用lockfile
当我们代码的构建结果是一个中间依赖,即被其他项目依赖时,不建议使用。换言之,lockfile应该进入顶层项目(最终用户消费的程序)的源码版本控制。
为什么被依赖件的源码仓不建议使用lock file呢?
主要原因在于npm仓库上发布的包本身。即,你发布到npm的内容并不总是与git仓上的内容相同。
npm使用npm pack命令将需要发布的文件打成一个tarball,你可以尝试使用如下命令来查看npm打包了哪些文件
npm pack --dry-run
也可以在 npm的doc 查看打包的完整文件列表,摘录其打包的内容如下
Certain files are always included, regardless of settings:
package.json
README
CHANGES
/CHANGELOG
/HISTORY
LICENSE
/LICENCE
NOTICE
- The file in the "main" field
README
,CHANGES
,LICENSE
&NOTICE
can have any case and extension.Conversely, some files are always ignored:
.git
CVS
.svn
.hg
.lock-wscript
.wafpickle-N
.*.swp
.DS_Store
._*
npm-debug.log
.npmrc
node_modules
config.gypi
*.orig
package-lock.json
(usenpm-shrinkwrap.json
if you wish it to be published)
可以看到,npm只打包了package.json,而没有打包package-lock.json。
这种情况下,当别人的工程依赖你的npm包的时候,无法下载到你的工程的package-lock.json,导致实际依赖的你的包的传递依赖(即你所依赖的包)的版本不一定与你发布时的一致。
所以,作为一个被别人依赖的模块,关键在于让自己“靠谱”一些,而这点并不依靠lock文件。建议的“靠谱”方法包括:
- 严格遵循 semver 语义化版本的原则来进行版本发布。对不遵循 semver 发布的模块敬而远之。
- 选择依赖时,尽量选择npm 上有较大的下载量的,当遇到模块问题时,波及范围越广,其修复的速度越快。
- 开源模块在github 上的问题反馈迅速,或者是由一些知名开发者维护。质量有一定的保障
- 版本号中,patch 位变更的发布不多(说明 bug fix 不多)。
有的人会认为,即使如此,仍然应该把package-lock文件归档到git仓,哪怕它实际没有被使用。毕竟要使用lockfile都是单独的命令,如果不使用的话,归档也没什么影响。但是,事实真的如此吗?
首先,npm包管理是使用的semver 语义化版本的机制来帮助开发者管理依赖,开发者可以在 package.json中通过 ^1.1.0 或者 ~1.0.0 的方式来引入模块,如果开发者信任他们依赖的模块,开发者可以通过 ^ 来锁定一个模块的大版本,这样在每次重新安装依赖或者打包的时候,都能够享受到这个包所有的新增功能和 bug 修复。而这个模块如果遵循 semver 原则,也不用担心它会引入一些不兼容变更导致项目出现一些未知异常。最终开发者需要关心的其实只有直接依赖的这些模块是否足够靠谱。 这样一来,每个模块对自身的依赖负责,一个项目虽然只直接依赖了十来个模块,但其最终却间接的依赖了上千个模块。真正想要通过package-lock.json去管理好这一份多达上千个模块的模块是非常困难的。(一个关于lockfile成为安全侵入的入口的例子见这里https://snyk.io/blog/why-npm-lockfiles-can-be-a-security-blindspot-for-injecting-malicious-modules/,例子中,正是由于lockfile动辄几百上千行的修改,对commit审核造成了很大的压力,而通常一个commit也就几十行的修改。)
其次,在有lockfile文件的情况下,npm install的真正行为情况如下:
1、npm 5.0.x 版本,不管package.json怎么变,npm install 时都会根据lock文件下载
2、5.1.0 - 5.4.2版本 npm install 会无视lock文件,去下载符合规则的最新的包。
3、5.4.2版本后,如果改了package.json,且package.json和lock文件不同,那么执行npm install时npm会根据package.json中的版本号去下载最新的包,并更新lock文件。如果没有更新package.json,那么npm install会根据lock文件下载,而不会理会package.json中实际的包的版本是否有更新。
换句话说,当存在lockfile的情况下,即使有新的补丁包存在,都可能不会被使用 。而这点显然不是我们希望的
最后,重申一遍,lockfile应该进入顶层项目(最终用户消费的程序)的源码版本控制,中间模块不建议使用lockfile。
P.S:有人会认为开源社区大多用了lock。其实仔细看github上的提交就会发现,lock文件很多是不允许个人提交的,lock文件的更新大多由机器人自动更新,也就是说,这里的lock文件,是作为一个CI快照更新上库的。同样的,也有不少高星的开源社区,例如ESlint,库上没有lock文件。