package.json 與 package-lock.json 的關(guān)系

模塊化開發(fā)在前端越來越流行,使用 node 和 npm 可以很方便的下載管理項(xiàng)目所需的依賴模塊。package.json 用來描述項(xiàng)目及項(xiàng)目所依賴的模塊信息。

那 package-lock.json 和 package.json 有啥關(guān)系和聯(lián)系呢?

package.json
管理包
大家都知道,**package.json 用來描述項(xiàng)目及項(xiàng)目所依賴的模塊信息。**,就是幫我們管理項(xiàng)目中的依賴包的,讓我們遠(yuǎn)離了依賴地獄。

通過 npm 管理,使用一些簡(jiǎn)單的命令,自動(dòng)生成package.json, 安裝包依賴關(guān)系都由package.json來管理,我們幾乎不必考慮它們。

語義版本控制
首先我們先來了解下依賴包的版本號(hào)的定義

版本號(hào)由三部分組成:major.minor.patch,主版本號(hào).次版本號(hào).修補(bǔ)版本號(hào)。

例如:1.2.3,主要版本1,次要版本2,補(bǔ)丁3。

補(bǔ)丁中的更改表示不會(huì)破壞任何內(nèi)容的錯(cuò)誤修復(fù)。
次要版本的更改表示不會(huì)破壞任何內(nèi)容的新功能。
主要版本的更改代表了一個(gè)破壞兼容性的大變化。如果用戶不適應(yīng)主要版本更改,則內(nèi)容將無法正常工作。
安裝依賴包的版本如何指定
相信大家都會(huì)經(jīng)歷過,我們安裝一些依賴包的時(shí)候,版本號(hào)前面都會(huì)帶 ^ 或者 ~ 的符號(hào),這兩個(gè)符號(hào)代表什么意思呢?

~ 會(huì)匹配最近的小版本依賴包,比如 ~1.2.3 會(huì)匹配所有 1.2.x 版本,但是不包括 1.3.0

^ 會(huì)匹配最新的大版本依賴包,比如 ^1.2.3 會(huì)匹配所有 1.x.x 的包,包括 1.3.0,但是不包括 2.0.0

* 安裝最新版本的依賴包,比如 *1.2.3 會(huì)匹配 x.x.x,

那么該如何選擇呢?當(dāng)然你可以指定特定的版本號(hào),直接寫1.2.3,前面什么前綴都沒有,這樣固然沒問題,但是如果依賴包發(fā)布新版本修復(fù)了一些小bug,那么需要手動(dòng)修改package.json文件;~ 和 ^ 則可以解決這個(gè)問題。

但是需要注意 ^ 版本更新可能比較大,會(huì)造成項(xiàng)目代碼錯(cuò)誤,所以 建議使用 ~ 來標(biāo)記版本號(hào),這樣可以保證項(xiàng)目不會(huì)出現(xiàn)大的問題,也能保證包中的小bug可以得到修復(fù)。

版本號(hào)寫 *,這意味著安裝最新版本的依賴包,但缺點(diǎn)同上,可能會(huì)造成版本不兼容,慎用!

多人開發(fā)時(shí)依賴包安裝的問題
看了上面版本號(hào)的指定后,我們可以知道,當(dāng)我們使用了 ^ 或者 ~ 來控制依賴包版本號(hào)的時(shí)候 ,多人開發(fā),就有可能存在大家安裝的依賴包版本不一樣的情況,就會(huì)存在項(xiàng)目運(yùn)行的結(jié)果不一樣。

我們舉個(gè)例子:

假設(shè)我們中安裝了 vue, 當(dāng)我們運(yùn)行安裝 npm install vue -save 的時(shí)候,在項(xiàng)目中的package.json 的 vue 版本是  vue: ^3.0.0, 我們電腦安裝的vue版本就是 3.0.0 版本,我們把項(xiàng)目代碼提交后,過了一段時(shí)間,vue 發(fā)布了新版本 3.0.1,這時(shí)新來一個(gè)同事,從新 git clone 克隆項(xiàng)目,執(zhí)行 npm install安裝的時(shí)候,在他電腦的vue版本就是 3.0.1了,因?yàn)閊只是鎖了主要版本,這樣我們電腦中的vue版本就會(huì)不一樣,從理論上講(大家都遵循語義版本控制的話),它們應(yīng)該仍然是兼容的,但也許 bugfix 會(huì)影響我們正在使用的功能,而且當(dāng)使用vue版本3.0.0和3.0.1運(yùn)行時(shí),我們的應(yīng)用程序會(huì)產(chǎn)生不同的結(jié)果。

