一文帶你瞭解npm的原理

2022-08-09 10:00:26
npm 是 JavaScript世界的包管理工具,並且是 平臺的預設包管理工具。通過 npm可以安裝、共用、分發程式碼,管理專案依賴關係。本篇文章帶大家瞭解一下npm的原理,希望對大家有所幫助!

npm的原理

npm據稱成為世界最大的包管理器?原因真的只是使用者友好?

一、npm init

用來初始化一個簡單的package.json檔案。package.json檔案用來定義一個package的描述檔案。

1、npm init的執行的預設行為

執行npm init --yes,全部使用預設的值。

2、 自定義npm init行為

npm init命令的原理是:呼叫指令碼,輸出一個初始化的package.json檔案。

獲取使用者輸入使用prompt()方法。

二、依賴包安裝

npm的核心功能:依賴管理。執行npm i從package.json中dependencies和devDependencies將依賴包安裝到當前目錄的node_modules資料夾中。

2.1、package定義

npm i 就可以安裝一個包。通常package就是我們需要安裝的包名,預設設定下npm會從預設的源(Registry)中查詢該包名的對應的包地址,並且下載安裝。 還可以是一個指向有效包名的http url/git url/資料夾路徑。

package的準確定義,符合以下a)到g)其中一個條件,他就是一個package:

1.png

package的準確定義

2.2、安裝本地包/遠端git倉庫包

共用依賴包,並非非要把包釋出到npm源上才能使用。

1)、場景1:本地模組參照

開發中避免不了模組之間呼叫,開發中,我們把頻繁呼叫的設定模組放在根目錄,然後如果有很多層級目錄,後來參照

const config = require(''../../../../..config)

這樣的路徑參照不利於程式碼重構。這時候我們需要考慮把這個模組分離出來供其他模組共用。比如config.js可以封裝成一個package放到node_modules目錄下。

不需要手動拷貝或者建立軟連線到node_modules目錄,npm 有自己的解決方案:

方案:

1、新增config資料夾,將config.js移入資料夾,名字修改為index.js,建立package.json定義config包

{ 
    "name": "config", 
    "main": "index.js", 
    "version": "0.1.0" 
}

2、在專案的package.json新增依賴項,然後執行npm i。

{ 
  "dependencies": { 
    "config":"file: ./config" 
  } 
}

檢視 node_modules 目錄我們會發現多出來一個名為 config,指向上層 config/ 資料夾的軟連結。這是因為 npm 識別 file: 協定的url,得知這個包需要直接從檔案系統中獲取,會自動建立軟連結到 node_modules 中,完成「安裝」過程。

2)、場景2:私有git共用package

團隊內會有一些程式碼/公用庫需要在團隊內不同專案間共用,但可能由於包含了敏感內容。

我們可以簡單的將被依賴的包託管到私有的git倉庫中,然後將git url儲存到dependencies中。npm會直接呼叫系統的git命令從git倉庫拉取包的內容到node_modules中。

npm支援的git url格式:

<protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]

git 路徑後可以使用 # 指定特定的 git branch/commit/tag, 也可以 #semver: 指定特定的 semver range.

比如:

git+ssh://[email protected]:npm/npm.git#v1.0.27 
git+ssh://[email protected]:npm/npm#semver:^5.0 
git+https://[email protected]/npm/npm.git 
git://github.com/npm/npm.git#v1.0.27

3)、場景3:開源package問題修復

此時我們可以手動進入 node_modules 目錄下修改相應的包內容,也許修改了一行程式碼就修復了問題。但是這種做法非常不明智!

方案:

fork原作者的git庫,在自己的repo修復問題,然後將dependencies中的相應依賴改為自己修復後版本的git url就可以解決問題。

三、npm install如何工作

npm i執行完畢,node_modules中看到所有的依賴包。開發人員無關注node_modules資料夾的結構細節,關注業務程式碼中參照依賴包。

理解node_modules結構幫助我們更好理解npm如何工作。npm2到npm5變化和改進。

3.1 npm2

npm2在安裝依賴包,採用的是簡單的遞迴安裝方法。每一個包都有自己的依賴包,每一個包的依賴都安裝在自己的node_modules中,依賴關係層層遞進,構成整個依賴樹,這個依賴樹與檔案系統中的檔案結構樹一一對應。

最方便的依賴樹的方式在根目錄下執行npm ls

優點:

  • 層級結構明顯,便於傻瓜式管理。

