你真的知道NPM版本管理规范吗
NPM Version Management Specification
来源
常规的开发,常规的代码,不动如山的CI,突然发生了错误,导致失败,出现以下错误:
1
|
Build failed: [BABEL] /xxx/xxx/yyy/.xxx.js: You gave us a visitor for the node type OptionalCallExpression but it's not a valid type
|
本地调试之,赫然出现了不一样的错误:
1
|
Build failed: Cannot find module '@babel/runtime/core-js/object/keys'
|
观察了一下package.json
,含有"babel-runtime": "^6.9.2"
,于是乎开开心心的安装了下@babel/runtime
=> npm install @babel/runtime
。
BOOOOOM!继续报错,寻遍 issue 未发现错误原因以及真正的解决办法,TnT
查看了下框架包,查找了下项目依赖包的依赖包,发现使用了@babel/runtime@7.0.0-beta.41
的版本,莫不是版本问题?!换之,修改了下package.json
文件如下:
1
|
- "babel-runtime": "^6.9.2"
|
常规rm -rf node_modules && cnpm install
,小段时间的等待之后,发现错误并没有消失,奇了怪了~~
继续查看依赖包的依赖包,发现它要7.0.0-beta.41
,而在我的node_modules/
黑洞里的@babel/runtime
却安装的是7.0.0
版本,Bingo,问题找到了,锁个版本,修改如下:
1
|
- "@babel/runtime": "^7.0.0-beta.41"
|
常规rm -rf node_modules && cnpm install
之后,问题消失了,部署跑CI瞧一下,问题解决。
简单的一个问题,在知道原因之后。如果不知道原因呢??(此处有个黑人问号)
幸好我知道些npm
版本的控制规范,才得已比较早的定位问题并解决之,带着这份小确幸,重新整理了下npm
包管理器的版本管理规范(NPM Version Management Specification)。
语义化版本控制规范 SemVer
SemVer
(Semantic Versioning,语义化版本控制)是Github起草的一个语义化版本号管理模块,它实现了版本号的解析和比较,规范版本号的格式,它解决了依赖地狱的问题。
基本规则
语义化版本控制,顾名思义,就是让版本号更具有语义,可以传达出关于软件本身的一些重要信息而不只是简单的一串数字。
基本版本格式
1
|
主版本号(Major).次版本号(Minor).修订号(Patch)
|
每个部分都为整数(>=0
),按照递增的规则改变。
版本号递增规则
- 主版本号(Major):当你做了不兼容的API修改
- 次版本号(Minor):当你做了向下兼容的功能性新增
- 修订号(Patch):当你做了向下兼容的问题修正
- 先行版本号及版本编译信息可以加到
基本版本格式
的后面,作为延伸- 先行版本号由首位的连接号”-“、标识符号(由ASCII码的英文数字和连接号标识符[0-9A-Za-z-]组成)、句点”.“组成。如1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。先行版的优先级低于相关联的标准版本
- 版本编译信息由首位的一个加号和一连串以句点分隔的标识符号(由ASCII码的英文数字和连接号标识符[0-9A-Za-z-]组成)组成。如1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。判断版本优先层级时,版本编译信息可以被忽略
如何比较版本高低
判断优先层级时,必须把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较。由左到右依次比较每个标识符号,第一个差异值用来决定优先层级(其中字母连接号以ASCII排序进行比较、其他都相同时栏位多的先行版本号优先级较高)。如:
1
|
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。
|
范围规则
<空>
锁定版本号
1.0.0
: 锁定了版本只能为1.0.0
<、<=、>、>=、=
指定版本范围,甚至可以通过||
组合多个比较器
=1.2.7 <1.3.0
中包括1.2.7
、1.2.8
、1.2.99
等等,但不包括1.2.6
、1.3.0
或1.1.0
等等1.2.7 || >=1.2.9 <2.0.0
中包括1.2.7
、1.2.9
、1.4.6
等等,但不包括1.2.8
或2.0.0
等等
-
连字符表示版本号范围,表示的是一个闭区间
1.2.3 - 2.3.4
相当于>=1.2.3
和<=2.3.4
x、X、*
可以替代主版本号.次版本号.修订号
三段中任意一段,表示该位置版本号没有限制;另外缺省三段中任意一段与用x
、X
或*
替换该段效果相同
*
相当于>=0.0.0
,表示任何版本号1.X
或1.x
相当于>=1.0.0 <2.0.0
,匹配到主版本号1.2.*
相当于>=1.2.0 <1.3.0
,匹配到主版本号和次版本号""
(空字符串) 相当于*
,即相当于>=0.0.0
1
相当于1.x.x
,即相当于>=1.0.0 <2.0.0
1.2
相当于1.2.x
,即相当于>=1.2.0 <1.3.0
~
允许小版本迭代
- 如果有缺省值,缺省部分任意迭代;
- 如果没有缺省值,只允许补丁即修订号(Patch)的迭代
eg.:
~1.2.3
:>=1.2.3 <1.3.0
~1.2
:>=1.2.0 < 1.3.0
(相当于1.2.x
)~1
:>=1.0.0 <2.0.0
(相当于1.x
)~0.2.3
:>=0.2.3 <0.3.0
~0.2
:>=0.2.0 <0.3.0
(相当于0.2.x
)~0
:>=0.0.0 <1.0.0
(相当于0.x
)~1.2.3-beta.2
:>=1.2.3-beta.2 <1.3.0
(注意,在1.2.3
版本中,允许使用大于等于beta.2
的先行版本号,而除1.2.3
之外的版本号不允许使用先行版本号,所以此处1.2.3-beta.4
是允许的,而1.2.4-beta.2
是不允许的)
^
允许大版本迭代
- 允许从左到右的第一段不为
0
那一版本位+1
迭代(左闭右开); - 如果有缺省值,且缺省值之前没有不为0的版本位,则允许缺省值的前一位版本
+1
迭代
eg.:
^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.2.x
:>=1.2.0 <2.0.0
^0.0.x
:>=0.0.0 <0.1.0
^0.0
:>=0.0.0 <0.1.0
^1.x
:>=1.0.0 <2.0.0
^0.x
:>=0.0.0 <1.0.0
^1.2.3-beta.2
:>=1.2.3-beta.2 <2.0.0
(注意,在1.2.3
版本中,允许使用大于等于beta.2
的先行版本号,而除了1.2.3
之外的版本号不允许使用先行版本号,所以此处1.2.3-beta.4
是允许的,而1.2.4-beta.2
是不允许的);^0.0.3-beta
:>=0.0.3-beta <0.0.4
(同上,此处0.0.3-pr.2
是允许的)
锁定(控制)版本
看到这,聪明的你一定想到了package-lock.json
或是yarn.lock
。
在npm
的版本>=5.1
的时候,package-lock.json
文件是自动打开的,意味着会自动生成,package-lock.json
(官方文档)可以理解为/node_modules
文件夹内容的json
映射,并能够感知npm
的安装/升级/卸载的操作。可以保证在不同的环境下安装的包版本保持一致。听上去很不错哈,实际使用中,大部分它的表现确实不错,可是如上述问题:我手动修改了package.json
文件内依赖的版本,package-lock.json
就没那么聪明(至少目前是,未来会不会变聪明就不可知了),且不会变化。于是BOOOOOOM~~~~
SO
如果你真的想保证你的包版本在各个环境都是一样的话,请修改下package.json
中的依赖,去掉默认前面的^
,当然这样的话,你就没法自动享受依赖包小版本的修复了,问题来了,在什么情况下选择哪一种呢?
- 在依赖包严格按照版本规范来开发的,你可以使用
^
来享受包的最新功能和修复。这也是推荐的。 - 在你不可知或已知依赖包不是那么规范的情况下,或许它在一个小版本(
patch
)做出不兼容更改(不兼容更改在beta
等先行版本中一定[墨菲定律]会发生),那么这个时候,你应该把这个依赖包的版本在package.json
上锁住版本,而不应该把它交给package-lock.json
来处理 - 记住一点,绝对不要在生成环境下使用
beta
等先行版本依赖包,因为如果那是你的私有项目,它会在未来的某一刻坑害了你,如果这是你的共有项目,那么,它一定会在未来的某一刻对你的所有用户做出致命的坑害行为!(beta
包就是不负责任的流氓包,玩觉爽就好 ^o^)
最后:rm -rf node_modules/ && npm install
大法在你使用package-lock
的情况下,请更换为:rm -rf node_modules && rm -rf package-lock.json && npm install
。