你不知道的npm

npm.jpeg

引言

做爲 node 自帶的包管理器工具,在 nodejs 社區和 web 前端工程化領域發展日益龐大的背景下,npm已經成爲每位前端開發同窗必備的工具。css

天天,無數的開發人員使用npm來構建項目,npm initnpm install等方式幾乎成爲了構建項目的首選方式,可是大多數同窗對於 npm 的使用卻只停留在了npm install這裏。(相信刪除 node_modules 文件夾,從新執行 npm install 這種事情你應該作過吧)前端

本篇文章主要是結合我以往的經驗,帶你們更深層次的瞭解一下 npmvue

npm 中的依賴包

依賴包類型

npm 目前支持一下幾種類型的依賴包管理node

  • dependencies
  • devDependencies
  • peerDependencies
  • optionalDependencies
  • bundledDependencies / bundleDependencies
"dependencies": {
    "koa": "^2.7.0",
    "koa-bodyparser": "^4.2.1",
    "koa-redis": "^4.0.0",
  },
  "devDependencies": {
    "babel-eslint": "^10.0.3",
    "cross-env": "^6.0.3",
    "lint-staged": "^9.5.0",
    "mysql2": "^2.1.0",
    "nodemon": "^1.19.1",
    "precommit": "^1.2.2",
    "redis": "^2.8.0",
    "sequelize": "^5.21.3",
  },
  "peerDependencies": {},
  "optionalDependencies": {},
  "bundledDependencies": []

dependencies

應用依賴,或者叫作業務依賴,是咱們最經常使用的一種。這種依賴是應用發佈後上線所須要的,也就是說其中的依賴項屬於線上代碼的一部分。好比框架react,第三方的組件庫ant-design等。可經過下面的命令來安裝:mysql

npm i ${packageName} -S

devDependencies

開發環境依賴。這種依賴只在項目開發時所須要,好比構建工具webpackgulp,單元測試工具jestmocha等。可經過下面的命令來安裝:react

npm i ${packageName} -D

peerDependencies

同行依賴。這種依賴的做用是提示宿主環境去安裝插件在peerDependencies中所指定依賴的包,用於解決插件與所依賴包不一致的問題。webpack

聽起來可能沒有那麼好理解,舉個例子來講明下。antd@3.19.5只是提供了一套基於reactui組件庫,但它要求宿主環境須要安裝指定的react版本,因此你能夠看到 node_modules 中 antd 的package.json中有這麼一項配置:ios

"peerDependencies": {
    "react": ">=16.0.0",
    "react-dom": ">=16.0.0"
  },

它要求宿主環境安裝大於等於16.0.0版本的react,也就是antd的運行依賴宿主環境提供的該範圍的react安裝包。git

在安裝插件的時候,peerDependencies 在 npm 2.xnpm 3.x中表現不同。npm2.x 會自動安裝同等依賴,npm3.x 再也不自動安裝,會產生警告!手動在 package.json文件中添加依賴項能夠解決。

optionalDependencies

可選依賴。這種依賴中的依賴包即便安裝失敗了,也不影響整個安裝的過程。須要注意的是,optionalDependencies會覆蓋dependencies中的同名依賴包,因此不要在兩個地方都寫。github

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

bundledDependencies / bundleDependencies

打包依賴。若是在打包發佈時但願一些依賴包也出如今最終的包裏,那麼能夠將包的名字放在bundledDependencies中,bundledDependencies 的值是一個字符串數組,如:

{
  "name": "sequelize-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mysql2": "^2.1.0"
  },
  "devDependencies": {
    "sequelize": "^5.21.3"
  },
   "bundledDependencies": [
    "mysql2",
    "sequelize"
  ]
}

執行打包命令npm pack,在生成的sequelize-test-1.0.0.tgz包中,將包含mysql2sequelize

須要注意的是,在 bundledDependencies中指定的依賴包,必須先在 dependencies 和 devDependencies 聲明過,不然打包會報錯。

語義化版本控制

爲了在軟件版本號中包含更多意義,反映代碼所作的修改,產生了語義化版本,軟件的使用者能從版本號中推測軟件作的修改。npm 包使用語義化版控制,咱們可安裝必定版本範圍的 npm 包,npm 會選擇和你指定的版本相匹配 的 (latest)最新版本安裝。

