前端工程化(5):你所須要的npm知識儲備都在這了

npm在前端開發流程中提供了很是完善的自動化工具鏈,已成爲每一個前端開發者必備的工具,可是一樣因爲其強大性致使不少前端開發者只會簡單的使用它。本文將總結在平常開發中所須要的npm知識點,以便開發者們更好的將npm運用在實際開發中。javascript

1. npm 處理 node_modules 目錄結構

一個項目開發、上線所依賴的插件包都存放在node_modules中。雖然在實際開發中無需關注這個目錄裏面的文件結構細節,但瞭解node_modules中的內容能夠幫助咱們更好的理解npm組織這些文件的機制。css

假設項目App中有以下三個依賴:html

"dependencies": {
    A: "1.0.0",
    B: "1.0.0",
    C: "1.0.0"
}
複製代碼

ABC三個模塊又有以下依賴:前端

A@1.0.0 -> D@1.0.0

B@1.0.0 -> D@2.0.0

C@1.0.0 -> D@1.0.0
複製代碼

npm 2.x - 嵌套結構

npm 2.x安裝依賴方式比較簡單直接,以遞歸的方式,按照包依賴的樹形結構下載填充本地目錄結構,也就是說每一個包都會將該包的依賴安裝到當前包所在的node_modules目錄中。vue

執行npm install後,項目Appnode_modules會變成以下目錄結構:java

├── node_modules
│   ├── A@1.0.0
│   │   └── node_modules
│   │   │   └── D@1.0.0
│   ├── B@1.0.0
│   │   └── node_modules
│   │   │   └── D@2.0.0
│   └── C@1.0.0
│   │   └── node_modules
│   │   │   └── D@1.0.0

複製代碼

很顯然這樣的依賴組織結構,有以下優勢:node

  • 層級結構明顯
  • 簡單的實現了多版本兼容
  • 保證了對依賴包不管是安裝仍是刪除都會有統一的行爲和結構

可是缺點也同樣很明顯:webpack

  • 可能形成相同模塊大量冗餘問題
  • 可能形成目錄結構嵌套比較深的問題

npm 3.x - 扁平結構

npm 3.x則採用了扁平化的結構來安裝組織node_modules。也就是在執行npm install的時候,按照package.json 裏依賴的順序依次解析,遇到新的包就把它安裝在第一級目錄,後續安裝若是遇到一級目錄已經存在的包,會先按照約定版本判斷版本,若是符合版本約定則忽略,不然會按照npm 2.x的方式依次掛在依賴包目錄下git

還以項目App爲例,在npm 3.x環境下,執行npm install後,node_modules會變成以下目錄結構:github

├── node_modules
│   ├── A@1.0.0
│   ├── D@1.0.0
│   ├── B@1.0.0
│   │   └── node_modules
│   │   │   └── D@2.0.0
│   └── C@1.0.0
複製代碼

模塊的安裝次序決定了node_modules中的目錄結構,npm會優先將模塊安裝在根目錄下的node_modules中。

再在項目中安裝模塊E@1.0.0(依賴於模塊D@2.0.0),目錄結構變爲:

├── node_modules
│   ├── A@1.0.0
│   ├── D@1.0.0
│   ├── B@1.0.0
│   │   └── node_modules
│   │   │   └── D@2.0.0
│   └── C@1.0.0
│   ├── E@1.0.0
│   │   └── node_modules
│   │   │   └── D@2.0.0
複製代碼

BE模塊下都包含了依賴的模塊D@2.0.0,存在代碼冗餘的狀況。

再在項目中安裝模塊F@1.0.0(依賴於模塊D@1.0.0)。因爲D@1.0.0已經存在於項目根目錄下的node_modules 下,因此在安裝F模塊的時候,無需再在其依賴包中安裝D@1.0.0模塊,目錄結構變爲:

├── node_modules
│   ├── A@1.0.0
│   ├── D@1.0.0
│   ├── B@1.0.0
│   │   └── node_modules
│   │   │   └── D@2.0.0
│   └── C@1.0.0
│   ├── E@1.0.0
│   │   └── node_modules
│   │   │   └── D@2.0.0
│   └── F@1.0.0
複製代碼

從以上結構能夠看出,npm 3.x並無完美的解決npm 2.x中的問題,甚至還會退化到npm 2.x的行爲。

爲了解決目錄中存在不少副本的狀況,(在AC模塊的依賴模塊D升級到2.0.0前提下)能夠經過npm dedupe指令把全部二級的依賴模塊D@2.0.0重定向到一級目錄下:

├── node_modules
│   ├── A@1.0.0
│   ├── D@2.0.0
│   ├── B@1.0.0
│   └── C@1.0.0
│   ├── E@1.0.0
│   └── F@1.0.0
複製代碼

node_modules路徑查找機制:模塊再找對應的依賴包時,nodejs會嘗試從當前模塊所在目錄開始,嘗試在它的node_modules 文件夾里加載相應模塊,若是沒有找到,那麼就再向上一級目錄移動,直到全局安裝路徑中的node_modules爲止。

npm 5.x - package-lock.json

npm 5.x開始,安裝組織node_modulesnpm 3.x同樣採用了扁平化的方式,最大的變化是增長了 package-lock.json 文件。

npm爲了讓開發者在安全的前提下使用最新的依賴包,在package.json中一般作了鎖定大版本的操做,這樣在每次npm install的時候都會拉取依賴包大版本下的最新的版本。這種機制最大的一個缺點就是當有依賴包有小版本更新時,可能會出現協同開發者的依賴包不一致的問題。

