npm基本用法及原理(10000+)


   作為前端開發者,應該每個人都用過npm,那么npm到底是什么東西呢?npm run,npm install的時候發生了哪些事情呢?下面做詳細說明。

1.npm是什么

npm是JavaScript語言的包管理工具,它由三個部分組成:

  • npm網站 進入
    npm官網上可以查找包,查看包信息。
  • 注冊表
    一個巨大的數據庫,存放包的信息
  • 命令行工具npm-cli
    開發者運行npm命令的工具

這三者中,與我們打交道最多的就是npm-cli,其實我們所說的npm的使用,就是指這個工具的使用,那它到底是個什么東西呢?我們先來看看它被放在哪里,在系統命令行(window cmd)工具中輸入 where npm(安裝node會自帶npm),就能找到它的位置:
頂頂頂頂
然后根據路徑找到npm文件打開:
在這里插入圖片描述
從標紅的地方可以看出,這其實就是一個腳本,它最終執行的是: node npm-cli.js

   所以到目前為止,我們可以知道當在命令行輸入npm時,其實是在node環境中,執行了一段npm-cli.js代碼,這是對npm的一個直觀的認識。
   至於npm-cli.js里面的邏輯是什么,就是研究源碼層面的事了,這里不涉及。我們主要來看npm的用法和功能層面的原理。首先來看npm的配置文件package.json。

2.package.json文件

當我們運行命令npm init,根據提示輸入一些信息后(npm init -y不需輸入信息),會在當前目錄下生成一個package.json文件:

{
  "name": "testNpm",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

這里就是一個npm包的基本信息,包括包名name,版本version,描述description,作者author,主文件main,腳本scripts等等, 這里先主要來看下main

2.1 入口文件 main

   main配置項的值是一個js文件的路徑,它將作為程序的主入口文件。也就是說當別人引用了這個包時import testNpm from 'testNpm',其實引入的就是testNpm/index.js文件所export出的模塊。

2.2 腳本 scripts

npm scripts 腳本應該是我們打交道最多的一個配置項了,它一個json的對象,由腳本名稱和腳本內容組成:

"scripts":{
	"star":"echo star npm",
	"echo":"echo hello npm"
}

一般用npm run xxx來運行,但是一些關鍵命令比如:start,test,stop,restart等等,可以直接npm xxx來執行。那scripts是如何執行腳本的呢?又可以執行哪些腳本呢?

npm 腳本可以執行的命令
其實當我們npm run xxx的時候,就是把xxx的內容生成了一個shell腳本,然后執行腳本,那么npm的shell具體是什么呢?我們可以運行npm config get -l來查看npm的全部配置:
在這里插入圖片描述
可能個人的系統和配置不同,以我個人電腦配置為例,其實就是cmd.exe,其實就是window系統的cmd命令行工具。所以在cmd中可以執行的命令,在npm的scripts中都可以執行,舉例說明:

"scripts":{
	/*系統命令*/
	"echo":"echo hello npm",
	"dir":"dir",
	"ip":"ipconfig"
}

像dir,ipconfig,echo這些都是可以直接在cmd命令行中執行的命令,在npm的scripts中都可以通過npm run xxx來執行。這一類是系統cmd的內部命令,不需要安裝額外的插件,就可以直接執行。
還有一種就是我們在cmd還可以執行外部命令,比如我們如果安裝了node,git等客戶端,可以直接在cmd窗口執行(需配置了系統的環境變量):
在這里插入圖片描述
這一類的命令npm也可以執行:

"scripts":{
	/*系統命令*/
    "echo":"echo hello npm",
    "dir":"dird",
    "ip":"ipconfig",
    /*全局外部命令*/
    "git":"git --version",
    "node":"node -v",
}

這是全局引入的外部命令,還有些項目內部才有的命令,比如我們在項目下安裝eslint: npm install eslint --save-dev,在scripts中配置了腳本的話,我們可以直接運行npm run eslint

"scripts":{
	/*系統命令*/
    "echo":"echo hello npm",
    "dir":"dird",
    "ip":"ipconfig",
    /*全局外部命令*/
    "git":"git --version",
    "node":"node -v",
    /*項目內外部命令*/
    "eslint":"eslint -v"
}

但是如果我們直接在cmd窗口執行eslint -v,則會報錯,
在這里插入圖片描述
這是因為系統找不到eslint的位置(沒有配系統環境變量),但是既然cmd室npm 腳本執行的環境,為什么npm run eslint可以執行呢?
這是因為當我們通過npm run xxx執行腳本的時候,會把當前目錄的'node_modules/.bin'加入到環境變量,也就是說npm執行腳本的時候,會自動到node_modules/.bin目錄下找,如果找到則可以正常執行,我們來看一下:
在這里插入圖片描述
在node_modules/.bin目錄下果然是eslint.cmd腳本的,而它作的其實就是node eslint.js,用node來執行eslint.js的代碼。

npm 腳本可以執行的命令總結:

  • cmd內部命令,例如dir,ipconfig...
  • 外部命令
    • 全局命令,加入了系統環境變量
    • 項目下命令,這部分會放在node_modules/.bin目錄下,而npm會自動鏈接到此目錄。

2.3 npm腳本其他配置

路徑通配符
我們在寫腳本命令的時候,常常要匹配文件,這就要用到路徑的通配符。
總的來說*表示任意字符串,在目錄中表示1級目錄,**表示0級或多級目錄,例如:

src/*:src目錄下的任意文件,匹配 src/a.js; src/b.json;不匹配src/aa/a.js
src/*.js:src目錄下任何js文件,匹配 src/a.js; 不匹配 src/b.json;src/aa/a.js
src/*/*.js:src目錄下一級的任意js文件,匹配 src/aa/a.js; 不匹配src/a.js;src/a/aa/a.js
src/**/*.js:src目錄下的任意js文件,匹配 src/a.js; src/a/a.js; src/a/aa/a.js

命令參數
關於npm的參數,我們先來看一段代碼:
node代碼:

	//index.js
	
	console.log(process.env.npm_package_name)
	console.log(process.env.npm_config_env)
	console.log(process.argv)

npm配置:

	//package.json
	
{
  "name": "npm",
  "version": "1.0.0",
  "scripts": {
    "node":"node index.js --name=node age=28",
  },
}

然后我們執行命令npm run node --env=npmEnv,結果為:
在這里插入圖片描述

下面來做下說明,其實npm的參數都是指node環境下的參數,用node的全局變量process來獲取。

  • npm內部變量
    當我們在執行npm命令的時候,就會把package.json的參數加上npm_package_前綴,加入到process.env的變量中,所以在上面的node代碼可以通過process.env.npm_package_name獲取到package.json里面配置的name屬性。
  • 命令參數
    當我們在運行npm命令時,帶上以雙橫線為后綴的參數:npm 命令 --xx=xx,npm就會把xx加上npm_config_前綴,加入到process.env變量中,如果原來有同名的,命令參數的優先級最高,會覆蓋掉原來的,所以在上面的node代碼可以通過process.env.npm_config_env獲取到npm run node --env=npmEnv命令里的參數env的值,如果參數沒有賦值:npm run node --env,則默認值為true
  • 腳本參數
    這個其實要根據腳本的內容來看,比如我們上面的腳本是node index.js --env=node,這其實是純粹的node命令了,可以通過process.argv來獲取node的命令參數,這是個數組,第一個為node命令路徑,第二個為執行文件路徑,后面的值為用空格隔開的其他參數,如上面打印的結果所示。

執行順序
npm腳本的執行順序分為兩部分:

  • 命令鈎子
    npm腳本有pre,post兩類鈎子,一個是執行前,一個是執行后。比如,當我們執行npm run start時,會按照以下順序執行npm run prestart ->npm run start ->npm run poststart
  • 多任務並行
    如果要執行多個腳本,可以用&&&來連接
    • npm run aa & npm run bb 並行執行,沒有先后關系
    • npm run aa && npm run bb 串行執行,先執行完aa再執行bb

3.npm 包管理

npm做完包管理工具,主要的作用還是包的安裝及管理。

3.1 安裝包 npm install xxx

npm install xxx 命令用於安裝包。
我們先來運行npm install vuenpm install eslint --save-dev,會發現項目會有以下變化:

  • 添加了目錄node_modules
    安裝的包和包的依賴都存放在這里,引入的時候,會自動到此目錄下找。
  • package.json文件自動添加了如下配置:
      "dependencies": {
        "vue": "^2.6.13"
      },
      "devDependencies": {
        "eslint": "^7.27.0"
      }
    
    npm 在安裝包的同時,會把包的名稱和版本加入到dependencies配置中,這表明這是項目必需的包。
    如果帶上參數--save-dev,則加入到devDependencies配置中,這表明這是項目開發時才需要的工具包,不是項目必需的。
  • 添加了package-lock.json文件
    鎖定包的版本和依賴結構。

3.2 從package.json配置文件安裝包

包依賴類型
現在把node_modules目錄和package-lock.json文件都刪除,然后運行npm install,會發現項目會自動安裝vue和eslint包。
如果我們執行npm install --production則表明我們只是想安裝項目必須的包,用於生產環境,這是就只會安裝dependencies對象下的包。
其實npm包除了這兩種還有其他包的依賴類型:

  • dependencies
    業務依賴,是項目的必須包,是項目線上代碼的一部分。npm install --production只會安裝此配置下的包。
  • devDependencies
    開發環境依賴,只在開發環境需要。npm install --save-dev安裝包並添加到此配置下。
  • peerDependencies
    同行依賴,當運行npm install,會提示安裝此配置下的包。注意只是警告提示,不會自動安裝。
  • optionalDependencies
    可選依賴,表明即使安裝失敗,也不影響項目的安裝過程。會覆蓋掉dependencies中的同名包。
  • bundledDependencies
    打包依賴,發布當前包的時候,會把此配置下的依賴包也一起打包。必須先在 dependenciesdevDependencies 聲明過,否則打包會報錯。

包版本說明
npm采用semver作為包版本管理規范。此規范規定軟件版本由三個部分組成:

  • 主版本號做了不兼容的重大變更
  • 次版本號做了向下兼容的功能添加
  • 補丁版本號做了向下兼容的bug修復

除了版本號之外,還有一些版本修飾,后面可以帶上數字:

  • alpha內測版 eg:3.0.0-alpha.1
  • beta公測版 eg:3.0.0-beta.10
  • rc正式版本的候選版 eg:3.0.0-rc.3

版本匹配

  • */x:匹配任意值
    1.1.* = >=1.1.0 <1.2.0
    1.x = >=1.0.0 <2.0.0
  • ^xxx: 最左側非0版本號不變,不小於xxx
    ^1.2.3 = >=1.2.3 <2.0.0 主版本號不變
    ^0.1.2 = >=0.1.2 <0.2.0 主、次版本號不變
    ^0.0.2 = = 0.0.2 主、次、補丁版本號都不變
  • ~xxx:如果列出了次版本號,則次版本號不變,如果沒有列出次版本號,則主版本號不變,均不小於xxx
    ~1.2.3 = >=1.2.3 <1.3.0 主、次版本號不變
    ~1 = >=1.0.0 <2.0.0 主版本號不變

3.3 package-lock.json作用

固定版本
當我們安裝包的時候,會自動添加package-lock.json文件,那么這個文件的作用是什么呢?在這個問題之前,先來看看npm install的安裝原理:

//package.json
{
  "name": "npm",
  "version": "1.0.0",
  "dependencies": {
    "vue": "^2.5.1"
  },
  "devDependencies": {
    "eslint": "^7.0.0"
  }
}

有上面一份npm配置文件,當npm install時會安裝兩個包:vue ^2.5.1,eslint ^7.0.0 ,符合所配置版本的包是一個范圍多個,npm會會安裝符合版本配置的最新版本。比如:
vue ^2.5.1 = >=2.5.1 <3.0.0, npm會選擇安裝2.6.13,因為它在匹配版本范圍內,且是目前最新的vue2的版本,它不會選擇2.5.03.0.0
那么如果只有一份package.json文件,就很可能導致項目依賴的版本不一樣。比如開發時候vue2的最新版本是2.6.13,過了幾個月項目要上線,部署的時候vue2的最新版本已經是2.7.0了,那么線上就會安裝最新的版本。如果2.7.0有一些不兼容2.6.13的地方,或者有bug,那就會導致我們開發的一個經典問題:開發環境沒問題,一上線就壞。如果項目是多個人協同開發,甚至會導致開發環境都不一樣。
那么我們來看看package-lock.json文件怎么解決這個問題的:

//package-lock.json
{
  "name": "npm",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "vue": {
      "version": "2.6.13",
      "resolved": "https://registry.nlark.com/vue/download/vue-2.6.13.tgz?cache=0&sync_timestamp=1622664849693&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fvue%2Fdownload%2Fvue-2.6.13.tgz",
      "integrity": "sha1-lLLBsx/d8d/MNPKOyEi6jwHqTFs="
    },
	.....
  }
}