大家思考思考,這樣的話,不同人電腦安裝的依賴版項(xiàng)目,是不是都有可能不一樣,就會(huì)導(dǎo)致每個(gè)人電腦運(yùn)行的應(yīng)用程序產(chǎn)生不同的結(jié)果。就會(huì)存在bug的隱患。

這時(shí)也許有同學(xué)想到,那么我們?cè)趐ackage.json上面鎖死依賴包的版本號(hào)不就可以了? 直接寫 vue: 3.0.0鎖死,這樣大家安裝vue的版本都是3.0.0版本了。

這個(gè)想法固然是不錯(cuò)的,但是你只能控制你自己的項(xiàng)目鎖死版本號(hào),那你項(xiàng)目中依賴包的依賴包呢?你怎么控制限制別人鎖死版本號(hào)呢?

為了解決這個(gè)不同人電腦安裝的所有依賴版本都是一致的,確保項(xiàng)目代碼在安裝所執(zhí)行的運(yùn)行結(jié)果都一樣,這時(shí) package-lock.json 就應(yīng)運(yùn)而生了。

package-lock.json
package-lock.json 是在 npm(^5.x.x.x)后才有,中途有幾次更改

介紹
官方文檔是這樣解釋的:package-lock.json 它會(huì)在 npm 更改 node_modules 目錄樹 或者 package.json 時(shí)自動(dòng)生成的 ,它準(zhǔn)確的描述了當(dāng)前項(xiàng)目npm包的依賴樹,并且在隨后的安裝中會(huì)根據(jù) package-lock.json 來安裝,保證是相同的一個(gè)依賴樹,不考慮這個(gè)過程中是否有某個(gè)依賴有小版本的更新。

它的產(chǎn)生就是來對(duì)整個(gè)依賴樹進(jìn)行版本固定的(鎖死)。

當(dāng)我們?cè)谝粋€(gè)項(xiàng)目中npm install時(shí)候,會(huì)自動(dòng)生成一個(gè)package-lock.json文件,和package.json在同一級(jí)目錄下。package-lock.json記錄了項(xiàng)目的一些信息和所依賴的模塊。這樣在每次安裝都會(huì)出現(xiàn)相同的結(jié)果. 不管你在什么機(jī)器上面或什么時(shí)候安裝。

當(dāng)我們下次再npm install時(shí)候,npm 發(fā)現(xiàn)如果項(xiàng)目中有 package-lock.json 文件,會(huì)根據(jù) package-lock.json 里的內(nèi)容來處理和安裝依賴而不再根據(jù) package.json。

注意,使用cnpm install時(shí)候,并不會(huì)生成 package-lock.json 文件,也不會(huì)根據(jù) package-lock.json 來安裝依賴包,還是會(huì)使用 package.json 來安裝。

package-lock.json 生成邏輯
簡(jiǎn)單描述一下 package-lock.json 生成的邏輯。假設(shè)我們現(xiàn)在有三個(gè) package,在項(xiàng)目 lock-test中,安裝依賴A,A項(xiàng)目面有B,B項(xiàng)目面有C

// package lock-test
{ "name": "lock-test", "dependencies": { "A": "^1.0.0" }}
// package A
{ "name": "A", "version": "1.0.0", "dependencies": { "B": "^1.0.0" }}
// package B
{ "name": "B", "version": "1.0.0", "dependencies": { "C": "^1.0.0" }}
// package C
{ "name": "C", "version": "1.0.0" }

在這種情況下 package-lock.json, 會(huì)生成類似下面鋪平的結(jié)構(gòu)

// package-lock.json
{
    "name": "lock-test",  
    "version": "1.0.0",  
    "dependencies": {    
        "A": { "version": "1.0.0" },
        "B": { "version": "1.0.0" },
        "C": { "version": "1.0.0" }  
    }
}

如果后續(xù)無論是直接依賴的 A 發(fā)版,或者間接依賴的B, C 發(fā)版,只要我們不動(dòng) package.json, package-lock.json 都不會(huì)重新生成。

A 發(fā)布了新版本 1.1.0,雖然我們 package.json 寫的是 ^1.0.0 但是因?yàn)?package-lock.json 的存在,npm i 并不會(huì)自動(dòng)升級(jí),

我們可以手動(dòng)運(yùn)行 npm i A@1.1.0 來實(shí)現(xiàn)升級(jí)。