npm 採用了semver規範做爲依賴版本管理方案。版本號由三部分組成:主版本號次版本號補丁版本號。變動不一樣的版本號,表明不一樣的意義:

  • 主版本號(major):軟件作了不兼容的變動(breaking change 重大變動)
  • 次版本號(minor):添加功能或者廢棄功能,向下兼容
  • 補丁版本號(patch):bug 修復,向下兼容

下面讓咱們來看下經常使用的幾個版本格式:

  • "compression": "1.7.4"

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

  • "typescript": "^3.4.3"

表示兼容補丁和小版本更新的版本號。官方的定義是可以兼容除了最左側的非 0 版本號以外的其餘變化。這句話不是很好理解,舉幾個例子你們就明白了:

"^3.4.3" 等價於 `">= 3.4.3 < 4.0.0"。即只要最左側的 "3" 不變,其餘均可以改變。因此 "3.4.5", "3.6.2" 均可以兼容。

"^0.4.3" 等價於 ">= 0.4.3 < 0.5.0"。由於最左側的是 "0",那麼只要第二位 "4" 不變,其餘的都兼容,好比 "0.4.5" 和 "0.4.99"。

"^0.0.3" 等價於 ">= 0.0.3 < 0.0.4"。大版本號和小版本號都爲 "0" ,因此也就等價於精確的 "0.0.3"。
  • "mime-types": "~2.1.24"

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

"~2.1.24" 列出了小版本號 "1",所以只兼容第三位的修改,等價於 ">= 2.1.24 < 2.2.0"。

"~2.1" 也列出了小版本號 "2",所以和上面同樣兼容第三位的修改,等價於 ">= 2.1.0 < 2.2.0"。

"~2" 沒有列出小版本號,能夠兼容第二第三位的修改,所以等價於 ">= 2.0.0 < 3.0.0"
  • "underscore-plus": "1.x" "uglify-js": "3.4.x"

除了上面的xX還有*和(),這些都表示使用通配符的版本號,能夠匹配任何內容。具體來講:

"*" 、"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"。
  • "css-tree": "1.0.0-alpha.33" "@vue/test-utils": "1.0.0-beta.29"

有時候爲了表達更加確切的版本,還會在版本號後面添加標籤或者擴展,來講明是預發佈版本或者測試版本等。常見的標籤有:

標籤 含義 補充
demo demo 版本 可能用於驗證問題的版本
dev 開發版 開發階段用的,bug 多,體積較大等特色,功能不完善
alpha α 版本 預覽版,或者叫內部測試版;通常不向外發布,會有不少 bug;通常只有測試人員使用。
beta 測試版(β 版本) 測試版,或者叫公開測試版;這個階段的版本會一直加入新的功能;在 alpha 版以後推出。
gamma (γ)伽馬版本 較 α 和 β 版本有很大的改進,與穩定版相差無幾,用戶可以使用
trial 試用版本 本軟件一般都有時間限制,過時以後用戶若是但願繼續使用,通常得交納必定的費用進行註冊或購買。有些試用版軟件還在功能上作了必定的限制。
csp 內容安全版本 js 庫經常使用
rc 最終測試版本 可能成爲最終產品的候選版本,若是未出現問題則可發佈成爲正式版本
latest 最新版本 不指定版本和標籤,npm 默認安最新版
stable 穩定版

npm install 原理分析

咱們都知道,執行npm install後,依賴包被安裝到了node_modules中。雖然在實際開發中咱們無需十分關注裏面具體的細節,但瞭解node_modules中的內容能夠幫助咱們更好的理解npm安裝依賴包的具體機制。

嵌套結構

在 npm 的早期版本中,npm 處理依賴的方式簡單粗暴,以遞歸的方式,嚴格按照 package.json 結構以及子依賴包的 package.json 結構將依賴安裝到他們各自的 node_modules 中。

舉個例子,咱們的項目ts-axios如今依賴了兩個模塊: axiosbody-parser:

{
  "name": "ts-axios",
  "dependencies": {
    "axios": "^0.19.0",
    "body-parser": "^1.19.0",
  }
}

axios依賴了follow-redirectsis-buffer模塊:

{
  "name": "axios",
  "dependencies": {
      "follow-redirects": "1.5.10",
      "is-buffer": "^2.0.2"
    },
}

body-parser依賴了bytescontent-type等模塊:

{
  "name": "body-parser",
  "dependencies": {
    "bytes": "3.1.0",
    "content-type": "~1.0.4",
     ...
  }
}

那麼,執行 npm install 後,獲得的 node_modules 中模塊目錄結構就是下面這樣的:

這樣的方式優勢很明顯, node_modules 的結構和 package.json 結構一一對應,層級結構明顯,而且保證了每次安裝目錄結構都是相同的。

可是,試想一下,若是你依賴的模塊很是之多,你的 node_modules 將很是龐大,嵌套層級很是之深:

從上圖這種狀況,咱們不可貴出嵌套結構擁有如下缺點:

  • 在不一樣層級的依賴中,可能引用了同一個模塊,致使大量冗餘
  • 嵌套層級過深可能致使不可預知的問題

扁平結構

爲了解決以上問題,npm 在 3.x 版本作了一次較大更新。其將早期的嵌套結構改成扁平結構。

安裝模塊時,無論其是直接依賴仍是子依賴的依賴,優先將其安裝在 node_modules 根目錄。

仍是上面的依賴結構,咱們在執行 npm install 後將獲得下面的目錄結構:

此時咱們若在模塊中又依賴了 is-buffer@2.0.1 版本:

{
  "name": "ts-axios",
  "dependencies": {
    "axios": "^0.19.0",
    "body-parser": "^1.19.0",
    "is-buffer": "^2.0.1"
  }
}

當安裝到相同模塊時,判斷已安裝的模塊版本是否符合新模塊的版本範圍,若是符合則跳過,不符合則在當前模塊的 node_modules 下安裝該模塊。

此時,咱們在執行 npm install 後將獲得下面的目錄結構:

對應的,若是咱們在項目代碼中引用了一個模塊,模塊查找流程以下:

  • 在當前模塊路徑下搜索
  • 在當前模塊 node_modules 路徑下搜索
  • 在上級模塊的 node_modules 路徑下搜索
  • ...
  • 直到搜索到全局路徑中的 node_modules

假設咱們又依賴了一個包 axios2@^0.19.0,而它依賴了包 is-buffer@^2.0.3,則此時的安裝結構是下面這樣的:

因此 npm 3.x 版本並未徹底解決老版本的模塊冗餘問題,甚至還會帶來新的問題。

咱們在 package.json 一般只會鎖定大版本,這意味着在某些依賴包小版本更新後,一樣可能形成依賴結構的改動,依賴結構的不肯定性可能會給程序帶來不可預知的問題。

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": "ts-axios",
  "dependencies": {
    "axios": "^0.19.0",
  }
}

在執行 npm install 後生成的 package-lock.json 以下:

{
  "name": "ts-axios",
  "version": "0.1.0",
  "dependencies": {
      "axios": {
        "version": "0.19.0",
        "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
        "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
        "requires": {
          "follow-redirects": "1.5.10",
          "is-buffer": "^2.0.2"
        },
        "dependencies": {
          "debug": {
            "version": "3.1.0",
            "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
            "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
            "requires": {
              "ms": "2.0.0"
            }
          },
          "follow-redirects": {
            "version": "1.5.10",
            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
            "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
            "requires": {
              "debug": "=3.1.0"
            }
          },
          "is-buffer": {
            "version": "2.0.3",
            "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
            "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
          },
          "ms": {
            "version": "2.0.0",
            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
            "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
          }
      }
    },
  }
}

最外面的兩個屬性 nameversionpackage.json 中的 nameversion ,用於描述當前包名稱和版本。

dependencies 是一個對象,對象和 node_modules 中的包結構一一對應,對象的 key 爲包名稱,值爲包的一些描述信息:

  • version: 包惟一的版本號
  • resolved: 安裝來源
  • integrity: 代表包完整性的 hash 值(驗證包是否已失效)
  • requires: 依賴包所須要的全部依賴項,與子依賴的 package.jsondependencies的依賴項相同。
  • dependencies: 依賴包node_modules中依賴的包,與頂層的dependencies同樣的結構

這裏注意,並非全部的子依賴都有 dependencies 屬性,只有子依賴的依賴和當前已安裝在根目錄的 node_modules 中的依賴衝突以後,纔會有這個屬性。

經過以上幾個步驟,說明package-lock.json文件和node_modules目錄結構是一一對應的,即項目目錄下存在package-lock.json可讓每次安裝生成的依賴目錄結構保持相同。

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

npm scripts 腳本

腳本功能是 npm 最強大、最經常使用的功能之一。

npm 容許在package.json文件中使用scripts字段來定義腳本命令。以vue-cli3爲例:

"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "test:unit": "vue-cli-service test:unit"
  },

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

原理

這裏咱們參考一下阮老師的文章:

npm 腳本的原理很是簡單。每當執行 npm run,就會自動新建一個 Shell,在這個 Shell 裏面執行指定的腳本命令。所以,只要是 Shell(通常是 Bash)能夠運行的命令,就能夠寫在 npm 腳本里面。
比較特別的是,npm run 新建的這個 Shell,會將當前目錄的 node_modules/.bin子目錄加入 PATH 變量,執行結束後,再將 PATH 變量恢復原樣。

傳入參數

在原有腳本後面加上 -- 分隔符, 後面再加上參數,就能夠將參數傳遞給 script 命令了,好比 eslint 內置了代碼風格自動修復模式,只需給它傳入 -–fix 參數便可,咱們能夠這樣寫:

"scripts": {
    "lint": "vue-cli-service lint --fix",
  },

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

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

執行順序

若是 npm 腳本里面須要執行多個任務,那麼須要明確它們的執行順序。

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

npm run script1 && npm run script2
串行命令執行過程當中,只要一個命令執行失敗,則整個腳本將馬上終止。

若是是並行執行,即多個任務能夠同時執行。使用&符號來鏈接。

npm run script1 & npm run script2

鉤子

這裏的鉤子和vuereact裏面的生命週期有點類似。

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

好比,在用戶執行npm run build的時候,會自動按照下面的順序執行。

npm run prebuild && npm run build && npm run postbuild

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

環境變量

npm 腳本有一個很是強大的功能,就是可使用 npm 的內部變量。

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

{
  "name": "sequelize-test",
  "version": "1.0.0",
  "description": "sequelize測試",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "mysql2": "^2.1.0",
    "sequelize": "^5.21.3"
  }
}

那麼,變量npm_package_name返回sequelize-test,變量npm_package_description返回sequelize測試。也就是:

console.log(process.env.npm_package_name)  // sequelize-test

console.log(process.env.npm_package_description)  // sequelize測試

npm 配置

優先級

npm 從如下來源獲取配置信息(優先級由高到低):

命令行

npm run dev --foo=bar

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

環境變量

若是 env 環境變量中存在以npm_config_爲前綴的環境變量,則會被識別爲 npm 的配置屬性。好比,環境變量中的npm_config_foo=bar 將會設置配置參數 foo 的值爲 "bar"

若是隻指定了參數名卻沒有指定任何值的配置參數,其值將會被設置爲 true

npmrc文件

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

  • 項目配置文件

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

  • 用戶配置文件

默認爲~/.npmrc/,可經過npm config get userconfig查看存放的路徑。

  • 全局配置文件

    經過npm config get globalconfig能夠查看具體存放的路徑。

  • npm 內置的配置文件

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

默認配置

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

npm config 指令

set

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

設置配置參數 key 的值爲 value,若是省略 value,key 會被設置爲 true

get

npm config get <key>

查看配置參數 key 的值。

delete

npm config delete <key>

刪除配置參數 key。

list

npm config list [-l] [--json]

查看全部設置過的配置參數。使用 -l 查看全部設置過的以及默認的配置參數。使用 --json 以 json 格式查看。

edit

npm config edit

在編輯器中打開 npmrc 文件,使用 --global 參數打開全局 npmrc 文件。

總結

以上就是我關於 npm 的一些深度挖掘,固然有不少方面沒有總結到位,後續我會在實戰的過程當中,不斷總結,隨時更新。也歡迎大佬隨時來吐槽

最後

你能夠關注個人同名公衆號【前端森林】,這裏我會按期發一些大前端相關的前沿文章和平常開發過程當中的實戰總結。固然,我也是開源社區的積極貢獻者,github地址https://github.com/Jack-cool,歡迎star!!!

相關文章
相關標籤/搜索