首先说npm,从上学到工作,写代码这些年来,接触过java、c#、js(nodejs)、python、c++等多种语言,这些语言依赖包管理各式各样,至今感到最丝滑的还是nodejs的包管理机制。

nodejs的包管理工具就是大家最常用的npm了,npm本身是依托于nodejs运行的,而安装nodejs的时候一般也会附带npm工具,nodejs版本和附带npm版本的列表见:nodejs历代版本。为什么要提及版本呢?

本文的主人公,在历代版本中的变化是神鬼莫测,各种两头不讨好,活像了各位开发哥被产品魔改需求的样子,也像我们改bug时堵东漏西的样子…

版本锁定——设计对工程的妥协

npm的包管理使用起来确实是简单方便,在nodejs社区蓬勃发展的过程中,海量的模块被发布到了npm仓库中,大家写代码的时候各种轮子需要自己造的越来越少,大部分都可以在npmjs.com上搜索,找个最popular的拿来用。

时间长了,大家发现,一个项目中手撸的代码没多少,依赖的模块倒是一堆一堆的,这些模块五花八门,维护者来自五湖四海,靠谱的、不靠谱的都有。 某几次下载代码npm install,发现跑不起来,经过一番排查,发现不是自己手撸代码的BUG,而是某个依赖包出BUG了,于是怒上心头,开始问候作者。其实呢,这种情况也分两种原因:

现实中这两种情况都会有,前者是开发者自己的问题,比如不管三七二十一给第三方模块版本依赖写了个latest,然后别人发布了个更高大上的不兼容新版本,开发者的代码却跪了。

后面一种情况,就是模块开发者的问题了,有可能是发布了一个兼容版本,但是引入了BUG;或者是模块依赖别的模块时没有遵循semver规范,即 开发者A 兼容依赖 模块B latest依赖 模块C,然后C发布不兼容版本,A和B全跪。套娃BUG,隔山打牛,怕不怕。。。

作为一个开发者,我能控制我的代码,我的package.json符合规范,但是我控制不了三方模块的代码,也控制不了三方模块的package.json符合规范。

于是有了。。。 npm-shrinkwrap.json。安装好依赖后,npm shrinkwrap可以生成npm-shrinkwrap.json文件,对本次安装的所有三方模块和三方模块依赖的三方模块等,的版本树,进行记录并锁定。下一次安装的时候,会按照shrinkwrap.json里边的记录来安装所有的模块。

这算是一种设计对工程的妥协吧,设计很美好,而现实中工程上的bug会很多。然而… 模块升级不一定是带来bug的(虽然这种情况很多),也有可能是修bug的, 所以这一妥协,就妥协出了后来的一系列问题。

npm v5.0.0 诞生

设计者意识到上面的问题在工程中是客观普遍存在的,于是npm v5.0.0中,除非手动生成了shrinkwrap.json文件,否则在npm install的时候会自动生成一个同功能的package-lock.json。

此时,每次重新下载依赖模块时,都会完全按照package-lock.json中记载的去下载。然而就如上面所说,模块版本升级不一定是带来bug的,也有可能是修复bug的啊,还含有可能带上新feature。

我更新了package.json中的模块版本,npm install,结果发现按照的还是老版本? 看看issue里这位老哥,都被逼的把 npm install 改成 rm -f package-lock.json && npm install

npm v5.1.0 迷惑行为

为了解决上面的问题,npm v5.1.0中,npm install的时候,changelog中写着,如果手动更新了package.json,那么install的时候会把package.json中的版本更新到package-lock.json中去。解决了上面的问题,对吧?

理想中的:package.json中依赖A@1.2.3,package-lock.json中是A@1.2.2,npm install之后两个文件中都变成A@1.2.3

这个特性确实是ok了,奈何大家发现,从git上拉代码下来,安装所有的依赖模块时,package.json和package-lock.json中都写着A@1.2.3,然后此时作者发布了一个 A@1.2.4兼容版本,此时npm install之后,安装的却是兼容版本A@1.2.4。好了,又回到了最开始的场景,假如A@1.2.4发布了一个BUG 。。。

这个版本里package-lock.json相当于是失效了,我反正是没理解,感觉这有点像是npm发布了一个带bug的版本。

npm v5.4.2 拨乱反正

对于5.1.0版本起的问题,npm维护者在这个issue里给出了回答(然而我在changelog里边没有看到相关描述),从5.4.2版本开始,逻辑变更为:

  • 如果只有一个 package.json 文件,运行 npm install,会生成新的 package-lock.json 文件
  • 如果 package.json 的 semver-range version 和 package-lock.json 中版本兼容,即使此时 package.json 中有新的版本,执行 npm install 也还是会根据 package-lock.json 下载
  • 如果手动修改了 package.json 中的依赖版本,且和 package-lock.json 中版本不兼容,那么执行 npm install 时 package-lock.json 将会更新到兼容 package.json 的版本

这下终于科学了

变迁总结

nodejs版本 npm版本 锁定方案 锁定描述
<8.0.0 <5.0.0 shrinkwrap.json 手动npm shrinkwrap生成,根据shrinkwrap.json锁定所有版本
<=8.1.4 <5.1.0 package-lock.json 自动生成,锁定所有版本
<=8.6.0 <5.4.2 package-lock.json 自动生成,不锁定任何版本
>8.6.0 >=5.4.2 package-lock.json 自动生成,锁定兼容版本,不锁定非兼容版本

要不要使用package-lock.json

说了那么多,到底要不要使用lock文件呢?这里有讨论

我的理解:
好处:能尽量保证项目的稳定,不受海量三方模块发布的影响
坏处:底层基础库的修复bug兼容更新无法自动应用,需要删除lock重新生成或者手动修改lock

如果使用了大量第三方模块,觉得三方模块及三方模块的依赖不可控,那么用lock锁定它们的兼容版本依赖,防止三方模块发布的兼容版本整崩整个项目;
如果觉得依赖的大部分模块可控(比如是公司内部团队维护),且这些模块作为基础模块会偶尔发布底层性能优化/bug修复等,那么可以考虑不使用lock文件。

☞ 参与评论