package-lock.json文件精確描述了node_modules 目錄下全部的包的樹狀依賴結構,每一個包的版本號都是徹底精確的。以sass-loaderpackage-lock.json中爲例:

"dependencies": {
  "sass-loader": {
    "version": "7.1.0",
    "resolved": "http://registry.npm.taobao.org/sass-loader/download/sass-loader-7.1.0.tgz",
    "integrity": "sha1-Fv1ROMuLQkv4p1lSihly1yqtBp0=",
    "dev": true,
    "requires": {
      "clone-deep": "^2.0.1",
      "loader-utils": "^1.0.1",
      "lodash.tail": "^4.1.1",
      "neo-async": "^2.5.0",
      "pify": "^3.0.0",
      "semver": "^5.5.0"
    },
    "dependencies": {
      "pify": {
        "version": "3.0.0",
        "resolved": "http://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz",
        "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
        "dev": true
      }
    }
  }
}

複製代碼

package-lock.json的詳細描述主要由versionresolvedintegritydevrequiresdependencies這幾個字段構成:

  • version:包惟一的版本號
  • resolved:安裝源
  • integrity:代表包完整性的hash值(驗證包是否已失效)
  • dev:若是爲true,則此依賴關係僅是頂級模塊的開發依賴關係或者是一個的傳遞依賴關係
  • requires:依賴包所須要的全部依賴項,對應依賴包package.jsondependencies中的依賴項
  • dependencies:依賴包node_modules中依賴的包,與頂層的dependencies同樣的結構

在上面的package-lock.json文件中能夠發現,在requiresdependencies中都存在pify依賴項。那咱們順便去node_modules裏面探下究竟:

  1. 打開根目錄的node_modules會發現安裝了sass-loader所須要的全部依賴包,這些依賴包中除了pify之外,全部依賴包的大版本號都與sass-loader所須要的一致。
  2. 到根目錄的node_modules找到pify依賴包,發現版本爲4.0.1
  3. 找到sass-loader項目依賴包,打開其node_modules發現其中也存在個pify依賴包,但版本爲3.0.0。這個版本的sass-loader真正依賴的是這個版本的pify

經過以上幾個步驟,也驗證了以前闡述過的npm 5.x是扁平化處理依賴的方式。

在開發一個應用時,建議把package-lock.json文件提交到代碼版本倉庫,從而讓你的團隊成員、運維部署人員或CI系統能夠在執行npm install時安裝的依賴版本都是一致的。

可是在開發一個庫時,則不該把package-lock.json文件發佈到倉庫中。實際上,npm也默認不會把package-lock.json文件發佈出去。之因此這麼作,是由於庫項目通常是被其餘項目依賴的,在不寫死的狀況下,就能夠複用主項目已經加載過的包,而一旦庫依賴的是精確的版本號那麼可能會形成包的冗餘。

2. npm 中的依賴包

依賴包分類

node 中其實總共有5種依賴:

  • dependencies - 業務依賴

  • devDependencies - 開發依賴

  • peerDependencies - 同伴依賴

  • bundledDependencies / bundleDependencies - 打包依賴

  • optionalDependencies - 可選依賴

做爲npm的使用者,咱們經常使用的依賴是dependenciesdevDependencies,剩下三種依賴則是做爲包的發佈者纔會使用到的字段。

dependencies

這種依賴在項目最終上線或者發佈npm包時所須要,即其中的依賴項應該屬於線上代碼的一部分。好比框架vue,第三方的組件庫element-ui等,這些依賴包都是必須裝在這個選項裏供生產環境使用。

經過命令npm install/i packageName -S/--save把包裝在此依賴項裏。若是沒有指定版本,直接寫一個包的名字,則安裝當前npm倉庫中這個包的最新版本。若是要指定版本的,能夠把版本號寫在包名後面,好比npm i vue@3.0.1 -S

npm 5.x開始,能夠不用手動添加-S/--save指令,直接執行npm i packageName把依賴包添加到dependencies中去。

devDependencies

這種依賴只在項目開發時所須要,即其中的依賴項不該該屬於線上代碼的一部分。好比構建工具webpackgulp,預處理器babel-loaderscss-loader,測試工具e2echai等,這些都是輔助開發的工具包,無須在生產環境使用。

經過命令npm install/i -D/--save-dev把包安裝成開發依賴。若是想縮減安裝包,可使用命令npm i --production忽略開發依賴,只安裝基本依賴,這一般在線上機器(或者QA環境)上使用。

千萬別覺得只有在dependencies中的模塊纔會被一塊兒打包,而在devDependencies中的不會!模塊可否被打包,取決於項目裏是否被引入了該模塊! 在業務項目中dependenciesdevDependencies沒有什麼本質區別,只是單純的一個規範做用,在執行npm i時兩個依賴下的模塊都會被下載;而在發佈npm包的時候,包中的dependencies依賴項在安裝該包的時候會被一塊兒下載,devDependencies依賴項則不會。

peerDependencies

這種依賴的做用是提示宿主環境去安裝插件在peerDependencies中所指定依賴的包,而後插件所依賴的包永遠都是宿主環境統一安裝的npm包,最終解決插件與所依賴包不一致的問題。

這句話聽起來可能有點拗口,舉個例子來給你們說明下。element-ui@2.6.3只是提供一套基於vueui組件庫,但它要求宿主環境須要安裝指定的vue版本,因此你能夠看到element項目中的package.json中具備一項配置:

"peerDependencies": {
    "vue": "^2.5.16"
}
複製代碼

它要求宿主環境安裝3.0.0 > vue@ >= 2.5.16的版本,也就是element-ui的運行依賴宿主環境提供的該版本範圍的vue依賴包。

