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
有任何問題都歡迎提問