NPM 入門

何謂 npm ?

npm(Node Package Manager)用來管理 Node.js 所安裝的模組。

npm 分成三個部分:

  • 網站
  • Command Line Tool
  • 登記處(Registry): 用來登記、管理開發者所發佈的模組。

我們會常使用到的是 Command Line Tool ,在安裝 Node.js 時 npm 就自動被安裝了。

npm init - 初始化 npm 資料夾

進入專案資料夾,輸入 npm init 可將將其變成 npm 管理的資料夾。

1
2
3
4
5
# 必須手動設定所有初始化選項
$ npm init

# 全部的選項都照預設
$ npm init -y

初始化後,資料夾內會出現 package.json 檔案,該檔案紀錄了關於目前的專案資料夾的基本資訊。

要看 package.json 有什麼內容,除了直接用文字編輯器打開外,也可以在終端機使用 cat 指令,如下:

1
$ cat package.json

package.json

package.json 記錄專案的資訊,基本會有下列屬性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "nodeedx", // 專案名
"version": "1.0.0", // 版本
"description": "", // 敘述
"main": "main.js", // 優先執行的檔案
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": { // 依存模組
"express": "^4.16.2"
}

"private": "true" // 決定這個模組是公有或私有
}

npm install - 安裝 module

接下來安裝模組,安裝模組分成全域安裝(global install)跟區域安裝(local install),區域安裝只會把模組裝在當前的專案資料夾,全域安裝則會把模組安裝到 Node.js 安裝的位置。

大部分的模組只與專案直接相關,因此一般來說我們傾向使用區域安裝。

下面來安裝 express 做為示範。

1
2
3
4
5
6
7
8
# 區域安裝用 express 示範

$ npm install express # 全名
$ npm i express # 簡寫

# 全域安裝用 node static server 示範
$ npm i --global static # 全名
$ npm i -g static # 簡寫

安裝完後檢視資料夾,會多兩項改變:

  1. 專案資料夾下多出 node_module 資料夾,裡面裝著我們專案會使用到的模組 express
  2. package.json 檔案內多出 dependencies 分類,裡面會有 express 模組。

node_modules 裡面除了 express 模組外,還多了其他沒看過的模組。點開 express 資料夾內,會看到 express 模組專屬的 package.json ,點開後發現 dependencies 分類中出現 node_modules 資料夾內部分模組的名稱。

dependencies - 模組的依存關係

一個模組有可能是其他模組所組成的,這就是模組之間的依存關係(module dependency)。而 npm 會自動幫我們把所有依存的模組放進 node_modules ,確保程式不會因為漏掉某些模組而無法順利執行。

package.json 裡面的 dependencies 負責記錄這些模組,一個專案資料夾只要有記錄完整的 package.json,只要輸入 npm i 就會把裡面記錄的所有依存模組都安裝,這讓專案的搬遷變得相當容易。

舉例來說,當我們發佈文件到 git 上面時,若把整包 node_modules 都傳上去,檔案會很肥。我們可以用 git ignorenode_modules 踢出記錄,只傳送 package.json ,當別人從 git 上將專案 pull 下來後,輸入 npm i 就能夠獲取完整的依存模組。

npm list - 查看模組之間的依存

以樹狀結構表示模組間的依存關係,稱為 dependencies tree 。 要查看 dependencies tree ,可輸入 npm list

1
2
npm list
npm ls # 簡寫

npm list 觀察 npm 如何幫我們處理模組之間的依存:

首先注意到 express 有個依存模組 qs

1
2
3
nodeedx@1.0.0 /Users/arel/Documents/ArelLearn/Backend/nodeedx
├─┬ express@4.16.2
│ │ └── qs@6.5.1

node_modules 資料夾內的確可以看到一個 qs 模組,且版本為 6.5.1 。
接著為專案故意安裝較舊版本的 qs 做為依存模組:

1
$ npm i qs@5 # 安裝 5.x.x 的最新版

然後再看一次 dependencies tree :

1
2
3
4
5
nodeedx@1.0.0 /Users/arel/Documents/ArelLearn/Backend/nodeedx
├─┬ express@4.16.2
│ │ └── qs@6.5.1
│ └
└── qs@5.2.1 # 多了這個

舊版本的 qs 確實裝在專案上,且沒有影響到與 express 相依的 qs

node_modules 看一下裡面的 qs 版本,竟然是 5.2.1 !
原來的 6.5.1 版跑去哪了?

