您好,登錄后才能下訂單哦!
這篇文章主要介紹“node.js npm的包管理機(jī)制是什么”的相關(guān)知識,小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“node.js npm的包管理機(jī)制是什么”文章能幫助大家解決問題。
在 Node.js
中,模塊是一個庫或框架,也是一個 Node.js
項(xiàng)目。Node.js
項(xiàng)目遵循模塊化的架構(gòu),當(dāng)我們創(chuàng)建了一個 Node.js
項(xiàng)目,意味著創(chuàng)建了一個模塊,這個模塊必須有一個描述文件,即 package.json
。它是我們最常見的配置文件,但是它里面的配置你真的有詳細(xì)了解過嗎?配置一個合理的 package.json
文件直接決定著我們項(xiàng)目的質(zhì)量,所以首先帶大家分析下 package.json
的各項(xiàng)詳細(xì)配置。
package.json
中有非常多的屬性,其中必須填寫的只有兩個:name
和 version
,這兩個屬性組成一個 npm
模塊的唯一標(biāo)識。
name
即模塊名稱,其命名時需要遵循官方的一些規(guī)范和建議:
包名會成為模塊url
、命令行中的一個參數(shù)或者一個文件夾名稱,任何非url
安全的字符在包名中都不能使用,可以使用 validate-npm-package-name
包來檢測包名是否合法。
語義化包名,可以幫助開發(fā)者更快的找到需要的包,并且避免意外獲取錯誤的包。
若包名稱中存在一些符號,將符號去除后不得與現(xiàn)有包名重復(fù)
例如:由于react-native
已經(jīng)存在,react.native
、reactnative
都不可以再創(chuàng)建。
如果你的包名與現(xiàn)有的包名太相近導(dǎo)致你不能發(fā)布這個包,那么推薦將這個包發(fā)布到你的作用域下。
例如:用戶名 conard
,那么作用域?yàn)?@conard
,發(fā)布的包可以是@conard/react
。
name
是一個包的唯一標(biāo)識,不得和其他包名重復(fù),我們可以執(zhí)行 npm view packageName
查看包是否被占用,并可以查看它的一些基本信息:
若包名稱從未被使用過,則會拋出 404
錯誤:
另外,你還可以去 https://www.npmjs.com/
查詢更多更詳細(xì)的包信息。
{ "description": "An enterprise-class UI design language and React components implementation", "keywords": [ "ant", "component", "components", "design", "framework", "frontend", "react", "react-component", "ui" ] }
description
用于添加模塊的的描述信息,方便別人了解你的模塊。
keywords
用于給你的模塊添加關(guān)鍵字。
當(dāng)然,他們的還有一個非常重要的作用,就是利于模塊檢索。當(dāng)你使用 npm search
檢索模塊時,會到description
和 keywords
中進(jìn)行匹配。寫好 description
和 keywords
有利于你的模塊獲得更多更精準(zhǔn)的曝光:
描述開發(fā)人員的字段有兩個:author
和 contributors
, author
指包的主要作者,一個 author
對應(yīng)一個人。 contributors
指貢獻(xiàn)者信息,一個 contributors
對應(yīng)多個貢獻(xiàn)者,值為數(shù)組,對人的描述可以是一個字符串,也可以是下面的結(jié)構(gòu):
{ "name" : "ConardLi", "email" : "lisqPersion@163.com", "url" : "https://github.com/ConardLi" }
{ "homepage": "http://ant.design/", "bugs": { "url": "https://github.com/ant-design/ant-design/issues" }, "repository": { "type": "git", "url": "https://github.com/ant-design/ant-design" }, }
homepage
用于指定該模塊的主頁。
repository
用于指定模塊的代碼倉庫。
bugs
指定一個地址或者一個郵箱,對你的模塊存在疑問的人可以到這里提出問題。
我們的項(xiàng)目可能依賴一個或多個外部依賴包,根據(jù)依賴包的不同用途,我們將他們配置在下面幾個屬性下:dependencies、devDependencies、peerDependencies、bundledDependencies、optionalDependencies
。
在介紹幾種依賴配置之前,首先我們來看一下依賴的配置規(guī)則,你看到的依賴包配置可能是下面這樣的:
"dependencies": { "antd": "ant-design/ant-design#4.0.0-alpha.8", "axios": "^1.2.0", "test-js": "file:../test", "test2-js": "http://cdn.com/test2-js.tar.gz", "core-js": "^1.1.5", }
依賴配置遵循下面幾種配置規(guī)則:
依賴包名稱:VERSION
VERSION
是一個遵循SemVer
規(guī)范的版本號配置,npm install
時將到npm服務(wù)器下載符合指定版本范圍的包。
依賴包名稱:DWONLOAD_URL
DWONLOAD_URL
是一個可下載的tarball
壓縮包地址,模塊安裝時會將這個.tar
下載并安裝到本地。
依賴包名稱:LOCAL_PATH
LOCAL_PATH
是一個本地的依賴包路徑,例如 file:../pacakges/pkgName
。適用于你在本地測試一個npm
包,不應(yīng)該將這種方法應(yīng)用于線上。
依賴包名稱:GITHUB_URL
GITHUB_URL
即 github
的 username/modulename
的寫法,例如:ant-design/ant-design
,你還可以在后面指定 tag
和 commit id
。
依賴包名稱:GIT_URL
GIT_URL
即我們平時clone代碼庫的 git url
,其遵循以下形式:
<protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]
其中 protocal
可以是以下幾種形式:
git://github.com/user/project.git#commit-ish
git+ssh://user@hostname:project.git#commit-ish
git+ssh://user@hostname/project.git#commit-ish
git+http://user@hostname/project/blah.git#commit-ish
git+https://user@hostname/project/blah.git#commit-ish
dependencies
指定了項(xiàng)目運(yùn)行所依賴的模塊,開發(fā)環(huán)境和生產(chǎn)環(huán)境的依賴模塊都可以配置到這里,例如
"dependencies": { "lodash": "^4.17.13", "moment": "^2.24.0", }
有一些包有可能你只是在開發(fā)環(huán)境中用到,例如你用于檢測代碼規(guī)范的 eslint
,用于進(jìn)行測試的 jest
,用戶使用你的包時即使不安裝這些依賴也可以正常運(yùn)行,反而安裝他們會耗費(fèi)更多的時間和資源,所以你可以把這些依賴添加到 devDependencies
中,這些依賴照樣會在你本地進(jìn)行 npm install
時被安裝和管理,但是不會被安裝到生產(chǎn)環(huán)境:
"devDependencies": { "jest": "^24.3.1", "eslint": "^6.1.0", }
peerDependencies
用于指定你正在開發(fā)的模塊所依賴的版本以及用戶安裝的依賴包版本的兼容性。
上面的說法可能有點(diǎn)太抽象,我們直接拿 ant-design
來舉個例子,ant-design
的 package.json
中有如下配置:
"peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" }
當(dāng)你正在開發(fā)一個系統(tǒng),使用了 ant-design
,所以也肯定需要依賴 React
。同時, ant-design
也是需要依賴 React
的,它要保持穩(wěn)定運(yùn)行所需要的 React
版本是16.0.0
,而你開發(fā)時依賴的 React
版本是 15.x
:
這時,ant-design
要使用 React
,并將其引入:
import * as React from 'react'; import * as ReactDOM from 'react-dom';
這時取到的是宿主環(huán)境也就是你的環(huán)境中的 React
版本,這就可能造成一些問題。在 npm2
的時候,指定上面的 peerDependencies
將意味著強(qiáng)制宿主環(huán)境安裝 react@>=16.0.0和react-dom@>=16.0.0
的版本。
npm3
以后不會再要求 peerDependencies
所指定的依賴包被強(qiáng)制安裝,相反 npm3
會在安裝結(jié)束后檢查本次安裝是否正確,如果不正確會給用戶打印警告提示。
"dependencies": { "react": "15.6.0", "antd": "^3.22.0" }
例如,我在項(xiàng)目中依賴了 antd
的最新版本,然后依賴了 react
的 15.6.0
版本,在進(jìn)行依賴安裝時將給出以下警告:
某些場景下,依賴包可能不是強(qiáng)依賴的,這個依賴包的功能可有可無,當(dāng)這個依賴包無法被獲取到時,你希望 npm install
繼續(xù)運(yùn)行,而不會導(dǎo)致失敗,你可以將這個依賴放到 optionalDependencies
中,注意 optionalDependencies
中的配置將會覆蓋掉 dependencies
所以只需在一個地方進(jìn)行配置。
當(dāng)然,引用 optionalDependencies
中安裝的依賴時,一定要做好異常處理,否則在模塊獲取不到時會導(dǎo)致報(bào)錯。
和以上幾個不同,bundledDependencies
的值是一個數(shù)組,數(shù)組里可以指定一些模塊,這些模塊將在這個包發(fā)布時被一起打包。
"bundledDependencies": ["package1" , "package2"]
{ "license": "MIT" }
license
字段用于指定軟件的開源協(xié)議,開源協(xié)議里面詳盡表述了其他人獲得你代碼后擁有的權(quán)利,可以對你的的代碼進(jìn)行何種操作,何種操作又是被禁止的。同一款協(xié)議有很多變種,協(xié)議太寬松會導(dǎo)致作者喪失對作品的很多權(quán)利,太嚴(yán)格又不便于使用者使用及作品的傳播,所以開源作者要考慮自己對作品想保留哪些權(quán)利,放開哪些限制。
軟件協(xié)議可分為開源和商業(yè)兩類,對于商業(yè)協(xié)議,或者叫法律聲明、許可協(xié)議,每個軟件會有自己的一套行文,由軟件作者或?qū)iT律師撰寫,對于大多數(shù)人來說不必自己花時間和精力去寫繁長的許可協(xié)議,選擇一份廣為流傳的開源協(xié)議就是個不錯的選擇。
以下就是幾種主流的開源協(xié)議:
MIT
:只要用戶在項(xiàng)目副本中包含了版權(quán)聲明和許可聲明,他們就可以拿你的代碼做任何想做的事情,你也無需承擔(dān)任何責(zé)任。
Apache
:類似于 MIT
,同時還包含了貢獻(xiàn)者向用戶提供專利授權(quán)相關(guān)的條款。
GPL
:修改項(xiàng)目代碼的用戶再次分發(fā)源碼或二進(jìn)制代碼時,必須公布他的相關(guān)修改。
如果你對開源協(xié)議有更詳細(xì)的要求,可以到 choosealicense.com/ 獲取更詳細(xì)的開源協(xié)議說明。
{ "main": "lib/index.js", }
main
屬性可以指定程序的主入口文件,例如,上面 antd
指定的模塊入口 lib/index.js
,當(dāng)我們在代碼用引入 antd
時:import { notification } from 'antd';
實(shí)際上引入的就是 lib/index.js
中暴露出去的模塊。
當(dāng)你的模塊是一個命令行工具時,你需要為命令行工具指定一個入口,即指定你的命令名稱和本地可指定文件的對應(yīng)關(guān)系。如果是全局安裝,npm 將會使用符號鏈接把可執(zhí)行文件鏈接到 /usr/local/bin
,如果是本地安裝,會鏈接到 ./node_modules/.bin/
。
{ "bin": { "conard": "./bin/index.js" } }
例如上面的配置:當(dāng)你的包安裝到全局時:npm
會在 /usr/local/bin
下創(chuàng)建一個以 conard
為名字的軟鏈接,指向全局安裝下來的 conard
包下面的 "./bin/index.js"
。這時你在命令行執(zhí)行 conard
則會調(diào)用鏈接到的這個js文件。
這里不再過多展開,更多內(nèi)容在我后續(xù)的命令行工具文章中會進(jìn)行詳細(xì)講解。
{ "files": [ "dist", "lib", "es" ] }
files
屬性用于描述你 npm publish
后推送到 npm
服務(wù)器的文件列表,如果指定文件夾,則文件夾內(nèi)的所有內(nèi)容都會包含進(jìn)來。我們可以看到下載后的包是下面的目錄結(jié)構(gòu):
另外,你還可以通過配置一個
.npmignore
文件來排除一些文件, 防止大量的垃圾文件推送到npm
, 規(guī)則上和你用的.gitignore
是一樣的。.gitignore
文件也可以充當(dāng).npmignore
文件。
man
命令是 Linux
下的幫助指令,通過 man
指令可以查看 Linux
中的指令幫助、配置文件幫助和編程幫助等信息。
如果你的 node.js
模塊是一個全局的命令行工具,在 package.json
通過 man
屬性可以指定 man
命令查找的文檔地址。
man
文件必須以數(shù)字結(jié)尾,或者如果被壓縮了,以 .gz
結(jié)尾。數(shù)字表示文件將被安裝到 man
的哪個部分。如果 man
文件名稱不是以模塊名稱開頭的,安裝的時候會給加上模塊名稱前綴。
例如下面這段配置:
{ "man" : [ "/Users/isaacs/dev/npm/cli/man/man1/npm-access.1", "/Users/isaacs/dev/npm/cli/man/man1/npm-audit.1" ] }
在命令行輸入 man npm-audit
:
一個 node.js
模塊是基于 CommonJS
模塊化規(guī)范實(shí)現(xiàn)的,嚴(yán)格按照 CommonJS
規(guī)范,模塊目錄下除了必須包含包描述文件 package.json
以外,還需要包含以下目錄:
bin
:存放可執(zhí)行二進(jìn)制文件的目錄
lib
:存放js代碼的目錄
doc
:存放文檔的目錄
test
:存放單元測試用例代碼的目錄
...
在模塊目錄中你可能沒有嚴(yán)格按照以上結(jié)構(gòu)組織或命名,你可以通過在 package.json
指定 directories
屬性來指定你的目錄結(jié)構(gòu)和上述的規(guī)范結(jié)構(gòu)的對應(yīng)情況。除此之外 directories
屬性暫時沒有其他應(yīng)用。
{ "directories": { "lib": "src/lib/", "bin": "src/bin/", "man": "src/man/", "doc": "src/doc/", "example": "src/example/" } }
不過官方文檔表示,雖然目前這個屬性沒有什么重要作用,未來可能會整出一些花樣出來,例如:doc 中存放的 markdown 文件、example 中存放的示例文件,可能會友好的展示出來。
{ "scripts": { "test": "jest --config .jest.js --no-cache", "dist": "antd-tools run dist", "compile": "antd-tools run compile", "build": "npm run compile && npm run dist" } }
scripts
用于配置一些腳本命令的縮寫,各個腳本可以互相組合使用,這些腳本可以覆蓋整個項(xiàng)目的生命周期,配置后可使用 npm run command
進(jìn)行調(diào)用。如果是 npm
關(guān)鍵字,則可以直接調(diào)用。例如,上面的配置制定了以下幾個命令:npm run test
、npm run dist
、npm run compile
、npm run build
。
config
字段用于配置腳本中使用的環(huán)境變量,例如下面的配置,可以在腳本中使用process.env.npm_package_config_port
進(jìn)行獲取。
{ "config" : { "port" : "8080" } }
如果你的 node.js
模塊主要用于安裝到全局的命令行工具,那么該值設(shè)置為 true
,當(dāng)用戶將該模塊安裝到本地時,將得到一個警告。這個配置并不會阻止用戶安裝,而是會提示用戶防止錯誤使用而引發(fā)一些問題。
如果將 private
屬性設(shè)置為 true
,npm將拒絕發(fā)布它,這是為了防止一個私有模塊被無意間發(fā)布出去。
"publishConfig": { "registry": "https://registry.npmjs.org/" },
發(fā)布模塊時更詳細(xì)的配置,例如你可以配置只發(fā)布某個 tag
、配置發(fā)布到的私有 npm
源。更詳細(xì)的配置可以參考 npm-config
假如你開發(fā)了一個模塊,只能跑在 darwin
系統(tǒng)下,你需要保證 windows
用戶不會安裝到你的模塊,從而避免發(fā)生不必要的錯誤。
使用 os
屬性可以幫助你完成以上的需求,你可以指定你的模塊只能被安裝在某些系統(tǒng)下,或者指定一個不能安裝的系統(tǒng)黑名單:
"os" : [ "darwin", "linux" ] "os" : [ "!win32" ]
例如,我把一個測試模塊指定一個系統(tǒng)黑名單:"os" : [ "!darwin" ]
,當(dāng)我在此系統(tǒng)下安裝它時會爆出如下錯誤:
在node環(huán)境下可以使用 process.platform 來判斷操作系統(tǒng)。
和上面的 os
類似,我們可以用 cpu
屬性更精準(zhǔn)的限制用戶安裝環(huán)境:
"cpu" : [ "x64", "ia32" ] "cpu" : [ "!arm", "!mips" ]
在node環(huán)境下可以使用 process.arch 來判斷 cpu 架構(gòu)。
Nodejs
成功離不開 npm
優(yōu)秀的依賴管理系統(tǒng)。在介紹整個依賴系統(tǒng)之前,必須要了解 npm
如何管理依賴包的版本,本章將介紹 npm包
的版本發(fā)布規(guī)范、如何管理各種依賴包的版本以及一些關(guān)于包版本的最佳實(shí)踐。
你可以執(zhí)行 npm view package version
查看某個 package
的最新版本。
執(zhí)行 npm view conard versions
查看某個 package
在npm服務(wù)器上所有發(fā)布過的版本。
執(zhí)行 npm ls
可查看當(dāng)前倉庫依賴樹上所有包的版本信息。
npm包
中的模塊版本都需要遵循 SemVer
規(guī)范——由 Github
起草的一個具有指導(dǎo)意義的,統(tǒng)一的版本號表示規(guī)則。實(shí)際上就是 Semantic Version
(語義化版本)的縮寫。
SemVer規(guī)范官網(wǎng): https://semver.org/
SemVer
規(guī)范的標(biāo)準(zhǔn)版本號采用 X.Y.Z
的格式,其中 X、Y 和 Z 為非負(fù)的整數(shù),且禁止在數(shù)字前方補(bǔ)零。X 是主版本號、Y 是次版本號、而 Z 為修訂號。每個元素必須以數(shù)值來遞增。
主版本號(major
):當(dāng)你做了不兼容的API 修改
次版本號(minor
):當(dāng)你做了向下兼容的功能性新增
修訂號(patch
):當(dāng)你做了向下兼容的問題修正。
例如:1.9.1 -> 1.10.0 -> 1.11.0
當(dāng)某個版本改動比較大、并非穩(wěn)定而且可能無法滿足預(yù)期的兼容性需求時,你可能要先發(fā)布一個先行版本。
先行版本號可以加到“主版本號.次版本號.修訂號”的后面,先加上一個連接號再加上一連串以句點(diǎn)分隔的標(biāo)識符和版本編譯信息。
內(nèi)部版本(alpha
):
公測版本(beta
):
正式版本的候選版本rc
: 即 Release candiate
下面我們來看看 React
的歷史版本:
可見是嚴(yán)格按照 SemVer
規(guī)范來發(fā)版的:
版本號嚴(yán)格按照 主版本號.次版本號.修訂號
格式命名
版本是嚴(yán)格遞增的,:16.8.0 -> 16.8.1 -> 16.8.2
發(fā)布重大版本或版本改動較大時,先發(fā)布alpha
、beta
、rc
等先行版本
在修改 npm
包某些功能后通常需要發(fā)布一個新的版本,我們通常的做法是直接去修改 package.json
到指定版本。如果操作失誤,很容易造成版本號混亂,我們可以借助符合 Semver
規(guī)范的命令來完成這一操作:
npm version patch
: 升級修訂版本號
npm version minor
: 升級次版本號
npm version major
: 升級主版本號
在開發(fā)中肯定少不了對一些版本號的操作,如果這些版本號符合 SemVer
規(guī)范 ,我們可以借助用于操作版本的npm包semver
來幫助我們進(jìn)行比較版本大小、提取版本信息等操作。
Npm 也使用了該工具來處理版本相關(guān)的工作。
npm install semver
比較版本號大小
semver.gt('1.2.3', '9.8.7') // false semver.lt('1.2.3', '9.8.7') // true
判斷版本號是否符合規(guī)范,返回解析后符合規(guī)范的版本號。
semver.valid('1.2.3') // '1.2.3' semver.valid('a.b.c') // null
將其他版本號強(qiáng)制轉(zhuǎn)換成semver版本號
semver.valid(semver.coerce('v2')) // '2.0.0' semver.valid(semver.coerce('42.6.7.9.3-alpha')) // '42.6.7'
一些其他用法
semver.clean(' =v1.2.3 ') // '1.2.3' semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true semver.minVersion('>=1.0.0') // '1.0.0'
以上都是semver最常見的用法,更多詳細(xì)內(nèi)容可以查看 semver文檔:https://github.com/npm/node-semver
我們經(jīng)常看到,在 package.json
中各種依賴的不同寫法:
"dependencies": { "signale": "1.4.0", "figlet": "*", "react": "16.x", "table": "~5.4.6", "yargs": "^14.0.0" }
前面三個很容易理解:
"signale": "1.4.0"
: 固定版本號
"figlet": "*"
: 任意版本(>=0.0.0
)
"react": "16.x"
: 匹配主要版本(>=16.0.0 <17.0.0
)
"react": "16.3.x"
: 匹配主要版本和次要版本(>=16.3.0 <16.4.0
)
再來看看后面兩個,版本號中引用了 ~
和 ^
符號:
~
: 當(dāng)安裝依賴時獲取到有新版本時,安裝到 x.y.z
中 z
的最新的版本。即保持主版本號、次版本號不變的情況下,保持修訂號的最新版本。
^
: 當(dāng)安裝依賴時獲取到有新版本時,安裝到 x.y.z
中 y
和 z
都為最新版本。 即保持主版本號不變的情況下,保持次版本號、修訂版本號為最新版本。
在 package.json
文件中最常見的應(yīng)該是 "yargs": "^14.0.0"
這種格式的 依賴, 因?yàn)槲覀冊谑褂?npm install package
安裝包時,npm
默認(rèn)安裝當(dāng)前最新版本,然后在所安裝的版本號前加 ^
號。
注意,當(dāng)主版本號為 0
的情況,會被認(rèn)為是一個不穩(wěn)定版本,情況與上面不同:
主版本號和次版本號都為 0
: ^0.0.z
、~0.0.z
都被當(dāng)作固定版本,安裝依賴時均不會發(fā)生變化。
主版本號為 0
: ^0.y.z
表現(xiàn)和 ~0.y.z
相同,只保持修訂號為最新版本。
1.0.0 的版本號用于界定公共 API。當(dāng)你的軟件發(fā)布到了正式環(huán)境,或者有穩(wěn)定的API時,就可以發(fā)布1.0.0版本了。所以,當(dāng)你決定對外部發(fā)布一個正式版本的npm包時,把它的版本標(biāo)為1.0.0。
實(shí)際開發(fā)中,經(jīng)常會因?yàn)楦鞣N依賴不一致而產(chǎn)生奇怪的問題,或者在某些場景下,我們不希望依賴被更新,建議在開發(fā)中使用 package-lock.json
。
鎖定依賴版本意味著在我們不手動執(zhí)行更新的情況下,每次安裝依賴都會安裝固定版本。保證整個團(tuán)隊(duì)使用版本號一致的依賴。
每次安裝固定版本,無需計(jì)算依賴版本范圍,大部分場景下能大大加速依賴安裝時間。
使用 package-lock.json 要確保npm的版本在5.6以上,因?yàn)樵?.0 - 5.6中間,對 package-lock.json的處理邏輯進(jìn)行過幾次更新,5.6版本后處理邏輯逐漸穩(wěn)定。
關(guān)于 package-lock.json
詳細(xì)的結(jié)構(gòu),我們會在后面的章節(jié)進(jìn)行解析。
我們的目的是保證團(tuán)隊(duì)中使用的依賴一致或者穩(wěn)定,而不是永遠(yuǎn)不去更新這些依賴。實(shí)際開發(fā)場景下,我們雖然不需要每次都去安裝新的版本,仍然需要定時去升級依賴版本,來讓我們享受依賴包升級帶來的問題修復(fù)、性能提升、新特性更新。
使用 npm outdated
可以幫助我們列出有哪些還沒有升級到最新版本的依賴:
黃色表示不符合我們指定的語意化版本范圍 - 不需要升級
紅色表示符合指定的語意化版本范圍 - 需要升級
執(zhí)行 npm update
會升級所有的紅色依賴。
對外部發(fā)布一個正式版本的npm包時,把它的版本標(biāo)為1.0.0
。
某個包版本發(fā)行后,任何修改都必須以新版本發(fā)行。
版本號嚴(yán)格按照 主版本號.次版本號.修訂號
格式命名
版本號發(fā)布必須是嚴(yán)格遞增的
發(fā)布重大版本或版本改動較大時,先發(fā)布alpha、beta、rc
等先行版本
主工程依賴了很多子模塊,都是團(tuán)隊(duì)成員開發(fā)的npm
包,此時建議把版本前綴改為~
,如果鎖定的話每次子依賴更新都要對主工程的依賴進(jìn)行升級,非常繁瑣,如果對子依賴完全信任,直接開啟^
每次升級到最新版本。
主工程跑在docker
線上,本地還在進(jìn)行子依賴開發(fā)和升級,在docker
版本發(fā)布前要鎖定所有依賴版本,確保本地子依賴發(fā)布后線上不會出問題。
確保npm
的版本在5.6
以上,確保默認(rèn)開啟 package-lock.json
文件。
由初始化成員執(zhí)行 npm inatall
后,將 package-lock.json
提交到遠(yuǎn)程倉庫。不要直接提交 node_modules
到遠(yuǎn)程倉庫。
定期執(zhí)行 npm update
升級依賴,并提交 lock
文件確保其他成員同步更新依賴,不要手動更改 lock
文件。
升級依賴: 修改 package.json
文件的依賴版本,執(zhí)行 npm install
降級依賴: 直接執(zhí)行 npm install package@version
(改動package.json
不會對依賴進(jìn)行降級)
注意改動依賴后提交lock
文件
npm install
大概會經(jīng)過上面的幾個流程,這一章就來講一講各個流程的實(shí)現(xiàn)細(xì)節(jié)、發(fā)展以及為何要這樣實(shí)現(xiàn)。
我們都知道,執(zhí)行 npm install
后,依賴包被安裝到了 node_modules
,下面我們來具體了解下,npm
將依賴包安裝到 node_modules
的具體機(jī)制是什么。
在 npm
的早期版本, npm
處理依賴的方式簡單粗暴,以遞歸的形式,嚴(yán)格按照 package.json
結(jié)構(gòu)以及子依賴包的 package.json
結(jié)構(gòu)將依賴安裝到他們各自的 node_modules
中。直到有子依賴包不在依賴其他模塊。
舉個例子,我們的模塊 my-app
現(xiàn)在依賴了兩個模塊:buffer
、ignore
:
{ "name": "my-app", "dependencies": { "buffer": "^5.4.3", "ignore": "^5.1.4", } }
ignore
是一個純 JS
模塊,不依賴任何其他模塊,而 buffer
又依賴了下面兩個模塊:base64-js
、 ieee754
。
{ "name": "buffer", "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" } }
那么,執(zhí)行 npm install
后,得到的 node_modules
中模塊目錄結(jié)構(gòu)就是下面這樣的:
這樣的方式優(yōu)點(diǎn)很明顯, node_modules
的結(jié)構(gòu)和 package.json
結(jié)構(gòu)一一對應(yīng),層級結(jié)構(gòu)明顯,并且保證了每次安裝目錄結(jié)構(gòu)都是相同的。
但是,試想一下,如果你依賴的模塊非常之多,你的 node_modules
將非常龐大,嵌套層級非常之深:
在不同層級的依賴中,可能引用了同一個模塊,導(dǎo)致大量冗余。
在 Windows
系統(tǒng)中,文件路徑最大長度為260個字符,嵌套層級過深可能導(dǎo)致不可預(yù)知的問題。
為了解決以上問題,NPM
在 3.x
版本做了一次較大更新。其將早期的嵌套結(jié)構(gòu)改為扁平結(jié)構(gòu):
安裝模塊時,不管其是直接依賴還是子依賴的依賴,優(yōu)先將其安裝在 node_modules
根目錄。
還是上面的依賴結(jié)構(gòu),我們在執(zhí)行 npm install
后將得到下面的目錄結(jié)構(gòu):
此時我們?nèi)粼谀K中又依賴了 base64-js@1.0.1
版本:
{ "name": "my-app", "dependencies": { "buffer": "^5.4.3", "ignore": "^5.1.4", "base64-js": "1.0.1", } }
當(dāng)安裝到相同模塊時,判斷已安裝的模塊版本是否符合新模塊的版本范圍,如果符合則跳過,不符合則在當(dāng)前模塊的 node_modules
下安裝該模塊。
此時,我們在執(zhí)行 npm install
后將得到下面的目錄結(jié)構(gòu):
對應(yīng)的,如果我們在項(xiàng)目代碼中引用了一個模塊,模塊查找流程如下:
在當(dāng)前模塊路徑下搜索
在當(dāng)前模塊 node_modules
路徑下搜素
在上級模塊的 node_modules
路徑下搜索
...
直到搜索到全局路徑中的 node_modules
假設(shè)我們又依賴了一個包 buffer2@^5.4.3
,而它依賴了包 base64-js@1.0.3
,則此時的安裝結(jié)構(gòu)是下面這樣的:
所以 npm 3.x
版本并未完全解決老版本的模塊冗余問題,甚至還會帶來新的問題。
試想一下,你的APP假設(shè)沒有依賴 base64-js@1.0.1
版本,而你同時依賴了依賴不同 base64-js
版本的 buffer
和 buffer2
。由于在執(zhí)行 npm install
的時候,按照 package.json
里依賴的順序依次解析,則 buffer
和 buffer2
在 package.json
的放置順序則決定了 node_modules
的依賴結(jié)構(gòu):
先依賴buffer2
:
先依賴buffer
:
另外,為了讓開發(fā)者在安全的前提下使用最新的依賴包,我們在 package.json
通常只會鎖定大版本,這意味著在某些依賴包小版本更新后,同樣可能造成依賴結(jié)構(gòu)的改動,依賴結(jié)構(gòu)的不確定性可能會給程序帶來不可預(yù)知的問題。
為了解決 npm install
的不確定性問題,在 npm 5.x
版本新增了 package-lock.json
文件,而安裝方式還沿用了 npm 3.x
的扁平化的方式。
package-lock.json
的作用是鎖定依賴結(jié)構(gòu),即只要你目錄下有 package-lock.json
文件,那么你每次執(zhí)行 npm install
后生成的 node_modules
目錄結(jié)構(gòu)一定是完全相同的。
例如,我們有如下的依賴結(jié)構(gòu):
{ "name": "my-app", "dependencies": { "buffer": "^5.4.3", "ignore": "^5.1.4", "base64-js": "1.0.1", } }
在執(zhí)行 npm install
后生成的 package-lock.json
如下:
{ "name": "my-app", "version": "1.0.0", "dependencies": { "base64-js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz", "integrity": "sha1-aSbRsZT7xze47tUTdW3i/Np+pAg=" }, "buffer": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==", "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" }, "dependencies": { "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" } } }, "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "ignore": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==" } } }
我們來具體看看上面的結(jié)構(gòu):
最外面的兩個屬性 name
、version
同 package.json
中的 name
和 version
,用于描述當(dāng)前包名稱和版本。
dependencies
是一個對象,對象和 node_modules
中的包結(jié)構(gòu)一一對應(yīng),對象的 key
為包名稱,值為包的一些描述信息:
version
:包版本 —— 這個包當(dāng)前安裝在 node_modules
中的版本
resolved
:包具體的安裝來源
integrity
:包 hash
值,基于 Subresource Integrity
來驗(yàn)證已安裝的軟件包是否被改動過、是否已失效
requires
:對應(yīng)子依賴的依賴,與子依賴的 package.json
中 dependencies
的依賴項(xiàng)相同。
dependencies
:結(jié)構(gòu)和外層的 dependencies
結(jié)構(gòu)相同,存儲安裝在子依賴 node_modules
中的依賴包。
這里注意,并不是所有的子依賴都有 dependencies
屬性,只有子依賴的依賴和當(dāng)前已安裝在根目錄的 node_modules
中的依賴沖突之后,才會有這個屬性。
例如,回顧下上面的依賴關(guān)系:
我們在 my-app
中依賴的 base64-js@1.0.1
版本與 buffer
中依賴的 base64-js@^1.0.2
發(fā)生沖突,所以 base64-js@1.0.1
需要安裝在 buffer
包的 node_modules
中,對應(yīng)了 package-lock.json
中 buffer
的 dependencies
屬性。這也對應(yīng)了 npm
對依賴的扁平化處理方式。
所以,根據(jù)上面的分析, package-lock.json
文件 和 node_modules
目錄結(jié)構(gòu)是一一對應(yīng)的,即項(xiàng)目目錄下存在 package-lock.json
可以讓每次安裝生成的依賴目錄結(jié)構(gòu)保持相同。
另外,項(xiàng)目中使用了 package-lock.json
可以顯著加速依賴安裝時間。
我們使用 npm i --timing=true --loglevel=verbose
命令可以看到 npm install
的完整過程,下面我們來對比下使用 lock
文件和不使用 lock
文件的差別。在對比前先清理下npm
緩存。
不使用 lock
文件:
使用 lock
文件:
可見, package-lock.json
中已經(jīng)緩存了每個包的具體版本和下載鏈接,不需要再去遠(yuǎn)程倉庫進(jìn)行查詢,然后直接進(jìn)入文件完整性校驗(yàn)環(huán)節(jié),減少了大量網(wǎng)絡(luò)請求。
開發(fā)系統(tǒng)應(yīng)用時,建議把 package-lock.json
文件提交到代碼版本倉庫,從而保證所有團(tuán)隊(duì)開發(fā)者以及 CI
環(huán)節(jié)可以在執(zhí)行 npm install
時安裝的依賴版本都是一致的。
在開發(fā)一個 npm
包 時,你的 npm
包 是需要被其他倉庫依賴的,由于上面我們講到的扁平安裝機(jī)制,如果你鎖定了依賴包版本,你的依賴包就不能和其他依賴包共享同一 semver
范圍內(nèi)的依賴包,這樣會造成不必要的冗余。所以我們不應(yīng)該把package-lock.json
文件發(fā)布出去( npm
默認(rèn)也不會把 package-lock.json
文件發(fā)布出去)。
在執(zhí)行 npm install
或 npm update
命令下載依賴后,除了將依賴包安裝在node_modules
目錄下外,還會在本地的緩存目錄緩存一份。
通過 npm config get cache
命令可以查詢到:在 Linux
或 Mac
默認(rèn)是用戶主目錄下的 .npm/_cacache
目錄。
在這個目錄下又存在兩個目錄:content-v2
、index-v5
,content-v2
目錄用于存儲 tar
包的緩存,而index-v5
目錄用于存儲tar
包的 hash
。
npm 在執(zhí)行安裝時,可以根據(jù) package-lock.json
中存儲的 integrity、version、name
生成一個唯一的 key
對應(yīng)到 index-v5
目錄下的緩存記錄,從而找到 tar
包的 hash
,然后根據(jù) hash
再去找緩存的 tar
包直接使用。
我們可以找一個包在緩存目錄下搜索測試一下,在 index-v5
搜索一下包路徑:
grep "https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz" -r index-v5
然后我們將json格式化:
{ "key": "pacote:version-manifest:https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz:sha1-aSbRsZT7xze47tUTdW3i/Np+pAg=", "integrity": "sha512-C2EkHXwXvLsbrucJTRS3xFHv7Mf/y9klmKDxPTE8yevCoH5h8Ae69Y+/lP+ahpW91crnzgO78elOk2E6APJfIQ==", "time": 1575554308857, "size": 1, "metadata": { "id": "base64-js@1.0.1", "manifest": { "name": "base64-js", "version": "1.0.1", "engines": { "node": ">= 0.4" }, "dependencies": {}, "optionalDependencies": {}, "devDependencies": { "standard": "^5.2.2", "tape": "4.x" }, "bundleDependencies": false, "peerDependencies": {}, "deprecated": false, "_resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz", "_integrity": "sha1-aSbRsZT7xze47tUTdW3i/Np+pAg=", "_shasum": "6926d1b194fbc737b8eed513756de2fcda7ea408", "_shrinkwrap": null, "bin": null, "_id": "base64-js@1.0.1" }, "type": "finalized-manifest" } }
上面的 _shasum
屬性 6926d1b194fbc737b8eed513756de2fcda7ea408
即為 tar
包的 hash
, hash
的前幾位 6926
即為緩存的前兩層目錄,我們進(jìn)去這個目錄果然找到的壓縮后的依賴包:
以上的緩存策略是從 npm v5 版本開始的,在 npm v5 版本之前,每個緩存的模塊在 ~/.npm 文件夾中以模塊名的形式直接存儲,儲存結(jié)構(gòu)是{cache}/{name}/{version}。
npm
提供了幾個命令來管理緩存數(shù)據(jù):
npm cache add
:官方解釋說這個命令主要是 npm
內(nèi)部使用,但是也可以用來手動給一個指定的 package 添加緩存。
npm cache clean
:刪除緩存目錄下的所有數(shù)據(jù),為了保證緩存數(shù)據(jù)的完整性,需要加上 --force
參數(shù)。
npm cache verify
:驗(yàn)證緩存數(shù)據(jù)的有效性和完整性,清理垃圾數(shù)據(jù)。
基于緩存數(shù)據(jù),npm 提供了離線安裝模式,分別有以下幾種:
--prefer-offline
: 優(yōu)先使用緩存數(shù)據(jù),如果沒有匹配的緩存數(shù)據(jù),則從遠(yuǎn)程倉庫下載。
--prefer-online
: 優(yōu)先使用網(wǎng)絡(luò)數(shù)據(jù),如果網(wǎng)絡(luò)數(shù)據(jù)請求失敗,再去請求緩存數(shù)據(jù),這種模式可以及時獲取最新的模塊。
--offline
: 不請求網(wǎng)絡(luò),直接使用緩存數(shù)據(jù),一旦緩存數(shù)據(jù)不存在,則安裝失敗。
上面我們多次提到了文件完整性,那么什么是文件完整性校驗(yàn)?zāi)兀?/p>
在下載依賴包之前,我們一般就能拿到 npm
對該依賴包計(jì)算的 hash
值,例如我們執(zhí)行 npm info
命令,緊跟 tarball
(下載鏈接) 的就是 shasum
(hash
) :
用戶下載依賴包到本地后,需要確定在下載過程中沒有出現(xiàn)錯誤,所以在下載完成之后需要在本地在計(jì)算一次文件的 hash
值,如果兩個 hash
值是相同的,則確保下載的依賴是完整的,如果不同,則進(jìn)行重新下載。
好了,我們再來整體總結(jié)下上面的流程:
檢查 .npmrc
文件:優(yōu)先級為:項(xiàng)目級的 .npmrc
文件 > 用戶級的 .npmrc
文件> 全局級的 .npmrc
文件 > npm 內(nèi)置的 .npmrc
文件
檢查項(xiàng)目中有無 lock
文件。
無 lock
文件:
不存在緩存:
存在緩存:將緩存按照依賴結(jié)構(gòu)解壓到 node_modules
將下載的包復(fù)制到 npm
緩存目錄
將下載的包按照依賴結(jié)構(gòu)解壓到 node_modules
重新下載
從 npm
遠(yuǎn)程倉庫下載包
校驗(yàn)包的完整性
校驗(yàn)不通過:
校驗(yàn)通過:
構(gòu)建依賴樹時,不管其是直接依賴還是子依賴的依賴,優(yōu)先將其放置在 node_modules
根目錄。
當(dāng)遇到相同模塊時,判斷已放置在依賴樹的模塊版本是否符合新模塊的版本范圍,如果符合則跳過,不符合則在當(dāng)前模塊的 node_modules
下放置該模塊。
注意這一步只是確定邏輯上的依賴樹,并非真正的安裝,后面會根據(jù)這個依賴結(jié)構(gòu)去下載或拿到緩存中的依賴包
從 npm
遠(yuǎn)程倉庫獲取包信息
根據(jù) package.json
構(gòu)建依賴樹,構(gòu)建過程:
在緩存中依次查找依賴樹中的每個包
將包解壓到 node_modules
生成 lock
文件
有 lock
文件:
檢查 package.json
中的依賴版本是否和 package-lock.json
中的依賴有沖突。
如果沒有沖突,直接跳過獲取包信息、構(gòu)建依賴樹過程,開始在緩存中查找包信息,后續(xù)過程相同
上面的過程簡要描述了 npm install
的大概過程,這個過程還包含了一些其他的操作,例如執(zhí)行你定義的一些生命周期函數(shù),你可以執(zhí)行 npm install package --timing=true --loglevel=verbose
來查看某個包具體的安裝流程和細(xì)節(jié)。
yarn
是在 2016
年發(fā)布的,那時 npm
還處于 V3
時期,那時候還沒有 package-lock.json
文件,就像上面我們提到的:不穩(wěn)定性、安裝速度慢等缺點(diǎn)經(jīng)常會受到廣大開發(fā)者吐槽。此時,yarn
誕生:
上面是官網(wǎng)提到的 yarn
的優(yōu)點(diǎn),在那個時候還是非常吸引人的。當(dāng)然,后來 npm
也意識到了自己的問題,進(jìn)行了很多次優(yōu)化,在后面的優(yōu)化(lock
文件、緩存、默認(rèn)-s...)中,我們多多少少能看到 yarn
的影子,可見 yarn
的設(shè)計(jì)還是非常優(yōu)秀的。
yarn
也是采用的是 npm v3
的扁平結(jié)構(gòu)來管理依賴,安裝依賴后默認(rèn)會生成一個 yarn.lock
文件,還是上面的依賴關(guān)系,我們看看 yarn.lock
的結(jié)構(gòu):
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 base64-js@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.0.1.tgz#6926d1b194fbc737b8eed513756de2fcda7ea408" integrity sha1-aSbRsZT7xze47tUTdW3i/Np+pAg= base64-js@^1.0.2: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== buffer@^5.4.3: version "5.4.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115" integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== ignore@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==
可見其和 package-lock.json
文件還是比較類似的,還有一些區(qū)別就是:
package-lock.json
使用的是 json
格式,yarn.lock
使用的是一種自定義格式
yarn.lock
中子依賴的版本號不是固定的,意味著單獨(dú)又一個 yarn.lock
確定不了 node_modules
目錄結(jié)構(gòu),還需要和 package.json
文件進(jìn)行配合。而 package-lock.json
只需要一個文件即可確定。
yarn
的緩策略看起來和 npm v5
之前的很像,每個緩存的模塊被存放在獨(dú)立的文件夾,文件夾名稱包含了模塊名稱、版本號等信息。使用命令 yarn cache dir
可以查看緩存數(shù)據(jù)的目錄:
yarn
默認(rèn)使用prefer-online
模式,即優(yōu)先使用網(wǎng)絡(luò)數(shù)據(jù),如果網(wǎng)絡(luò)數(shù)據(jù)請求失敗,再去請求緩存數(shù)據(jù)。
關(guān)于“node.js npm的包管理機(jī)制是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點(diǎn)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。