Abo

关于 package-lock.json 存在的意义

经常会提到的package-lock跟package两者的区别与意义

Great minds think alike

        

需求由来

用一句话来概括很简单,就是锁定安装时的包的版本号,并且需要上传到git,以保证其他人在npm install时大家的依赖能保证一致

根据官方文档,这个package-lock.json 是在 npm install 时候生成一份文件,用以记录当前状态下实际安装的各个npm package的具体来源和版本号。

它有什么用呢?因为npm是一个用于管理package之间依赖关系的管理器,它允许开发者在pacakge.json中间标出自己项目对npm各库包的依赖。你可以选择以如下方式来标明自己所需要库包的版本

这里举个例子:

1
2
3
4
// package.json
"dependencies": {
"@types/node": "^8.0.33",
},

这里面的 向上标号^是定义了向后(新)兼容依赖,指如果 types/node的版本是超过8.0.33,并在大版本号(8)上相同,就允许下载最新版本的 types/node库包,例如实际上可能运行npm install时候下载的具体版本是8.0.35。

大多数情况这种向新兼容依赖下载最新库包的时候都没有问题,可是因为npm是开源世界,各库包的版本语义可能并不相同,有的库包开发者并不遵守严格这一原则:相同大版本号的同一个库包,其接口符合兼容要求。这时候用户就很头疼了:在完全相同的一个nodejs的代码库,在不同时间或者不同npm下载源之下,下到的各依赖库包版本可能有所不同,因此其依赖库包行为特征也不同有时候甚至完全不兼容。

因此npm最新的版本就开始提供自动生成package-lock.json功能,为的是让开发者知道只要你保存了源文件,到一个新的机器上、或者新的下载源,只要按照这个package-lock.json所标示的具体版本下载依赖库包,就能确保所有库包与你上次安装的完全一样。

实际场景

同一个项目第一次cnpm install的时候还可以启动,过一段时间,把node_modules删掉,重新cnpm install,发现项目启动报错了。奇怪,项目代码和之前一样,一点都没改动。查其原因,发现是package.json文件的依赖模块版本号没有固定,如 "roadhog": "^2.5.0-beta.4"。这里有个^,安装的时候会安装2.x.x的最新版本,因为这期间该模块有更新,导致两次install的版本不一样,所以项目报错了

第一个想到的是我把该模块写死不就行了 "roadhog": "2.5.0-beta.4"。但是仔细一想,仍会有问题。npm最方便的地方,是它可以帮我们管理依赖,自动下载依赖。意思是假如我们依赖roadhog,roadhog又依赖模块A、B等,我们安装roadhog时,npm会自动帮我们安装好roadhog依赖的模块A、B等。

我们把roadhog锁死了,但是其引用的A模块仍用的是^括号写法(即没有锁死版本),所以假如A模块更新了,仍可能会出现之前类似的问题。那么该怎么进行版本控制呢。
所以我们要对整个依赖树做锁定,那前后编译出来的应用版本就不会存在两次安装版本不一的问题了。这就引出了我们的package-lock.json文件。它的产生就是来对整个依赖树进行版本固定的(锁死)

package-lock.json它会在npm更改node_modules目录树或者package.json时自动生成的,它准确的描述了当前项目npm包的依赖树,并且在随后的安装中会根据package-lock.json来安装,保证是相同的一个依赖树,不考虑这个过程中是否有某个依赖有小版本的更新

语义版本控制

版本由三部分组成:X,Y,Z,X 表示大版本(major)、Y 表示小版本(minor)、Z 表示补丁版本(patch)。

例如:1.2.3,主要版本1,次要版本2,补丁3。

补丁中的更改表示不会破坏任何内容的错误修复。 次要版本的更改表示不会破坏任何内容的新功能。 主要版本的更改代表了一个破坏兼容性的大变化。 如果用户不适应主要版本更改,则内容将无法正常工作。

关于*、~、^符号的意义

  1. *意味着安装最新大版本的依赖包
  2. ^ 指的是只要不修改 [major, minor, patch] 三元组中,最左侧的第一个非0位,都是可以的

^1.2.3 匹配 >=1.2.3 < 2.0.0

^0.2.3 匹配 >=0.2.3 <0.3.0

^0.0.3 匹配 >=0.0.3 <0.0.4

  1. ~ 简而言之就是允许修改最后一位被指定的版本号

~1.2.3 匹配 >=1.2.3 <1.3.0

~1.2 匹配 >=1.2.0 <1.3.0 (Same as 1.2.x)

~1 匹配 >=1.0.0 < 2.0.0

~0.2.3 匹配 >=0.2.3 <0.3.0

~1.2 匹配 >=1.2.0 <1.3.0 (Same as 0.2.x)

~0 匹配 >=0.0.0 < 1.0.0(Same as 0.x)

package-lock.js的生成

原来package.json文件只能锁定版本,也就是版本号的第一位,并不能锁定后面的小版本,你每次npm install都是拉取的该大版本下的最新的版本,为了稳定性考虑我们几乎是不敢随意升级依赖包的,这将导致多出来很多工作量,测试/适配等,所以package-lock.json文件出来了,当你每次安装一个依赖的时候就锁定在你安装的这个版本。

那如果我们安装时的包有bug,后面需要更新怎么办

在以前(在没有package-lock.json的时代),可能就是直接改package.json里面的版本,然后再npm install了,但是5版本后就不支持这样做了,因为版本已经锁定在package-lock.json里了,所以我们只能npm install xxx@x.x.x 这样去更新我们的依赖,然后package-lock.json也能随之更新

默认,当我们在一个项目中npm install时候(前提该项目有package.json文件),安装完成后,会自动生成一个package-lock.json文件(位置和之前的package.json文件同级)。该文件里面记录了package.json依赖的模块,以及依赖的依赖。并且给每个依赖标明了版本, 获取地址和哈希值, 使得每次安装都会出现相同的结果. 不管你在什么机器上面或什么时候安装。

当我们下次再npm install时候,npm发现如果项目中有package-lock.json文件,会根据package-lock.json里的内容来处理和安装依赖而不再根据package.json.

网上说npm版本是v5.0.0以上的版本,但是测试我的版本是5.几(具体忘了),安装的时候并没有生成package-lock.json,于是升级了最新版本(我当时升级的是6.9.0),发现可以了。

1
2
3
npm -v //查看npm版本
npm install -g npm //升级npm到最新版本
npm install -g npm@6.0.1 //升级npm到指定版本

如果package-lock.json已经生成,我再次更新package.json某个模块版本(包括手动更改package.json某个版本号,然后重新npm install或者类似npm install antd@3.15.0来更新某个模块),package-lock.json都会自动更新到之前设置的版本,所以不用担心package.json更新了,package-lock.json还是旧的问题。npm v5.1.0之前存在该问题,npm v5.1.0以后不存在该问题。此同步功能是作为npm v5.1.0的一部分发布的,该版本于2017年7月5日上线。

两者的优先级

npm(^5.x.x.x)后的做法,npm 使用package-lock.json,而不是使用 package.json 来解析和安装模块。因为 package-lock 为每个模块及其每个依赖项指定了版本,位置和完整性哈希,所以它每次创建的安装都是相同的。 无论你使用什么设备,或者将来安装它都无关紧要,每次都应该给你相同的结果,这非常有用。

总结

  • 如果你使用的 npm 版本 为 ^5.x.x , package-lock.json 会默认自动生成
  • 你应该使用 package-lock 来确保一致的安装和兼容的依赖关系
  • 你应该将 package-lock 提交到源代码控制
  • 补充:cnpm不支持package-lock