原作者:栖邀
npm 是一個友善的 Node 子產品分發、管理工具。我們平常會使用
npm install
安裝子產品,使用 npm publish
釋出子產品。事實上除了基本功能,這 2 個指令還有其他非常有用的特性。這篇文章會給大家介紹這些指令的一些“進階”用法。 子產品的版本号
我們先來說說版本号。npm 使用的是一種叫做
semantic version的規範,它的規則很簡單,總結起來就是下面幾條:
- 使用 semver 的軟體必須定義公開、嚴謹、易于了解的 API。也就是子產品要提供功能給使用者。
- 版本号格式為:
,并且 X、Y、Z 均為正整數并且不斷遞增。X 表示大版本(major)、Y 表示小版本(minor)、Z 表示更新檔版本(patch)。X.Y.Z
- 一個版本釋出後,此版本内容不能再變更,變更必須再釋出一個新版本。也就是不能覆寫釋出。
-
表示初始版本,這種版本下的 API 不能保證穩定,随時可能變更。0.Y.Z
- 當進行了向後相容的 bug 修複時,更新檔版本 Z 必須增加。
- 當引入了向後相容的新功能時,小版本 Y 必須增加,同時 Z 必須重置為 0(小版本裡面可能會包含 bug 修複)。
- 當引入了不相容的變更時,大版本 X 必須增加,同時 Y、Z 必須重置為 0(大版本裡面可能會包含小版本或者更新檔版本的改動)。
-
後面還可以加預釋出版本号、建構資訊,格式為:X.Y.Z
,比如:X.Y.Z-pre_lease+build_meta
、1.0.0-alpha+20151226
。1.0.0-beta.2+20151230
- 進行版本号比較時,遵循下面的規則:1)依次按數值比較 X、Y、Z 的值,直到第一個不同的位置;2)如果兩個版本的 X、Y、Z 都相等,含有 pre-release 版本号的較小;3)如果兩個版本的 X、Y、Z 都相等并且都含有 pre-release 版本号,要單獨比較 pre-release 版本。比如:
,1.0.0 < 2.0.0 < 2.1.0 < 2.1.1
1.0.0-alpha < 1.0.0
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2
子產品的依賴管理
在進行項目開發時,對于依賴的子產品進行管理是非常必要的。這種管理主要展現在:
- 如何友善的擷取項目需要使用的子產品
- 目前項目依賴了哪些子產品,或許還會指定子產品依賴的環境(哪些子產品是生産階段依賴的、哪些是開發階段依賴的)
- 子產品的哪些版本是和目前項目相容的,可以直接使用
- 其他成員(或者系統)如何友善的擷取所有子產品,進而能讓項目正常運作
下面我們看看 npm 是如何幫助我們解決上面這些問題的。
擷取子產品
我們在進行項目開發時,如果需要某個功能,都會先去 npm registry 上搜一搜,看看有沒有類似的子產品可以直接複用,進而提高開發效率。擷取子產品非常簡單,我們在項目的根目錄直接執行
npm install name
就可以将子產品安裝在
node_modules
目錄下,然後直接
require
就可使用。
依賴了哪些子產品
上面擷取子產品的方法是一種“臨時”起意的方式,并不能記錄我們依賴子產品的具體資訊(除非我們自己去查找)。我們安裝子產品時,可以執行
npm install name --save
(生産階段的依賴) 或是
npm install name --save-dev
(開發階段的依賴),來将子產品資訊儲存到項目的
package.json
檔案中。
子產品依賴表示法
執行
npm install name --save
來安裝子產品時,npm 會首先安裝子產品的最新版本,然後将子產品名及子產品版本号以最“保險”的表示方式寫入到
package.json
比如,我們執行
npm install koa --save
,安裝子產品後,會更新
package.json
檔案的
dependencies
字段:
{
"dependencies": {
"koa": "~1.1.0"
}
}
項目對子產品的依賴可以使用下面的 3 種方法來表示(假設目前版本号是 1.1.0 ):
- 相容子產品新釋出的更新檔版本:
~1.1.0
1.1.x
1.1
- 相容子產品新釋出的小版本、更新檔版本:
^1.1.0
1.x
1
- 相容子產品新釋出的大版本、小版本、更新檔版本:
*
x
可以看到,npm 預設會将依賴表示為最“保險”的方式(即:擷取子產品新釋出的更新檔版本),我們也可以使用
npm install koa --save-exact
來精确的指定子產品版本(這種情況依賴就直接表示為:
"koa": "1.1.0"
)。
除了擷取子產品的最新版本,我們還可以精确擷取子產品的某個具體版本,如:
npm install [email protected] --save
,這種情況依賴就會表示為:
"koa": "~1.0.0"
安裝子產品
子產品的依賴都被寫入了
package.json
檔案,其他合作者(比如:其他小夥伴、或者是部署服務)隻要進入項目的根目錄,執行
npm install
就可以将依賴的子產品全部安裝到
node_modules
目錄下。
比如下面的
package.json
:
{
"dependencies": {
"bluebird": "~3.1.1",
"request-promise": "^1.0.2",
"lodash": "*"
},
"devDependencies": {
"mocha": "~2.3.4"
}
}
當我們執行
npm install
時,就會安裝
dependencies
及
devDependencies
字段裡列出的所有子產品。如果隻想安裝
dependencies
裡面列出的子產品,可以使用
npm install --only=production
這裡 npm 實際安裝的子產品是根據依賴的表示來決定的:
-
表示會安裝最新的更新檔版本。比如:安裝 3.1.2、3.1.3 等,但是不會安裝 3.2.0 這種小版本或者4.0.0 這種大版本(就算這些版本是最新的)。這種表示法是比較保險的,因為更新檔版本隻是 bug 修複,不會新增功能。"bluebird": "~3.1.1"
-
表示會安裝最新的更新檔版本或者小版本。比如:安裝 1.1.0、1.1.1 等小版本,或者 1.0.3、1.0.4 等更新檔版本,但是不會安裝 2.0.0 這種大版本。而且,總是安裝版本号最大的(也就是優先安裝小版本)。這種表示法通常是保險的,小版本會增加向後相容的功能。"request-promise": "^1.0.2"
-
表示安裝最新的版本(不管這個版本是大版本、小版本、還是更新檔版本)。這種表示法非常危險,如果有大版本,直接就安裝了大版本,而大版本通常是不會向後相容的,可能導緻項目功能運作異常。"lodash": "*"
子產品的釋出
對于子產品的提供者來說,每一次釋出都應該盡量遵循 semver 的規範來變更版本号,不要讓使用者困惑甚至是想罵人......
在“子產品的版本号”部分我們介紹了 semver 規範,npm 也提供了相關的指令來讓我們友善的進行版本号變更:
- 更新更新檔版本号:
npm version patch
- 更新小版本号:
npm version minor
- 更新大版本号:
npm version major
一個比較合适的釋出流程可以是:
- 根據此次變更執行
指令(npm 會根據npm version [new version]
指定的類型更新new version
中的package.json
字段,然後進行一次 commit,最後打上一個該版本号的 tag)。version
-
,将改動同步到遠端代碼倉庫。git push origin master --tags
-
,将子產品釋出到 npm 倉庫。npm publish
子產品的 tag
子產品安裝指令的最簡形式
npm install name
的完整版其實應該是:
npm install name@latest
。這裡的
latest
是子產品版本的一個 tag,會對應到子產品的一個具體版本。
我們來看一個例子:子產品 koa 在 npm registry 上的資訊如下(完整版見:
http://registry.npmjs.com/koa):
{
"name": "koa",
"dist-tags": {
"latest": "1.1.2",
"next": "2.0.0-alpha.3"
},
"versions": {
"0.0.1": {...},
"1.1.2": {...},
"2.0.0-alpha.3": {...}
}
}
當執行
npm install koa
時,其實是執行
npm install koa@latest
,而這個
latest
等于
dist-tags.latest
(版本 1.1.2),最後版本 1.1.2 被安裝,同時依賴會标記為
"koa": "~1.1.2"
npm install koa@next
時,
next
dist-tags.next
(版本 2.0.0-alpha.3),最後版本 2.0.0-alpha.3 被安裝,同時依賴會标記為
"koa": "~2.0.0-alpha.3"
子產品的維護者在進行子產品釋出時,可以指定将目前版本釋出為哪個 tag(預設是 latest)。
npm publish
時,會首先将目前版本釋出到 npm registry,然後更新
dist-tags.latest
的值為新版本。
npm publish --tag=next
時,會首先将目前版本釋出到 npm registry,并且更新
dist-tags.next
的值為新版本。這裡的 next 可以是任意有意義的命名(比如:v1.x、v2.x 等等)。
可以看出,tag 就是對子產品某一個版本的标記(類比 git 中 tag 是對某一次送出的标記),并且這個 tag 還可以随時更新為新的版本号。
能對版本打 tag,使得我們在維護多個版本時非常友善。比如,可以像 koa 的做法一樣,新開一個 next 的 tag 來提供新版本給社群試用,而不影響現在的穩定版本。等到新版本逐漸穩定後,再将其釋出為 latest 即可。
其他問題
這樣看起來,釋出 Node 子產品是不是很簡單?其實,我們沒有說的東西還有很多。比如下面這些問題:
每次釋出前怎麼保證子產品提供的功能沒有損壞?可以考慮接入持續內建系統,每次 push 運作單元測試,未通過不進行釋出。
版本号其實對維護者不友好,很容易改錯,有沒有什麼好方法來解決?可以考慮規範送出資訊,維護者隻是描述變更,從送出資訊中抽取變更類型然後生成正确的版本号和變更日志(比如:angular 的送出資訊規範:
https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit)
這些問題要是可以自動化多好!我嘗試搜尋了下,果然有開發者在搞了。推薦大家看看這個工具:
https://github.com/semantic-release/semantic-release,很好的解決了上面的問題。
但是其實最最核心的問題是維護者得養成好習慣(寫單測、規範送出資訊),不然再好的工具都白搭。我也在慢慢培養中,真是痛苦......
參考資料
- http://semver.org/
- https://docs.npmjs.com/misc/semver
- https://docs.npmjs.com/cli/dist-tag
- https://docs.npmjs.com/cli/publish
- https://docs.npmjs.com/cli/install
- https://medium.com/greenkeeper-blog/one-simple-trick-for-javascript-package-maintainers-to-avoid-breaking-their-user-s-software-and-to-6edf06dc5617