因?yàn)?1.1.0 package-lock.json 里記錄的 A@1.0.0 是不一致的,因此會(huì)更新 package-lock.json 里的 A 的版本為 1.1.0。

B 發(fā)布了新版本 1.0.1, 1.0.2, 1.1.0, 此刻如果我們不做操作是不會(huì)自動(dòng)升級(jí) B 的版本的,但如果此刻 A 發(fā)布了 1.1.1,雖然并沒有升級(jí) B 的依賴,但是如果我們項(xiàng)目里升級(jí) A@1.1.1,此時(shí) package-lock.json 里會(huì)把 B 直接升到 1.1.0 ,因?yàn)榇丝蘜1.0.0的最新版本就是 1.1.0。






經(jīng)過這些操作后 項(xiàng)目 lock-test 的 package.json 變成

// package
lock-test{ "dependencies": { "A": "^1.1.0" }}

對(duì)應(yīng)的 package-lock.json 文件

{  
    "name": "lock-test",  
    "version": "1.0.0",
    "dependencies": {  
        "A": { "version": "1.1.0" },
        "B": { "version": "1.1.0" },
        "C": { "version": "1.0.0" }
    }
}

這個(gè)時(shí)候我們將 B 加入我們 lock-test 項(xiàng)目的依賴, B@^1.0.0,package.json如下

{ "dependencies": { "A": "^1.1.0", "B": "^1.0.0" }}

我們執(zhí)行這個(gè)操作后,package-lock.json 并沒有被改變,因?yàn)楝F(xiàn)在 package-lock.json 里 B@1.1.0 滿足 ^1.0.0 的要求

但是如果我們將 B 的版本固定到 2.x 版本, package-lock.json 就會(huì)發(fā)生改變

{ "dependencies": { "A": "^1.1.0", "B": "^2.0.0" }}

因?yàn)榇嬖诹藘蓚€(gè)沖突的B版本,package-lock.json 文件會(huì)變成如下形式

{  
    "name": "lock-test",
    "version": "1.0.0",  
    "dependencies": {    
        "A": {      
            "version": "1.1.0",      
            "dependencies": {        
                "B": { "version": "1.1.0" }      
            }    
        },    
        "B": { "version": "2.0.0" },    
        "C": { "version": "1.0.0" }  
    }
}

因?yàn)?B 的版本出現(xiàn)了沖突,npm 使用嵌套描述了這種行為

我們實(shí)際開發(fā)中并不需要關(guān)注這種生成的算法邏輯,我們只需要了解,package-lock.json 的生成邏輯是為了能夠精準(zhǔn)的反映出我們 node_modules 的結(jié)構(gòu),并保證能夠這種結(jié)構(gòu)被還原。

package-lock.json 可能被意外更改的原因
package.json 文件修改了
挪動(dòng)了包的位置
將部分包的位置從 dependencies 移動(dòng)到 devDependencies 這種操作,雖然包未變,但是也會(huì)影響 package-lock.json,會(huì)將部分包的 dev 字段設(shè)置為 true

registry 的影響
經(jīng)過實(shí)際使用發(fā)現(xiàn),如果我們 node_modules 文件夾下的包中下載時(shí),就算版本一樣,安裝源 registry 不同,執(zhí)行 npm i 時(shí)也會(huì)修改 package-lock.json

可能還存在其他的原因,但是 package-lock.json 是不會(huì)無緣無故被更改的,一定是因?yàn)?package.json 或者 node_modules 被更改了,因?yàn)?正如上面提到的 package-lock.json 為了能夠精準(zhǔn)的反映出我們 node_modules 的結(jié)構(gòu)

開發(fā)的建議
一般情況下 npm install 是可以的,他能保證根據(jù) package-lock.json 還原出開發(fā)時(shí)的 node_modules。

但是為了防止出現(xiàn)剛剛提到的意外情況,除非涉及到對(duì)包的調(diào)整,其他情況下建議使用 npm ci 來安裝依賴,會(huì)避免異常的修改 package-lock.json,

持續(xù)集成工具中更推薦是用 npm ci,保證構(gòu)建環(huán)境的準(zhǔn)確性,npm i 和 npm ci 的區(qū)別 可以參考官方文檔 npm-ci

參考文章:

我的package-lock.json被誰改了?

npm install 生成的package-lock.json是什么文件?有什么用?

作者:阿離王

鏈接:https://juejin.cn/post/7078233610683170824

作者:阿離王


歡迎關(guān)注微信公眾號(hào) :前端Q