在安裝插件的時候,peerDependenciesnpm 2.xnpm 3.x中表現不同:

npm 2.x中,安裝包中peerDependencies所指定的依賴會隨着npm install packageName一塊兒被強制安裝,而且peerDependencies中指定的依賴會安裝在宿主環境中,因此不須要在宿主環境的package.json文件中指定對所安裝包中peerDependencies內容的依賴。

npm 3.x中,不會再要求peerDependencies所指定的依賴包被強制安裝,npm 3.x只會在安裝結束後檢查本次安裝是否正確,若是不正確會給用戶打印警告提示,好比提示用戶有的包必須安裝或者有的包版本不對等。

大白話:若是你安裝我,那麼你最好也要按照個人要求安裝A、B和C。

bundledDependencies / bundleDependencies

這種依賴跟npm pack打包命令有關。假設package.json中有以下配置:

{
  "name": "font-end",
  "version": "1.0.0",
  "dependencies": {
    "fe1": "^0.3.2",
    ...
  },
  "devDependencies": {
    ...
    "fe2": "^1.0.0"
  },
  "bundledDependencies": [
    "fe1",
    "fe2"
  ]
}
複製代碼

執行打包命令npm pack,會生成front-end-1.0.0.tgz壓縮包,而且該壓縮包中包含fe1fe2兩個安裝包,這樣使用者執行npm install front-end-1.0.0.tgz也會安裝這兩個依賴。

bundledDependencies中指定的依賴包,必須先在dependenciesdevDependencies聲明過,不然打包會報錯。

optionalDependencies

這種依賴中的依賴項即便安裝失敗了,也不影響整個安裝的過程。須要注意的是,若是一個依賴同時出如今dependenciesoptionalDependencies中,那麼optionalDependencies會得到更高的優先級,可能形成一些預期以外的效果,因此儘可能要避免這種狀況發生。

在實際項目中,若是某個包已經失效,咱們一般會尋找它的替代者,或者換一個實現方案。不肯定的依賴會增長代碼判斷和測試難度,因此這個依賴項仍是儘可能不要使用。

依賴包版本號

npm採用了semver規範做爲依賴版本管理方案。