缺點:

  • 複雜工程,目錄結構可能太深,深層的檔案路徑過長觸發window檔案系統中檔案路徑不能超過260個字元長。

  • 部分被多個包依賴的包在很多地方重複安裝,造成大量的冗餘。

3.2 npm3

npm3的node_modules目錄改成更加扁平狀層級結構。npm3在安裝的時候遍歷整個依賴樹,計算最合理的資料夾安裝方式,所有被重複依賴的包都可以去重安裝。

npm來說,同名不同版本的包是兩個獨立的包。

npm3的依賴樹結構不再與資料夾層級一一對應。

3.3 npm5

沿用npm3的扁平化依賴包安裝方式。最大的變化時增加package-lock.json檔案。

package-lock.json作用:鎖定依賴安裝結構,發現node_modules目錄檔案層級結構是與json的結構一一對應。

npm5預設會在執行npm i後生成package-lock.json檔案,提交到git/svn程式碼庫。

要升級,不要使用 5.0版本。

注意:在 npm 5.0 中,如果已有 package-lock 檔案存在,若手動在 package.json 檔案新增一條依賴,再執行 npm install, 新增的依賴並不會被安裝到 node_modules 中, package-lock.json 也不會做相應的更新。

四、依賴包版本管理

介紹依賴包升級管理相關知識。

4.1 語意化版本semver

npm依賴管理的一個重要特性採用語意化版本(semver)規範,作為版本管理方案。

語意化版本號必須包含三個數位,格式:major.minor.patch。意思是:主版本號.小版本號.修改版本號。

我們需要在dependencies中使用semver約定的指定所需依賴包的版本號或者範圍。

常用的規則如下圖:

2.png

semver語意化版本

1、任意兩條規則,用空格連線起來,表示「與」邏輯,即為兩個規則的交集。

如 >=2.3.1 <=2.8.0 可以解讀為: >=2.3.1 且 <=2.8.0

  • 可以匹配 2.3.1, 2.4.5, 2.8.0
  • 但不匹配 1.0.0, 2.3.0, 2.8.1, 3.0.0

2、任意兩條規則,用||連線起來,表示「或」邏輯,即為兩條規則的並集。

如 ^2 >=2.3.1 || ^3 >3.2

  • 可以匹配 2.3.1, 2,8.1, 3.3.1
  • 但不匹配 1.0.0, 2.2.0, 3.1.0, 4.0.0

3、更直觀的表示版本號範圍的寫法

  • 或 x 匹配所有主版本
  • 1 或 1.x 匹配 主版本號為 1 的所有版本
  • 1.2 或 1.2.x 匹配 版本號為 1.2 開頭的所有版本

4、在 MAJOR.MINOR.PATCH 後追加 - 後跟點號分隔的標籤,作為預釋出版本標籤 通常被視為不穩定、不建議生產使用的版本。

  • 1.0.0-alpha
  • 1.0.0-beta.1
  • 1.0.0-rc.3

4.2 依賴版本升級

在安裝完一個依賴包之後有新的版本釋出了,如何使用npm進行版本升級呢?

  • npm i或者npm update,但是不同的npm版本,不同的package.json和package-lock.json檔案,安裝和升級表現是不同的。

使用npm3的結論:

  • 如果本地 node_modules 已安裝,再次執行 install 不會更新包版本, 執行 update 才會更新; 而如果本地 node_modules 為空時,執行 install/update 都會直接安裝更新包。
  • npm update 總是會把包更新到符合 package.json 中指定的 semver 的最新版本號——本例中符合 ^1.8.0 的最新版本為 1.15.0
  • 一旦給定 package.json, 無論後面執行 npm install 還是 update, package.json 中的 webpack 版本一直頑固地保持 一開始的 ^1.8.0 巋然不動

使用npm5的結論:

  • 無論何時執行 install, npm 都會優先按照 package-lock 中指定的版本來安裝 webpack; 避免了 npm 3 表中情形 b) 的狀況;
  • 無論何時完成安裝/更新, package-lock 檔案總會跟著 node_modules 更新 —— (因此可以視 package-lock 檔案為 node_modules 的 JSON 表述)
  • 已安裝 node_modules 後若執行 npm update,package.json 中的版本號也會隨之更改為 ^1.15.0

4.3 最佳實踐

我常用的node是8.11.x,npm是5.6.0。

  • 使用npm >= 5.1 版本,保持package-lock.json檔案預設開啟設定。
  • 初始化,npm i 安裝依賴包,預設儲存^X.Y.Z,專案提交package.json和package-lock.json。
  • 不要手動修改package-lock.json

