原文鏈接:https://www.cnblogs.com/yalong/p/15013880.html
package.json 跟 package-lock.json 的知識點挺多的, 這里只聊一聊部分知識點
先看下dependencies與devDependencies
npm install 安裝依賴的時候,可以通過如下方式,把依賴寫入package.json
npm install --save 或者 npm install -S
npm install --save-dev 或者 npm install -D
dependencies與devDependencies的區別:
devDependencies下列出的模塊,是我們開發時用的依賴項,像一些進行單元測試之類的包,比如jest,我們用寫單元測試,它們不會被部署到生產環境。dependencies下的模塊,則是我們生產環境中需要的依賴,即正常運行該包時所需要的依賴項。
記着這句: "正常運行該包時所需要的依賴項"
后面的package-lock.json 中的 dependencies 對應的就是package.json中的 dependencies
Package.json 語義化版本
使用第三方依賴時,通常需要指定依賴的版本范圍,比如
"dependencies": {
"antd": "3.1.2",
"react": "~16.0.1",
"redux": "^3.7.2",
"lodash": "*"
}
上面的 package.json 文件表明,項目中使用的 antd 的版本號是 3.1.2,但是 3.1.1 和 3.1.2、3.0.1、2.1.1 之間有什么不同呢。
語義化版本規則規定,版本格式為:主版本號.次版本號.修訂號
,並且版本號的遞增規則如下:
- 主版本號:當你做了不兼容的 API 修改
- 次版本號:當你做了向下兼容的功能性新增
- 修訂號:當你做了向下兼容的問題修正
主版本號的更新通常意味着大的修改和更新,升級主版本后可能會使你的程序報錯,因此升級主版本號需謹慎,但是這往往也會帶來更好的性能和體驗。
次版本號的更新則通常意味着新增了某些特性,比如 antd 的版本從 3.1.1 升級到 3.1.2,之前的 Select 組件不支持搜索功能,升級之后支持了搜索。
修訂號的更新則往往意味着進行了一些 bug 修復。因此次版本號和修訂號應該保持更新,這樣能讓你之前的代碼不會報錯還能獲取到最新的功能特性。
但是,往往我們不會指定依賴的具體版本,而是指定版本范圍,比如上面的 package.json 文件里的 react、redux 以及 lodash,這三個依賴分別使用了三個符號來表明依賴的版本范圍。語義化版本范圍規定:
- ~:只升級修訂號
- ^:升級次版本號和修訂號
- *:升級到最新版本
因此,上面的 package.json 文件安裝的依賴版本范圍如下:
react@~16.0.1:>=react@16.0.1 && < react@16.1.0
redux@^3.7.2:>=redux@3.7.2 && < redux@4.0.0
lodash@*:lodash@latest
語義化版本規則定義了一種理想的版本號更新規則,希望所有的依賴更新都能遵循這個規則,但是往往會有許多依賴不是嚴格遵循這些規定的。
因此,如何管理好這些依賴,尤其是這些依賴的版本就顯得尤為重要,否則一不小心就會陷入因依賴版本不一致導致的各種問題中。
npm的歷史變遷
嵌套結構
我們都知道,執行 npm install 后,依賴包被安裝到了 node_modules ,下面我們來具體了解下,npm 將依賴包安裝到 node_modules 的具體機制是什么。
在 npm 的早期版本, npm 處理依賴的方式簡單粗暴,以遞歸的形式,嚴格按照 package.json 結構以及子依賴包的 package.json 結構將依賴安裝到他們各自的 node_modules 中。直到有子依賴包不在依賴其他模塊。
舉個例子,我們的模塊 my-app 現在依賴了兩個模塊: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"
}
}
那么,執行 npm install 后,得到的 node_modules 中模塊目錄結構就是下面這樣的:
這樣的方式優點很明顯, node_modules 的結構和 package.json 結構一一對應,層級結構明顯,並且保證了每次安裝目錄結構都是相同的。
但是,試想一下,如果你依賴的模塊非常之多,你的 node_modules 將非常龐大,嵌套層級非常之深:
- 在不同層級的依賴中,可能引用了同一個模塊,導致大量冗余。
- 在 Windows 系統中,文件路徑最大長度為260個字符,嵌套層級過深可能導致不可預知的問題。
扁平結構
為了解決以上問題,NPM 在 3.x 版本做了一次較大更新。其將早期的嵌套結構改為扁平結構:
- 安裝模塊時,不管其是直接依賴還是子依賴的依賴,優先將其安裝在 node_modules 根目錄。
還是上面的依賴結構,我們在執行 npm install 后將得到下面的目錄結構:
此時我們若在模塊中又依賴了 base64-js@1.0.1 版本:
{
"name": "my-app",
"dependencies": {
"buffer": "^5.4.3",
"ignore": "^5.1.4",
"base64-js": "1.0.1",
}
}
- 當安裝到相同模塊時,判斷已安裝的模塊版本是否符合新模塊的版本范圍,如果符合則跳過,不符合則在當前模塊的 node_modules 下安裝該模塊。
此時,我們在執行 npm install 后將得到下面的目錄結構:
對應的,如果我們在項目代碼中引用了一個模塊,模塊查找流程如下:
- 在當前模塊路徑下搜索
- 在當前模塊 node_modules 路徑下搜素
- 在上級模塊的 node_modules 路徑下搜索
- ...
- 直到搜索到全局路徑中的 node_modules
假設我們又依賴了一個包 buffer2@^5.4.3,而它依賴了包 base64-js@1.0.3,則此時的安裝結構是下面這樣的:
所以 npm 3.x 版本並未完全解決老版本的模塊冗余問題,甚至還會帶來新的問題。
另外,為了讓開發者在安全的前提下使用最新的依賴包,我們在 package.json 通常只會鎖定大版本,這意味着在某些依賴包小版本更新后,同樣可能造成依賴結構的改動,依賴結構的不確定性可能會給程序帶來不可預知的問題。
Lock文件(package-lock.json)
為了解決 npm install 的不確定性問題,在 npm 5.x 版本新增了 package-lock.json 文件,而安裝方式還沿用了 npm 3.x 的扁平化的方式。
package-lock.json 的作用是鎖定依賴結構,即只要你目錄下有 package-lock.json 文件,那么你每次執行 npm install 后生成的 node_modules 目錄結構一定是完全相同的。
例如,我們有如下的依賴結構:
{
"name": "my-app",
"dependencies": {
"buffer": "^5.4.3",
"ignore": "^5.1.4",
"base64-js": "1.0.1",
}
}
在執行 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=="
}
}
}
我們來具體看看上面的結構:
最外面的兩個屬性 name 、version 同 package.json 中的 name 和 version ,用於描述當前包名稱和版本。
dependencies 是一個對象,對象和 node_modules 中的包結構一一對應,對象的 key 為包名稱,值為包的一些描述信息:
- version:包版本 —— 這個包當前安裝在 node_modules 中的版本
- resolved:包具體的安裝來源
- integrity:包 hash 值,基於 Subresource Integrity 來驗證已安裝的軟件包是否被改動過、是否已失效
- requires:對應子依賴的依賴,與子依賴的 package.json 中 dependencies的依賴項相同。
- dependencies:結構和外層的 dependencies 結構相同,存儲安裝在子依賴 node_modules 中的依賴包。
這里注意,並不是所有的子依賴都有 dependencies 屬性,只有子依賴的依賴和當前已安裝在根目錄的 node_modules 中的依賴沖突之后,才會有這個屬性。
例如,回顧下上面的依賴關系:
我們在 my-app 中依賴的 base64-js@1.0.1 版本與 buffer 中依賴的 base64-js@^1.0.2 發生沖突,所以 base64-js@1.0.1 需要安裝在 buffer 包的 node_modules 中,對應了 package-lock.json 中 buffer 的 dependencies 屬性。這也對應了 npm 對依賴的扁平化處理方式。
所以,根據上面的分析, package-lock.json 文件 和 node_modules 目錄結構是一一對應的,即項目目錄下存在 package-lock.json 可以讓每次安裝生成的依賴目錄結構保持相同。
package-lock.json使用建議
開發系統應用時,建議把 package-lock.json 文件提交到代碼版本倉庫,從而保證所有團隊開發者以及 CI 環節可以在執行 npm install 時安裝的依賴版本都是一致的。
在開發一個 npm包 時,你的 npm包 是需要被其他倉庫依賴的,由於上面我們講到的扁平安裝機制,如果你鎖定了依賴包版本,你的依賴包就不能和其他依賴包共享同一 semver 范圍內的依賴包,這樣會造成不必要的冗余。所以我們不應該把package-lock.json 文件發布出去( npm 默認也不會把 package-lock.json 文件發布出去,當然如果非要用package-lock.json也是可以的)。