點開 express 資料夾,會發現裡面新開了一個 node_modules , 6.5.1 版的 qs 靜靜的躺在裡面。

也就是說,Node.js 會幫我們管理專案內的依存模組之間的依存問題,當有版本衝突時,會自動為我們將模組建立複本並分類。

npm remove - 移除模組

萬一要移除模組的時候,不要手動移除,因為 dependency tree 上面都會紀錄,自己亂刪不一定刪得掉,要使用 npm rm <module> 移除, npm 自然會幫我們連 package.json 內的記錄一併做處理。指令如下:

1
2
3
4
npm remove <模組名稱> # 全名
npm rm <模組名稱> # 簡寫

npm rm -g <模組名稱> # 移除全域模組

npm install 常用的參數

使用 npm install 安裝模組加上特定參數可以指定不同模組的用途。

以安裝 express 模組為例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 安裝最新版模組
npm i express@latest

# 安裝特定版本的模組
npm i express@4.2.0

# 安裝模組並且把模組加到 package.json 的 dependencies
npm i express --save # 預設,跟 npm i 一樣
npm i express -S

# 安裝模組並把模組加到 package.json 的 devDependencies
# 所謂的 devDependencies 就是開發時使用,生產時不用的模組
npm i express --save-dev
npm i express -D

# 這樣就不會把 devDependencies 的東西安裝到生產版本
npm i --production

# 安裝的版本是確切版本,不會向上或向下相容,不會隨新模組問世而更新
npm i express --save-exact
npm i express -E

# 安裝全域模組
npm i -g express

npm install --save-exact - 固定模組版本

為什麼會有 -exact 這種東西的需要呢?

打開 package.json 會發現我們裝的 express 版本長這樣 ^4.16.2 前面那個 ^ 意思為向上相容,以後如果有更新的版本,例如 4.20.2 問世了,用 npm i 部署檔案時, express 會自動安裝最新的版本,這是很危險的,萬一有某個我們用到的功能在新版本被拔,就 GG 了。

-exact 就是用來避免這種事,它會讓 package.json 內的版本不會多前面那個符號,因此每次下載時都會是固定版本的模組。

npm 版本定義

說到版本, Node.js 模組的版本表示方式採用語意版本(Semantic Versioning)簡稱 Semver ,由三個數字所組成。舉 express 的例, 4.16.2,分別的意思如下:

1
2
3
4 # major - 版本有大改,會影響現有功能
16 # minor - 版本有小改,新增新功能但不至於影響現有功能
2 # patch - 版本錯誤修正,不影響功能

大概像這樣,如果列在 dependencies 的版本前面被加了 ^ ,那只有 minor 和 patch 變動時,會隨之更新。如果前面被加的是 ~ ,那就只有 patch 變動時會跟著更新。

即使 ^ 不會動到 major ,但 minor 的版本改動仍有可能產生預期外的輸出結果。相對來說, --save-exact 對產品的部署是較安全的。

題外話,對於正在開發的專案,如果我們要改動版本,我們可以使用以下命令:

1
2
3
$ npm version patch # patch + 1
$ npm version minor # minor + 1
$ npm version major # major + 1

package-lock.json - 鎖定版本

在 npm 5.x.x 版以後,使用命令列更動模組配置時都會自動新增 package-lock.json 這個檔案。裡面記載了從模組到模組的所有依存模組的版本資訊。

npm install 時,所有模組時會以 package-lock.json 內記載的版本狀態來安裝。且不會有向上或向下相容的狀態,全都是確切版本。

既然有了 --save-exact ,為何還會需要 package-lock.json ?其中一個原因是 --save-exact 只能鎖住模組的版本,在沒有額外修改的情況下,不能鎖住模組的依存模組們的版本。

舉例來說,專案使用 express ,其版本被 --save-exact 限制, package.json 內的 dependencies 如下:

1
2
3
"dependencies": {   // 依存模組
"express": "4.16.2" # 固定版本
}

但是 express 模組本身的依存模組有百百種,如果我們非作者本人,根本不可能去限制其依存模組的版本狀態,更不可能花時間去一個個更動。

package-lock.json 就可以解決這個問題。

package.json 透過命令被更新時, package-lock.json 中所有模組的依存狀態也會自動同步,因此要讓專案參與者使用何種版本的模組是我們可以決定的。