升級依賴包:

  • 升級小版本,執行npm update升級到新的小版本。
  • 升級大版本,執行npm install @ 升級到新的大版本。
  • 手動修改package.json中的版本號,然後npm i。
  • 本地驗證升級新版本後沒有問題,提交新的package.json和package-lock.json檔案。

降級依賴包:

  • 正確:npm i @驗證沒有問題後,提交package.json和package-lock.json檔案。
  • 錯誤:修改package.json中的版本號,執行npm i不會生效。因為package-lock.json鎖定了版本。

刪除依賴包:

  • A計劃:npm uninstall 。提交package.json和package-lock.json。
  • B計劃:在package.json中刪除對應的包,然後執行npm i,提交package.json和package-lock.json。

五、npm的sctipts

5.1 基本使用

npm scripts是npm的一個重要的特性。在package.json中scripts欄位定義一個指令碼。

比如:

{ 
    "scripts": { 
        "echo": "echo HELLO WORLD" 
    } 
}

我們可以通過npm run echo 命令執行這段指令碼,就像shell中執行echo HELLO WOLRD,終端是可以看到輸出的。

總結如下:

  • npm run 命令執行時,會把./node_modules/.bin目錄新增到執行環境的PATH變數中。全域性的沒有安裝的包,在node_modules中安裝了,通過npm run 可以呼叫該命令。
  • 執行npm 指令碼時要傳入引數,需要在命令後加 -- 表明,比如 npm run test -- --grep="pattern" 可以將--grep="pattern"引數傳給test命令。
  • npm 還提供了pre和post兩種勾點的機制,可以定義某個指令碼前後的執行指令碼。
  • 執行時變數:npm run 的指令碼執行環境內,可以通過環境變數的方式獲取更多的執行相關的資訊。可以通過process.env物件存取獲得:
  • npm_lifecycle_event:正在執行的指令碼名稱
  • npm_package_:獲取當前package.json中某一個欄位的匹配值:如包名npm_package_name
  • npm_package__:package中的巢狀欄位。

5.2 node_modules/.bin目錄

儲存了依賴目錄中所安裝的可供呼叫的命令列包。本質是一個可執行檔案到指定檔案源的對映。

例如 webpack 就屬於一個命令列包。如果我們在安裝 webpack 時新增 --global 引數,就可以在終端直接輸入 webpack 進行呼叫。

上一節所說,npm run 命令在執行時會把 ./node_modules/.bin 加入到 PATH 中,使我們可直接呼叫所有提供了命令列呼叫介面的依賴包。所以這裡就引出了一個最佳實踐:

•將專案依賴的命令列工具安裝到專案依賴資料夾中,然後通過 npm scripts 呼叫;而非全域性安裝

於是 npm 從5.2 開始自帶了一個新的工具 npx.

5.3 npx

npx 的使用很簡單,就是執行 npx 即可,這裡的 預設就是 ./node_modules 目錄中安裝的可執行指令碼名。例如上面本地安裝好的 webpack 包,我們可以直接使用 npx webpack 執行即可。

5.4 用法

1、傳入引數

"scripts": { 
  "serve": "vue-cli-service serve", 
  "serve1": "vue-cli-service --serve1", 
  "serve2": "vue-cli-service -serve2", 
  "serve3": "vue-cli-service serve --mode=dev --mobile -config build/example.js" 
}

除了第一個可執行的命令,以空格分割的任何字串都是引數,並且都能通過process.argv屬性存取。

比如執行npm run serve3命令,process.argv的具體內容為:

[ '/usr/local/Cellar/node/7.7.1_1/bin/node', 
  '/Users/mac/Vue-projects/hao-cli/node_modules/.bin/vue-cli-service', 
  'serve', 
  '--mode=dev', 
  '--mobile', 
  '-config', 
  'build/example.js' 
]

2、多命令執行 在啟動時可能需要同時執行多個任務,多個任務的執行順序決定了專案的表現。

1)序列執行

序列執行,要求前一個任務執行成功之後才能執行下一個任務。使用 && 服務來連線。

npm run scipt1 && npm run script2

序列執行命令,只要一個命令執行失敗,整個指令碼會中止的。

2)並行執行

並行執行,就是多個命令同時平行執行,使用 & 符號來連線。

npm run script1 & npm run script2

3、env 環境變數 在執行npm run指令碼時,npm會設定一些特殊的env環境變數。其中package.json中的所有欄位,都會被設定為以npm_package_ 開頭的環境變數。

