Hi 大家好,我們昨天介紹了在你的專案中使用 NPM 幾個最基本的步驟
npm init 和 npm install
我們可以注意到,npm init 執行的動作很簡單
那就只是幫我們創建 package.json 的 boilerplate
而像是有廣大生態系的 npm,其實 package.json 裡面的內容也是包山包海的
最後的進度是到,我們發現了 npm install 後有三樣東西改變了
package.json 記錄了你剛剛裝的套件{
"name": "my-project",
"version": "1.0.0",
"description": "this is my project",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "alxtz",
"license": "ISC",
"dependencies": {
"selenium-webdriver": "^3.6.0"
}
}
node_modules 裡面除了剛剛安裝的 selenium-webdriver,還安裝了許多其他的套件
以及莫名其妙出現的 package-lock.json
(備註: 我這裡使用的是 npm 第 5.6.0 版,如果你發現 npm install 的結果不同,建議至少升級至 npm 5.0.0 以上)
記得我最早在寫 Python 時
許多小的 Project,會在 README 裡面寫
這個 Project 需要使用哪個 Dependency
有寫還好,我頂多使用 pip 一個一個安裝
但是遇到什麼資訊都沒有的 Project,你就只得等執行後
看執行錯誤,把相對應的套件裝起來
這邊 npm 為了方便
他會在你每次 npm install 時
都將有安裝的套件寫入 package.json
這樣以後別人要使用你的 code
會可以很輕易的知道該使用哪些 dependency
而且還可以確定使用的版本
(Python 後來有使用了 venv 這樣的 Dependency 管理機制,後面會提到)
這邊有個有意思的地方
那就是,跟 python 使用 pip 不太一樣
在 python 裡,使用 pip 安裝過 selenium 的話
你會發現,你之後再同台電腦上
不管寫什麼程式,都可以直接 import selenium
這個原因是,pip 預設會將這個函式庫裝在類似 usr/local/lib 這樣的地方
讓你所有寫的 Python 檔,預設都去一個地方讀這些函式庫
usr/local/lib 哪裡不好?這樣最大的問題是
當你 Project 有一個以上時
有時候不同的需求
可能會使用到同個函式庫的第 2 版 或 第 3 版
這時候,因為你的 usr/local/lib 只能存在一個版本的 selenium
你每次都可能得把它覆蓋掉
並且
當你想把你的 code 放到 server 端去跑時
你很難一眼看出這一堆 usr/local/lib 的函式庫
有哪些是被你專案所需要的,其他是其他用途的
這邊 npm 解決這個問題的做法
是每個專案要使用的函式庫
都直接安裝在他的資料夾底下
保持每個環境(資料夾),永遠都不會有相衝突的機會
而這個資料夾就是 node_modules
(你可能會覺得這樣有重複的函式庫,豈不是會浪費很多空間。
沒錯! npm 這樣本地儲存的方式的確比較耗費空間
但是他同時也避免了上述的全部問題
所以後來像是 Python 社群,也自己開發了一套工具 venv
他可以把 pip 預設的安裝路徑變成這個專案資料夾底下
並且創建一份 requirements.txt 來記錄所有依賴
而在 Ruby 裡,比較接近的東西會是 bundle)
這邊其實就非常簡單了
有了 package.json 紀錄我們所有的函式庫
我們之後想把 Project 放到另一個環境時
我們只需要複製我們的原始碼,和 package.json
之後執行 npm install,如果後面不加套件名稱的話
npm 會當作是你想把 package.json 上現在缺的 Dependency 都裝回來

這邊是一個範例
首先我先使用了 tree node_modules -L 1 這個指令
來展示 node_modules 下面有的所有 dependency
接著,我使用 rm -rf node_modules 來把 node_modules 整個砍掉
但是我們會發現在再執行 npm install 之後
我們之前所需要的函式庫都回來了
這可以幫助 npm 跟 git 相容
因為你不需要把整個龐大的 node_modules 都丟進版本控制裡
只要把 package.json 儲存起來就好了!
如果你有注意到的話,package.json 雖然只有紀錄 selenium-webdriver 一個套件
可是我們卻安裝了 30 個函式庫
node_modules
├── balanced-match/
├── brace-expansion/
├── concat-map/
├── core-js/
├── core-util-is/
├── es6-promise/
├── fs.realpath/
├── glob/
├── immediate/
├── inflight/
├── inherits/
├── isarray/
├── jszip/
├── lie/
├── minimatch/
├── once/
├── os-tmpdir/
├── pako/
├── path-is-absolute/
├── process-nextick-args/
├── readable-stream/
├── rimraf/
├── sax/
├── selenium-webdriver/
├── string_decoder/
├── tmp/
├── util-deprecate/
├── wrappy/
├── xml2js/
└── xmlbuilder/
(還只有一個是 selenium)
原因是,(這裡的)selenium-webdriver 本身也是使用 node.js 開發的專案
他本身八九不離十也是使用 package.json 來管理他的依賴
我們這邊可以實際進入 node_modules/selenium-webdriver 來看看
可以發現,selenium-webdriver 因為也使用 npm 當作 package manager
他的檔案結構跟我們的專案很像

而他的 package.json 也同樣地寫著 selenium-webdriver 所有會用到的 dependency
{
...
"dependencies": {
"jszip": "^3.1.3",
"rimraf": "^2.5.4",
"tmp": "0.0.30",
"xml2js": "^0.4.17"
},
"devDependencies": {
"express": "^4.14.0",
"mocha": "^3.1.2",
"multer": "^1.2.0",
"promises-aplus-tests": "^2.1.2",
"serve-index": "^1.8.0",
"sinon": "^1.17.6"
},
...
"name": "selenium-webdriver",
...
}
(這邊要注意的欄位有 devdependencies 和 dependencies,詳細差別後續篇章會介紹)
selenium 這個專案也有用到像是 jszip, express 這樣的套件
可是在他的資料夾底下,卻沒有 node_modules 來給 selenium 使用
原因是我們是把 selenium 當作依賴加入我們的資料夾
npm 聰明的地方是,他在安裝 selenium 時
會順便把 package.json 讀過一遍
如果看到有其他 dependency,就會在遞迴(重複)性的去安裝這些 dependency
像是假設 jszip 又需要其他 dependency,將會一直安裝直到全部都有為止
這也是 npm install 為什麼會花比較久時間的原因
希望今天的介紹可以讓你對 npm 有一個基本,但是扎實的理解
明天我們會介紹 package-lock.json 這個檔案,與 npm 的競爭對手,yarn
有任何問題都歡迎提問