我們看到package-lock.json文件里直接記錄了vue的固定版本號和下載地址。

npm在執行install的時候,會把每個需要安裝的包先在package-lock.json里查找,如果找到並且版本符合package.json的配置范圍(在范圍內就行,不需要最新),就會直接按照package-lock.json里的地址安裝。如果沒找到或者不符合范圍,則安裝原本的邏輯安裝(符合版本要求的最新版)。
這樣就確保,不管時間過了多久,只要package-lock.json文件不變,npm install安裝的包的版本都是一致的,避免代碼運行的依賴環境不同。

固定依賴結構
我們的一個項目通常會有很多依賴包,而這些依賴包很可能又會依賴其他的包,那如何來避免重復安裝呢?
比如:

//package.json
{
  "name": "npm",
  "version": "1.0.0",
  "dependencies": {
    "esquery": "^1.4.0",
    "esrecurse": "^4.3.0",
    "eslint-scope": "^5.1.1"
  }
}

依賴關系如下:

  • esquery : ^1.4.0,
    • estraverse : ^5.1.0
  • esrecurse : ^4.3.0
    • estraverse : ^5.2.0
  • eslint-scope :^5.1.1
    • esrecurse : ^4.3.0
      • estraverse :^5.2.0
    • estraverse :^4.1.1