4、指令勾點 在執行npm scripts命令(無論是自定義還是內建)時,都經歷了pre和post兩個勾點,在這兩個勾點中可以定義某個命令執行前後的命令。比如在執行npm run serve命令時,會依次執行npm run preserve、npm run serve、npm run postserve,所以可以在這兩個勾點中自定義一些動作:

"scripts": { 
  "preserve": "xxxxx", 
  "serve": "cross-env NODE_ENV=production webpack", 
  "postserve": "xxxxxx" 
}

5、常用指令碼範例

// 刪除目錄 
"clean": "rimraf dist/*", 

// 本地搭建一個http服務 
"server": "http-server -p 9090 dist/", 

// 開啟瀏覽器 
"open:dev": "opener http://localhost:9090", 

// 實時重新整理 
"livereload": "live-reload --port 9091 dist/", 

// 構建 HTML 檔案 
"build:html": "jade index.jade > dist/index.html", 

// 只要 CSS 檔案有變動,就重新執行構建 
"watch:css": "watch 'npm run build:css' assets/styles/", 

// 只要 HTML 檔案有變動,就重新執行構建 
"watch:html": "watch 'npm run build:html' assets/html", 

// 部署到 Amazon S3 
"deploy:prod": "s3-cli sync ./dist/ s3://example-com/prod-site/", 

// 構建 favicon 
"build:favicon": "node scripts/favicon.js",

六.npm設定

6.1 npm config

  • 通過npm config ls -l 可檢視npm 的所有設定,包括預設設定。
  • 通過npm config set ,常見設定:
  • proxy,https-proxy:指定npm使用的代理
  • registry:指定npm下載安裝包時的源,預設是https://registry.npmjs.org。可以指定私有的registry源。
  • package-lock.json:指定是否預設生成package-lock.json,建議保持預設true。
  • save :true/false指定是否在npm i之後儲存包為dependencies,npm5開始預設為true。
  • 通過npm config delete 刪除指定的設定項。

6.2 npmrc檔案

可以通過刪除npm config命令修改設定,還可以通過npmrc檔案直接修改設定。

npmrc檔案優先順序由高到低,包括:

  • 工程內組態檔:專案根目錄下的.npmrc檔案
  • 使用者級組態檔:使用者設定裡
  • 全域性組態檔
  • npm內建組態檔 我們可以在自己的團隊中在根目錄下建立一個.npmrc檔案來共用需要在團隊中共用的npm執行相關設定。

比如:我們在公司內網下需要代理才能存取預設源:https://registry.npmjs.org源;或者存取內網的registry,就可以在工作專案下新增.npmrc檔案並提交程式碼庫。

範例設定:

proxy = http://proxy.example.com/ 
https-proxy = http://proxy.example.com/ 
registry = http://registry.example.com/

這種在工程內組態檔的優先順序最高,作用域在這個專案下,可以很好的隔離公司專案和學習研究的專案兩種不同環境。

將這個功能與 ~/.npm-init.js 設定相結合,可以將特定設定的 .npmrc 跟 .gitignore, README 之類檔案一起做到 npm init 腳手架中,進一步減少手動設定。

6.3 node版本約束

一個團隊中共用了相同的程式碼,但是每個人開發機器不一致,使用的node版本也不一致,伺服器端可能與開發環境不一致。

  • 這就帶來了不一致的因素----方案:宣告式約束+指令碼限制。
  • 宣告:通過package.json的engines屬性宣告應用執行所需的版本要求。例如我呢專案中使用了async,await特性,得知node查閱相容表格[1]得知最低支援版本是7.6.0.因此指定engines設定為:
{ 
  "engines": {"node": ">=7.6.0"} 
}
  • 強約束(可選):需要新增強約束,需要自己寫指令碼勾點,讀取並解析engines欄位的semver range並與執行環境做比對校驗並適當提醒。

總結

  • npm init初始化新專案
  • 統一專案設定:需要團隊共用npm config設定項,固化到.npmrc檔案中
  • 統一執行環境:統一package.json,統一package-lock.json檔案。
  • 合理使用多樣化的源安裝依賴包
  • 使用npm版本:>= 5.2版本
  • 使用npm scripts和npx管理相應指令碼
  • 安全漏洞檢查:npm audit fix修復安全漏洞的依賴包(本質:自動更新到相容的安全版本)

參照連結

[1] node查閱相容表格: https://node.green/

更多node相關知識,請存取:!

以上就是一文帶你瞭解npm的原理的詳細內容,更多請關注TW511.COM其它相關文章!