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.01相當於1.x.x,即相當於>=1.0.0 <2.0.01.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。