按照semver的約定,一個npm依賴包的版本格式通常爲:主版本號.次版本號.修訂號x.y.z),每一個號的含義是:

  • 主版本號(也叫大版本,major version

    大版本的改動極可能是一次顛覆性的改動,也就意味着可能存在與低版本不兼容的API或者用法,(好比 vue 2 -> 3)。

  • 次版本號(也叫小版本,minor version

    小版本的改動應當兼容同一個大版本內的API和用法,所以應該讓開發者無感。因此咱們一般只說大版本號,不多會精確到小版本號。

    若是大版本號是 0 的話,表示軟件處於開發初始階段,一切均可能隨時被改變,可能每一個小版本之間也會存在不兼容性。因此在選擇依賴時,儘可能避開大版本號是 0 的包。

  • 修訂號(也叫補丁,patch

    通常用於修復bug或者很細微的變動,也須要保持向前兼容。

常見的幾個版本格式以下:

  • "1.2.3"

    表示精確版本號。任何其餘版本號都不匹配。在一些比較重要的線上項目中,建議使用這種方式鎖定版本。

  • "^1.2.3"

    表示兼容補丁和小版本更新的版本號。官方的定義是「可以兼容除了最左側的非 0 版本號以外的其餘變化」(Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple)。這句話很拗口,舉幾個例子你們就明白了:

    "^1.2.3" 等價於 ">= 1.2.3 < 2.0.0"。即只要最左側的 "1" 不變,其餘均可以改變。因此 "1.2.4", "1.3.0" 均可以兼容。
    
    "^0.2.3" 等價於 ">= 0.2.3 < 0.3.0"。由於最左側的是 "0",那麼只要第二位 "2" 不變,其餘的都兼容,好比 "0.2.4""0.2.99""^0.0.3" 等價於 ">= 0.0.3 < 0.0.4"。大版本號和小版本號都爲 "0" ,因此也就等價於精確的 "0.0.3"複製代碼

    從這幾個例子能夠看出,^ 是一個兼具更新和安全的寫法,可是對於大版本號爲 1 和 0 的版本仍是會有不一樣的處理機制的。

  • "~1.2.3"

    表示只兼容補丁更新的版本號。關於 ~ 的定義分爲兩部分:若是列出了小版本號(第二位),則只兼容補丁(第三位)的修改;若是沒有列出小版本號,則兼容第二和第三位的修改。咱們分兩種狀況理解一下這個定義:

    "~1.2.3" 列出了小版本號 "2",所以只兼容第三位的修改,等價於 ">= 1.2.3 < 1.3.0""~1.2" 也列出了小版本號 "2",所以和上面同樣兼容第三位的修改,等價於 ">= 1.2.0 < 1.3.0""~1" 沒有列出小版本號,能夠兼容第二第三位的修改,所以等價於 ">= 1.0.0 < 2.0.0"
    複製代碼

    從這幾個例子能夠看出,~ 是一個比^更加謹慎安全的寫法,並且~並不對大版本號 0 或者 1 區別對待,因此 "~0.2.3" 與 "~1.2.3" 是相同的算法。當首位是 0 而且列出了第二位的時候,二者是等價的,例如 "~0.2.3" 和 "^0.2.3"。

  • "1.x" 、"1.X"、1.*"、"1"、"*"

    表示使用通配符的版本號。x、X、* 和 (空) 的含義相同,都表示能夠匹配任何內容。具體來講:

    "*""x" 或者 (空) 表示能夠匹配任何版本。
    
    "1.x", "1.*""1" 表示匹配主版本號爲 "1" 的全部版本,所以等價於 ">= 1.0.0 < 2.0.0""1.2.x", "1.2.*""1.2" 表示匹配版本號以 "1.2" 開頭的全部版本,所以等價於 ">= 1.2.0 < 1.3.0"複製代碼
  • "1.2.3-beta.1"

    帶預發佈關鍵詞的版本號。先說說幾個預發佈關鍵詞的定義:

    alpha(α):預覽版,或者叫內部測試版;通常不向外部發布,會有不少bug;通常只有測試人員使用。
    
    beta(β):測試版,或者叫公開測試版;這個階段的版本會一直加入新的功能;在alpha版以後推出。
    
    rc(release candidate):最終測試版本;可能成爲最終產品的候選版本,若是未出現問題則可發佈成爲正式版本。
    複製代碼

    以包開發者的角度來考慮這個問題:假設當前線上版本是 "1.2.3",若是我做了一些改動須要發佈版本 "1.2.4",但我不想直接上線(由於使用 "~1.2.3" 或者 "^1.2.3" 的用戶都會直接靜默更新),這就須要使用預發佈功能。所以我可能會發布 "1.2.4-alpha.1" 或者 "1.2.4-beta.1" 等等。

    ">1.2.4-alpha.1"表示接受 "1.2.4-alpha" 版本下全部大於 1 的預發佈版本。所以 "1.2.4-alpha.7" 是符合要求的,但 "1.2.4-beta.1""1.2.5-alpha.2" 都不符合。此外若是是正式版本(不帶預發佈關鍵詞),只要版本號符合要求便可,不檢查預發佈版本號,例如 "1.2.5", "1.3.0" 都是承認的。
    
    "~1.2.4-alpha.1" 表示 ">=1.2.4-alpha.1 < 1.3.0"。這樣 "1.2.5", "1.2.4-alpha.2" 都符合條件,而 "1.2.5-alpha.1", "1.3.0" 不符合。
    
    "^1.2.4-alpha.1" 表示 ">=1.2.4-alpha.1 < 2.0.0"。這樣 "1.2.5", "1.2.4-alpha.2", "1.3.0" 都符合條件,而 "1.2.5-alpha.1", "2.0.0" 不符合。
    複製代碼

版本號還有更多的寫法,例如範圍(a - b),大於等於號(>=),小於等於號(<=),或(||)等等,由於用的很少,這裏再也不展開。詳細的文檔能夠參見語義化版本(semver)。它同時也是一個 npm 包,能夠用來比較兩個版本號的大小,以及是否符合要求等。

依賴包版本管理

npm 2.x/3.x已成爲過去式,在npm 5.x以上環境下(版本最好在5.6以上,由於在5.0 ~ 5.6中間對package-lock.json的處理邏輯更新過幾個版本,5.6以上纔開始穩定),管理項目中的依賴包版本你應該知道(以^版本爲例,其餘類型版本參照便可):

  1. 在大版本相同的前提下,若是一個模塊在package.json中的小版本要大於package-lock.json中的小版本,則在執行npm install時,會將該模塊更新到大版本下的最新的版本,並將版本號更新至package-lock.json。若是小於,則被package-lock.json中的版本鎖定。
// package-lock.json 中原版本
"clipboard": {
  "version": "1.5.10", 
},
"vue": {
  "version": "2.6.10",
}
// package.json 中修改版本
"dependencies": {
  "clipboard": "^1.5.12",
  "vue": "^2.5.6"
  ...
}

// 執行完 npm install 後,package-lock.json 中
"clipboard": {
  "version": "1.7.1", // 更新到大版本下的最新版本
},
"vue": {
  "version": "2.6.10", // 版本沒發生改變
}
複製代碼
  1. 若是一個模塊在package.jsonpackage-lock.json中的大版本不相同,則在執行npm install時,都將根據package.json中大版本下的最新版本進行更新,並將版本號更新至package-lock.json
// package-lock.json 中原版
"clipboard": {
  "version": "2.0.4",
}
// package.json 中修改版本
"dependencies": {
  "clipboard": "^1.6.1",
}

// 執行完npm install後,package-lock.json 中
// 
"clipboard": {
  "version": "1.7.1", // 更新到大版本下的最新版本
}
複製代碼
  1. 若是一個模塊在package.json中有記錄,而在package-lock.json中無記錄,執行npm install後,則會在package-lock.json生成該模塊的詳細記錄。同理,一個模塊在package.json中無記錄,而在package-lock.json中有記錄,執行npm install後,則會在package-lock.json刪除該模塊的詳細記錄。

  2. 若是要更新某個模塊大版本下的最新版本(升級小版本號),請執行以下命令:

npm install packageName
// 或者
npm update packageName
複製代碼
  1. 若是要更新到指定版本號(升級大版本號),請執行以下命令:
npm install packageName@x.x.x
複製代碼
  1. 卸載某個模塊,請執行以下命令:
npm uninstall packageName
複製代碼

經過上述的命令來管理依賴包,package.jsonpackage-lock.json中的版本號都將會隨之更新。

咱們在升級/卸載依賴包的時候,儘可能經過命令來實現,避免手動修改package.json中的版本號,尤爲不要手動修改package-lock.json

3. npm scripts 腳本

package.json中的 scripts 字段能夠用來自定義腳本命令,它的每個屬性,對應一段腳本。以vue-cli3爲例:

"scripts": {
  "serve": "vue-cli-service serve",
  ...
}
複製代碼

這樣就能夠經過npm run serve腳本代替vue-cli-service serve腳原本啓動項目,而無需每次敲一遍這麼冗長的腳本。

工做原理

package.json 中的 bin 字段

package.json中的字段 bin 表示的是一個可執行文件到指定文件源的映射。例如在@vue/clipackage.json中:

"bin": {
  "vue": "bin/vue.js"
}
複製代碼

若是全局安裝@vue/cli的話,@vue/cli源文件會被安裝在全局源文件安裝目錄(/user/local/lib/node_modules)下,而npm會在全局可執行bin文件安裝目錄(/usr/local/bin)下建立一個指向/usr/local/lib/node_modules/@vue/cli/bin/vue.js文件的名爲vue的軟連接,這樣就能夠直接在終端輸入vue來執行相關命令。以下圖所示:

若是局部安裝@vue/cli的話,npm則會在本地項目./node_modules/.bin目錄下建立一個指向./node_moudles/@vue/cli/bin/vue.js名爲vue的軟連接,這個時候須要在終端中輸入./node_modules/.bin/vue來執行(也可使用npx vue命令來執行,npx 的做用就是爲了方便調用項目內部安裝的模塊)。

軟連接(符號連接)是一類特殊的可執行文件, 其包含有一條以絕對路徑或者相對路徑的形式指向其它文件或者目錄的引用。在bin目錄下執行ll指令能夠查看具體的軟連接指向。在對連接文件進行讀或寫操做的時候,系統會自動把該操做轉換爲對源文件的操做,但刪除連接文件時,系統僅僅刪除連接文件,而不刪除源文件自己。

PATH 環境變量

terminal中執行命令時,命令會在PATH環境變量裏包含的路徑中去尋找相同名字的可執行文件。局部安裝的包只在./node_modules/.bin中註冊了它們的可執行文件,不會被包含在PATH環境變量中,這個時候在terminal中輸入命令將會報沒法找到的錯誤。

那爲何經過npm run能夠執行局部安裝的命令行包呢?

是由於每當執行npm run時,會自動新建一個Shell,這個 Shell會將當前項目的node_modules/.bin的絕對路徑加入到環境變量PATH中,執行結束後,再將環境變量PATH恢復原樣

咱們來驗證下這個說法。首先執行 env 查看當前全部的環境變量,能夠看到PATH環境變量爲:

PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
複製代碼

再在當前項目下執行npm run env查看腳本運行時的環境變量,能夠看到PATH環境變量爲:

PATH=/usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/mac/Vue-projects/hao-cli/node_modules/.bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
複製代碼

能夠看到運行時的PATH環境變量多了兩個路徑:npm指令路徑和項目中node_modules/.bin的絕對路徑。

因此,經過npm run能夠在不添加路徑前綴的狀況下直接訪問當前項目node_modules/.bin目錄裏面的可執行文件。

PATH環境變量,是告訴系統,當要求系統運行一個程序而沒有告訴它程序所在的完整路徑時,系統除了在當前目錄下面尋找此程序外,還應到哪些目錄下去尋找。

用法指南

傳入參數

關於scripts中的參數,這裏要多說幾句。網上有不少不是很準確的說法,通過本人的反覆試驗,node處理scripts參數其實很簡單,好比:

"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"
}
複製代碼

除了第一個可執行的命令,以空格分割的任何字符串(除了一些shell的語法)都是參數,而且都能經過process.argv屬性訪問

process.argv屬性返回一個數組,這個數組包含了啓動node進程時的命令行參數。第一個元素爲啓動node 進程的可執行文件的絕對路徑名process.execPath,第二個元素爲當前執行的JavaScript文件路徑。剩餘的元素爲其餘命令行參數。

好比執行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']
複製代碼

不少命令行包之因此這麼寫,都是依賴了 minimist 或者 yargs 等參數解析工具來對命令行參數進行解析。

minimistvue-cli-service serve --mode=dev --mobile -config build/example.js解析爲例,解析後的結果爲:

{ _: [ 'serve' ],
  mode: 'dev',
  mobile: true,
  config: 'build/example.js',
  '$0': '/Users/mac/Vue-projects/hao-cli/node_modules/.bin/vue-cli-service'}
複製代碼

./node_modules/.bin/vue-cli-service文件中能夠看到minimist對命令行參數的處理:

const rawArgv = process.argv.slice(2)
const args = require('minimist')(rawArgv, {
  boolean: [
    // build
    'modern',
    'report',
    'report-json',
    'watch',
    // serve
    'open',
    'copy',
    'https',
    // inspect
    'verbose'
  ]
})
const command = args._[0]
service.run(command, args, rawArgv).catch(err => {
  error(err)
  process.exit(1)
})
複製代碼

咱們還能夠經過命令行傳參的形式來進行參數傳遞:

npm run serve --params  // 參數params將轉化成process.env.npm_config_params = true
npm run serve --params=123 // 參數params將轉化成process.env.npm_config_params = 123
npm run serve -params  // 等同於--params參數

npm run serve -- --params  // 將--params參數添加到process.env.argv數組中
npm run serve params  // 將params參數添加到process.env.argv數組中
npm run serve -- params  // 將params參數添加到process.env.argv數組中
複製代碼

多命令運行

有的項目在啓動時可能須要同時執行多個任務,多個任務的執行順序決定了項目的表現。

串行執行

串行執行,要求前一個任務執行成功之後才能執行下一個任務,使用&&符號來鏈接。

npm run script1 && npm run script2
複製代碼

串行命令執行過程當中,只要一個命令執行失敗,則整個腳本終止。

並行執行

並行執行,就是多個命令能夠同時的平行執行,使用&符號來鏈接。

npm run script1 & npm run script2
複製代碼

這兩個符號是Bash的內置功能。此外,還可使用第三方的任務管理器模塊:script-runnernpm-run-allredrun

env 環境變量

在執行npm run腳本時,npm會設置一些特殊的env環境變量。其中package.json中的全部字段,都會被設置爲以npm_package_ 開頭的環境變量。好比package.json中有以下字段內容:

{
  "name": "sh",
  "version": "1.1.1",
  "description": "shenhao",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "git+ssh://git@gitlab.com/xxxx/sh.git"
  }
}
複製代碼

能夠經過process.env.npm_package_name 能夠獲取到package.jsonname字段的值sh,也能夠經過process.env.npm_package_repository_type獲取到嵌套屬性type的值git

同時,npm相關的全部配置也會被設置爲以npm_config_開頭的環境變量。

此外,還會設置一個比較特殊的環境變量npm_lifecycle_event,表示正在運行的腳本名稱。好比執行npm run serve 的時候,process.env.npm_lifecycle_event值爲serve,經過判斷這個變量,能夠將一個腳本使用在不一樣的npm scripts中。

這些環境變量只能在npm run的腳本執行環境內拿到,正常執行的node腳本是獲取不到的。因此,不能直接經過env $NODE_ENV的形式訪問,但能夠在scripts中定義腳本"scripts": "echo $NODE_ENV"來訪問。

指令鉤子

在執行npm scripts命令(不管是自定義仍是內置)時,都經歷了prepost兩個鉤子,在這兩個鉤子中能夠定義某個命令執行先後的命令。

好比在執行npm run serve命令時,會依次執行npm run preservenpm run servenpm run postserve,因此能夠在這兩個鉤子中自定義一些動做:

"scripts": {
  "preserve": "xxxxx",
  "serve": "vue-cli-service serve",
  "postserve": "xxxxxx"
}
複製代碼

固然,若是沒有指定preservepostserve,會默默的跳過。若是想要指定鉤子,必須嚴格按照prepost前綴來添加。

上面提到過一個環境變量process.env.npm_lifecycle_event能夠配合鉤子來一塊兒使用:

const event = process.env.npm_lifecycle_event

if (event === 'preserve') {
    console.log('Running the preserve task!')
} else if (_event === 'serve') {
    console.log('Running the serve task!')
}
複製代碼

4. npm 配置

npm的配置操做能夠幫助咱們預先設定好npm對項目的行爲動做,也可讓咱們預先定義好一些配置項以供項目中使用。因此瞭解npm的配置機制也是頗有必要。

優先級

npm能夠從不一樣的來源獲取其配置值,按優先級從高到低的順序排序:

命令行

npm run serve --params=123
複製代碼

執行上述命令時,會將配置項params的值設爲123,經過process.env.npm_config_params能夠訪問其配置值。這個時候的params配置值將覆蓋全部其餘來源存在的params配置值。

env 環境變量

若是env環境變量中存在以npm_config_爲前綴的環境變量,則會被識別爲npm的配置屬性。好比在env環境變量中設置npm_config_package_lock變量:

export npm_config_package_lock=false // 修改的是內存中的變量,只對當前終端有效
複製代碼

這時候執行npm installnpm會從環境變量中讀取到這個配置項,從而不會生成package-lock.json文件。

查看某個環境變量:echo $NODE_ENV
刪除某個環境變量:unset NODE_ENV

npmrc 文件

經過修改 npmrc 文件能夠直接修改配置。系統中存在多個npmrc文件,這些npmrc文件被訪問的優先級從高到低的順序爲:

  • 項目級的.npmrc文件

    只做用在本項目下。在其餘項目中,這些配置不生效。經過建立這個.npmrc文件能夠統一團隊的npm配置規範。

  • 用戶級的.npmrc文件

    mac下的地址爲~/.npmrc。(npm config get userconfig能夠看到存放的路徑)

  • 全局級的npmrc文件

    mac下的地址爲$PREFIX/etc/npmrc。(npm config get globalconfig能夠看到存放的路徑)

  • npm內置的npmrc文件

    這是一個不可更改的內置配置文件,爲了維護者以標準和一致的方式覆蓋默認配置。mac下的地址爲/path/to/npm/npmrc

.npmrc參照 npm/ini 格式編寫。

默認配置

經過npm config ls -l查看npm內部的默認配置參數。若是命令行、環境變量、全部配置文件都沒有配置參數,則使用默認參數值。

npm config 指令

npm提供了幾個 npm config 指令來進行用戶級和全局級配置:

set

npm config set <key> <value> [-g|--global]
npm config set registry <url>  # 指定下載 npm 包的來源,默認爲 https://registry.npmjs.org/ ,能夠指定私有源

npm config set prefix <path>  # prefix 參數指定全局安裝的根目錄
# 配置 prefix 參數後,當再對包進行全局安裝時,包會被安裝到以下位置:
# Mac 系統:{prefix}/lib/node_modules
# Windows 系統:{prefix}/node_modules
# 把可執行文件連接到以下位置:
# Mac 系統:{prefix}/bin
# Windows 系統:{prefix}
複製代碼

使用-g|--global標誌修改或新增全局級配置,不使用的話修改或者新增用戶級配置(相應級別的.npmrc文件會更新)。

若是key不存在,則會新增到配置中。若是省略value,則key會被設置成true

還能夠覆蓋package.jsonconfig字段的值:

// package.json
{
  "name" : "foo",
  "config" : { "port" : "8080" },
  "scripts" : { "start" : "node server.js" }
}
複製代碼
// server.js
console.log(process.env.npm_package_config_port)
複製代碼
npm config set foo:port 8000  # 打印8000
複製代碼

get

npm config get <key>
npm config get prefix  # 獲取 npm 的全局安裝路徑
複製代碼

按照配置優先級,獲取指定配置項的值。

delete

npm config delete <key>
複製代碼

npm官網上說能夠刪除全部配置文件中指定的配置項,但經實驗沒法刪除項目級的.npmrc文件裏指定的配置項。

list

npm config list [-l] [--json]
複製代碼

加上-l或者--json查看全部的配置項,包括默認的配置項。不加的話,不能查看默認的配置項。

edit

npm config edit [-g|--global]
複製代碼

在編輯器中打開配置文件。使用-g|--global標誌編輯全局級配置和默認配置,不使用的話編輯用戶級配置和默認配置。

參考 npm config 來獲取更多的默認配置。

5. npm 工程管理

項目版本號管理

package.json中的version字段表明的是該項目的版本號。每當項目發佈新版本時,須要將version字段進行相應的更新以便後期維護。雖然能夠手動的修改vsersion字段,可是爲了整個發佈過程的自動化,儘可能使用 npm version 指令來自動更新version

npm version (v)1.2.3  # 顯示設置版本號爲 1.2.3 
npm version major  # 大版本號加 1,其他版本號歸 0
npm version minor  # 小版本號加 1,修訂號歸 0
npm version patch  # 修訂號加 1
複製代碼

顯示的設置版本號時,版本號必須符合semver規範,容許在版本號前加上個v標識。

若是不想讓這次更新正式發佈,還能夠建立預發佈版本:

# 當前版本號爲 1.2.3
npm version prepatch  # 版本號變爲 1.2.4-0,也就是 1.2.4 版本的第一個預發佈版本
npm version preminor  # 版本號變爲 1.3.0-0,也就是 1.3.0 版本的第一個預發佈版本
npm version premajor  # 版本號變爲 2.0.0-0,也就是 2.0.0 版本的第一個預發佈版本
npm version prerelease  # 版本號變爲 2.0.0-1,也就是使預發佈版本號加一
複製代碼

git環境中,執行npm version修改完版本號之後,還會默認執行git add->git commit->git tag操做

其中commit message默認是自動修改完的版本號,能夠經過添加-m/--message選項來自定義commit message

npm version xxx -m "upgrade to %s for reasons"  # %s 會自動替換爲新版本號
複製代碼

好比執行npm version minor -m "feat(version): upgrade to %s for reasons"後:

若是git工做區還有未提交的修改,npm version將會執行失敗,能夠加上-f/--force後綴來強制執行。

若是不想讓npm version指令影響你的git倉庫,能夠在指令中使用--no-git-tag-version參數:

npm --no-git-tag-version version xxx
複製代碼

若是想默認不影響你的git倉庫,能夠在npm設置中禁止:

npm config set git-tag-version false  # 不自動打 tag
npm config set commit-hooks false     # 不自動 commit
複製代碼

模塊 tag 管理

不常常發佈包的同窗可能對模塊 tag 概念不是很清楚。以vue爲例,首先執行npm dist-tag ls vue查看vue包的tag

beta: 2.6.0-beta.3
csp: 1.0.28-csp
latest: 2.6.10
複製代碼

上面列出的betacsplatest就是tag。每一個tag對應了一個版本。

tag到底有什麼用呢?tag相似於git裏面分支的概念,發佈者能夠在指定的tag上發佈版本,而使用者能夠選擇指定的tag來安裝包。不一樣的標籤下的版本之間互不影響,這在發佈者發佈預發佈版本包和使用者嚐鮮預發佈版本包的同時,不影響到正式版本

在發佈包的時候執行npm publish默認會打上latest這個tag,其實是執行了npm publish --tag latest。而在安裝包的時候執行npm install xxx則會默認下載latest這個tag下面的最新版本,其實是執行了npm install xxx@latest。固然,咱們也能夠自定義tag

# 當前版本爲1.0.1
npm version prerelease  # 1.0.2-0
npm publish --tag beta
npm dist-tag ls xxx  # # beta: 1.0.2-0
npm install xxx@beta  # 下載beta版本 1.0.2-0
複製代碼

prerelease版本已經穩定了,能夠將prerelease版本設置爲穩定版本:

npm dist-tag add xxx@1.0.2-0 latest
npm dist-tag ls xxx  # latest: 1.0.2-0
複製代碼

域級包管理

細心的同窗會發現,在package.json中的依賴有兩種形式:

"devDependencies": {
  "@commitlint/cli": "^7.2.1",
  "commitizen": "^3.0.4"
}
複製代碼

其中以@開頭的包名,是一個域級包(scoped package),這種域級包的做用是將一些packages集中在一個命名空間下,一方面能夠集中管理,一方面能夠防止與別的包產生命名衝突。

要發佈域級包,首先要在項目的package.jsonname屬性中添加scope相關的聲明,能夠經過指令添加:

npm init --scope=scopeName -y
複製代碼

package.json變爲:

{
  "name": "@scopeName/package"
}
複製代碼

能夠將用戶名做爲域名,也能夠將組織名做爲域名。

因爲用@聲明瞭該包,npm會默認將此包認定爲私有包,而在npm上託管私有包是須要收費的,因此爲了不發佈私有包,能夠在發佈時添加--accss=public參數告知npm這不是一個私有包:

npm publish --access=public
複製代碼

域級包不必定就是私有包,可是私有包必定是一個域級包。

同時,在安裝域級包時須要按照域級包全名來安裝:

npm install @scopeName/package
複製代碼

6. npm 的幾個實用技巧

自定義默認的 npm init

使用npm init初始化一個新的項目時會提示你去填寫一些項目描述信息。若是以爲填寫這些信息比較麻煩的話,可使用-y標記表示接受package.json中的一些默認值:

npm init -y
複製代碼

也能夠設置初始化的默認值:

npm config set init-author-name <name> // 爲 npm init 設置了默認的做者名
複製代碼

查看 npm 腳本命令

查看當前項目的全部npm腳本命令最直接的辦法就是打開項目中的package.json文件並檢查scripts字段。咱們還可使用不帶任何參數的npm run命令查看:

npm run
複製代碼

查看環境變量

經過env查看當前的全部環境變量,而查看運行時的全部環境變量能夠執行:

npm run env
複製代碼

模塊管理

檢查當前項目依賴的全部模塊,包括子模塊以及子模塊的子模塊:

npm list/ls
複製代碼

若是還想查看模塊的一些描述信息(package.json中的description中的內容):

npm la/ll // 至關於npm ls --long
複製代碼

一個項目依賴的模塊每每不少,能夠限制輸出模塊的層級來查看:

npm list/ls --depth=0 // 只列出父包依賴的模塊
複製代碼

檢查項目中依賴的某個模塊的當前版本信息:

npm list/ls <packageName>
複製代碼

查看某個模塊包的版本信息:

npm view/info <packageName> version // 模塊已經發布的最新的版本信息(不包括預發佈版本)
npm view/info <packageName> versions // 模塊全部的歷史版本信息(包括預發佈版本)
複製代碼

查看一個模塊究竟是由於誰被安裝進來的,若是顯示爲空則代表該模塊爲內置模塊或者不存在:

npm ll <packageName>
複製代碼

查看某個模塊的全部信息,包括它的依賴、關鍵字、更新日期、貢獻者、倉庫地址和許可證等:

npm view/info <packageName>
複製代碼

查看當前項目中可升級的模塊:

npm outdated
複製代碼

整理項目中無關的模塊:

npm prune
複製代碼

查看模塊文檔

打開模塊的 github 主頁:

npm repo <packageName> 
複製代碼

打開模塊的文檔地址:

npm docs <packageName>
複製代碼

打開模塊的 issues 地址:

npm bugs <packageName>
複製代碼

在不一樣的目錄下運行腳本

你的文件夾中確定存在不少應用程序,而當你想要啓動某個應用程序時,確定是經過cd指令一步步進入到你所想要啓動的應用程序目錄下,而後再執行啓動命令。npm提供了--prefix能夠指定啓動目錄:

npm run dev --prefix /path/to/your/folder
複製代碼

模塊全局化

假設你在開發一個模塊A,同時須要在另一個項目B中測試它,固然你能夠將該模塊的代碼拷貝到須要使用它的項目中,但這也不是理想的方法,能夠在模塊A的目錄下執行:

npm link
複製代碼

npm link命令經過連接目錄和可執行文件,實現任意位置的npm包命令的全局可執行。

npm link主要作了兩件事:

  1. 爲目標npm模塊建立軟連接,將其連接到全局node模塊安裝路徑/usr/local/lib/node_modules/
  2. 爲目標npm模塊的可執行bin文件建立軟連接,將其連接到全局node命令安裝路徑/usr/local/bin/

安全漏洞檢查

檢查項目中是否存在具備安全漏洞的依賴包,若是存在,則將生成其漏洞報告顯示在控制檯中:

npm audit [--json]  # 加上--json,以 JSON 格式生成漏洞報告
複製代碼

npm升級到6.x版本之後,在項目中更新或者下載新的依賴包之後會自動執行 npm audit 命令,對項目依賴包進行安全檢查,若是存在安全漏洞,將生成漏洞報告並在控制檯中顯示。

修復存在安全漏洞的依賴包(自動更新到兼容的安全版本):

npm audit fix
複製代碼

執行npm audit fix能修復大部分存在安全漏洞的依賴包,對於一些沒能自動修復漏洞的依賴包,說明出現了SERVER WARNING之類的警告(主要發生在依賴包更改了不兼容的api或者大版本作了升級的狀況下),這意味着推薦的修復版本還可能出現問題,這時能夠執行以下命令來修復這些依賴包:

npm audit fix --force
複製代碼

--force會將依賴包版本號升級到最新的大版本,而不是兼容的安全版本。大版本的升級可能會出現一些不兼容的用法,因此儘可能避免使用--force

若是執行npm audit fix --force後仍是存在有安全漏洞的依賴包,手動執行npm audit打印出還存在安全漏洞的依賴包的具體信息,其中More info對應的連接中可能給出瞭解決方案。

若是想知道audit fix會怎麼處理項目中的依賴包,能夠預先查看:

npm audit fix --dry-run --json
複製代碼

若是隻想修復生產環境的依賴包(只更新dependencies中的依賴包,不更新devDependencies中的依賴包):

npm audit --only=prod
複製代碼

若是不想修復依賴包,只修改package-lock.json文件:

npm audit fix --package-lock-only
複製代碼

若是想安裝某個包時不進行安全漏洞檢查:

npm install packageName --no-audit
複製代碼

要想安裝全部包時都不進行安全漏洞檢查,則能夠修改npm配置:

npm config set audit false
複製代碼

依賴鎖定

npm默認安裝模塊時,會經過脫字符^來限定所安裝模塊的主版本號。能夠配置npm經過波浪符~來限定安裝模塊版本號:

npm config set save-prefix="~"
複製代碼

固然還能夠配置npm僅安裝精確版本號的模塊:

npm config set save-exact true
複製代碼

7. 最後

關於npm的知識點暫時總結到這裏,可能還有不少方面沒有總結到位,後續若是還有新的知識點會隨時更新,也歡迎各位大佬們隨時來補充,共同進步!

相關文章
相關標籤/搜索