如果按照這個嵌套結構來安裝包的話也是可以的,而且npm原來的版本就是這么做的,這樣可以保證每個包都安裝完整,但是問題是會導致一些包重復安裝,如果這個依賴很多的話,重復的數量也會很多。那npm是怎么處理的呢?
npm采用的是用扁平結構,包的依賴,不管是直接依賴,還是子依賴的依賴,都會優先放在第一級。
如果第一級有找到符合版本的包,就不重復安裝,如果沒找到,則在當前目錄下安裝。
比如上面的包會被安裝成如下的結構:

  • esquery :1.4.0,
    • estraverse : 5.2.0
  • esrecurse : 4.3.0
    • estraverse : 5.2.0
  • eslint-scope : 5.1.1
  • estraverse : 4.3.1

包安裝的數量從開始的8個減少到了6個,雖然還是有重復,但是因為這個json的結構,又是以包名為鍵名,所以同一級下只能有一個同名的包,就像 estraverse : 5.2.0不能放在外層,因為外層已經有了以estraverse 為名的對象:estraverse : 4.3.1
package-lock.json記錄的就是上面的依賴結構(上面只是簡寫,每一項還包含一些其他的信息,比如下載地址),這也是node_modules里面包的結構。
所以一個項目只要package-lock.json不變,它的依賴結構就不變,而且npm不用重新解析包的結構了,直接從package-lock.json文件就可以安裝完整且正確的包依賴,也提高了重新安裝的效率。

3.4 包緩存

npm安裝包不是每一次都從服務器直接下載,而是有緩存機制。當npm安裝包時,會在本地的緩存一份。執行npm config get cache可以查看緩存目錄:
在這里插入圖片描述
按照路徑打開文件夾,會發現_cacache緩存文件夾,打開文件夾會有index-v5content-v2兩個目錄。
其中index-v5存放的是包的索引,而content-v2則存放的是緩存的壓縮包。

緩存查找
那么npm是如何找到緩存包的呢?以vue包為例:

  • 1.首先安裝vue包: npm install vue
  • 2.查看package-lock.json文件,根據包信息獲取resolved,integrity字段,構造字符串:
    pacote:range-manifest:{resolved}:{integrity}
  • 3.把上面字符串按SHA256加密,得到加密字符串:
    2686ae12fd03809c9e5704cd01db518f1d7d07efe5ab61e6ef386e95b8481360
  • 4.上面加密字符串的前4位就是_cacache/index-v5目錄的下兩級,索引文件的位置:
    _cacache/index-v5/26/86/ae12fd03809c9e5704cd01db518f1d7d07efe5ab61e6ef386e95b8481360
  • 5.打開按照上面路徑找到的索引文件,在索引文件中找到_shasum字段:
    94b2c1b31fddf1dfcc34f28ec848ba8f01ea4c5b
  • 6.上面符串就是緩存包的位置,其前4位就是_cacache/content-v2/sha1目錄的下兩級,包位置:
    _cacache/content-v2/sha1/94/b2/c1b31fddf1dfcc34f28ec848ba8f01ea4c5b
  • 7.把按照上面路徑找到的文件的拓展名改為.tgz,然后解壓,會得到vue.tar包,再解壓,就是我們熟悉的vue包了。

3.5 npm install 原理流程圖

把npm install原理總結為下面的流程圖:
在這里插入圖片描述

4.npm常用命令

  • npm init [-y] 創建package.json文件 [直接創建]
  • npm run xxx [--env] 運行腳本 [參數]
  • npm config get [-l] 查看npm配置 [全部配置]
  • npm install xxx [--save-dev] [-g] 安裝npm包 [添加到開發依賴] [全局安裝]
  • npm uninstall xxx [-g] 刪除包 [刪除全局包]
  • npm info xxx 查看包信息
  • npm view xxx version 查看包最新版本
  • npm update [-g] xxx 更新包 [全局包]
  • npm root [-g] npm包安裝的目錄 [全局包安裝目錄]
  • npm ls [-g] 查看項目安裝的包 [全局安裝的包]
  • npm install [--production] 安裝項目 [只安裝項目依賴]
  • npm ci 安裝項目,不對比package.json,只從package-lock.json安裝,並且會先刪除node_modules目錄
  • npm config get cache 查看緩存目錄
  • npm cache clean --force 清除npm包緩存

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM