npm
前端工程化離不開 npm(node package manager) 或者 Yarn 這些管理工具。npm 或 Yarn 在工程專案中,除了負責依賴的安裝和維護以外,還能透過 npm scripts 串聯起各個職能部分,讓獨立的環節自動運轉起來。
npm誕生背景
npm由程式設計師isaacs(https://github.com/isaacs)發明,他的初步思路是集中管理所有的模組,所有的模組都上傳到倉庫中(registry)。在模組內建立package.json標註模組的基本資訊,像模組名、版本、依賴庫等等,然後透過npm publish釋出模組上傳倒npm倉庫中(registry),最後透過npm install安裝模組,模組安裝到node_modules目錄下。npm於2014年商業化,2020年被Github收購。
npm介紹
npm解決的核心問題是模組管理問題,npm包含cli腳手架、模組倉庫、官網(https://www.npmjs.com/)三大部分。
關於npm的相關資訊可以查閱:https://docs.npmjs.com/about-npm
產生一個模組首先透過npm init來建立一個模組,建立的模組中包含package.json,我們透過修改name、version、dependencies來確定模組的基本資訊,如果模組想使用其他的模組則需要使用npm install來安裝其他的模組,將模組下載到node_modules目錄中,其中模組也包含package.json和node_modules這就是npm的規範。
當module1開發完成後,呼叫npm publish上傳到npm registry中,registry包含兩部分,一部分是public公開的,任何人都能使用,另一部分是private私有的,現在npm商業化後主要靠私有倉庫收費。
public共有倉庫又分為兩種,一種是普通的倉庫,一種是組織的倉庫。
常見的npm命令如下:
- npm init:建立模組
- npm install:安裝模組
- npm publish:釋出模組
- npm link:關聯本地模組進行本地開發
- npm config:檢視或調整本地配置
- npm run:呼叫scripts
npm的侷限
npm只能解決模組的高校管理和獲取問題,無法解決效能載入效能問題。所以模組化發明後,制約其廣泛應用的因素就是效能因素。
npm內部機制和核心原理
我們先來看看 npm 的核心目標:
Bring the best of open source to you, your team and your company.
給你和你的團隊、你的公司帶來最好的開源庫和依賴。
透過這句話,我們可以知道 npm 最重要的一環是安裝和維護依賴。在平時開發中,“刪除 node_modules,重新 npm install”是一個百試不爽的解決 npm 安裝類問題的方法。
npm 的安裝機制和背後思想
它會優先安裝依賴包到當前專案目錄,使得不同應用專案的依賴各成體系,同時還減輕了包作者的 API 相容性壓力,但這樣做的缺陷也很明顯:如果我們的專案 A 和專案 B,都依賴了相同的公共庫 C,那麼公共庫 C 一般都會在專案 A 和專案 B 中,各被安裝一次。這就說明,同一個依賴包可能在我們的電腦上進行多次安裝。
當然,對於一些工具模組比如 supervisor 和 gulp,你仍然可以使用全域性安裝模式,這樣方便註冊 path 環境變數,我們可以在任何地方直接使用 supervisor、 gulp 這些命令。(不過,一般還是建議不同專案維護自己區域性的 gulp 開發工具以適配不同專案需求。)
npm的安裝機制如下圖所示:
npm install 執行之後,首先,檢查並獲取 npm 配置,這裡的優先順序為:專案級的 .npmrc 檔案 > 使用者級的 .npmrc 檔案> 全域性級的 .npmrc 檔案 > npm 內建的 .npmrc 檔案。
然後檢查專案中是否有 package-lock.json 檔案。
如果有,則檢查 package-lock.json 和 package.json 中宣告的依賴是否一致:
- 一致,直接使用 package-lock.json 中的資訊,從快取或網路資源中載入依賴;
- 不一致,按照 npm 版本進行處理(不同 npm 版本處理會有不同,具體處理方式如圖所示)。
如果沒有,則根據 package.json 遞迴構建依賴樹。然後按照構建好的依賴樹下載完整的依賴資源,在下載時就會檢查是否存在相關資源快取:
- 存在,則將快取內容解壓到 node_modules 中;
- 否則就先從 npm 遠端倉庫下載包,校驗包的完整性,並新增到快取,同時解壓到 node_modules。
最後生成 package-lock.json。
構建依賴樹時,當前依賴專案不管其是直接依賴還是子依賴的依賴,都應該按照扁平化原則,優先將其放置在 node_modules 根目錄(最新版本 npm 規範)。在這個過程中,遇到相同模組就判斷已放置在依賴樹中的模組版本是否符合新模組的版本範圍,如果符合則跳過;不符合則在當前模組的 node_modules 下放置該模組(最新版本 npm 規範)。
圖中標明的 npm 不同版本的不同處理情況,並學會從這種“歷史問題”中總結 npm 使用的團隊最佳實踐:同一個專案團隊,應該保證 npm 版本的一致。
npm快取機制
對於一個依賴包的同一版本進行本地化快取,是當代依賴包管理工具的一個常見設計。使用時要先執行以下命令:
npm config get cache
透過這行命令可以得到配置快取的根目錄在C:\Users\使用者名稱\AppData\Local\npm-cache(mac在/Users/使用者名稱/.npm),我們 cd 進入C:\Users\使用者名稱\AppData\Local\npm-cache中可以發現_cacache檔案。在 npm v5 版本之後,快取資料均放在根目錄中的_cacache資料夾中。
可以使用以下命令清除C:\Users\使用者名稱\AppData\Local\npm-cache\_cacache 中的檔案
npm cache clean --force
在_cacache目錄下,有三個目錄:
- content-v2
- index-v5
- tmp
其中 content-v2 裡面基本都是一些二進位制檔案。為了使這些二進位制檔案可讀,我們把二進位制檔案的副檔名改為 .tgz,然後進行解壓,得到的結果其實就是我們的 npm 包資源。
而 index-v5 檔案中,我們採用跟剛剛一樣的操作就可以獲得一些描述性的檔案,事實上這些內容就是 content-v2 裡檔案的索引。
那麼這些快取是如何生成的呢?
當 npm install 執行時,透過pacote把相應的包解壓在對應的 node_modules 下面。npm 在下載依賴時,先下載到快取當中,再解壓到專案 node_modules 下。pacote 依賴npm-registry-fetch來下載包,npm-registry-fetch 可以透過設定 cache 屬性,在給定的路徑下根據IETF RFC 7234生成快取資料。
然後在每次安裝資源時,根據 package-lock.json 中儲存的 integrity、version、name 資訊生成一個唯一的 key,這個 key 能夠對應到 index-v5 目錄下的快取記錄。如果發現有快取資源,就會找到 tar 包的 hash,根據 hash 再去找快取的 tar 包,並再次透過pacote把對應的二進位制檔案解壓到相應的專案 node_modules 下面,省去了網路下載資源的開銷。
注意
這裡提到的快取策略是從 npm v5 版本開始的。在 npm v5 版本之前,每個快取的模組在 ~/.npm 資料夾> > 中以模組名的形式直接儲存,儲存結構是:{cache}/{name}/{version}。
npm的使用技巧
配置 npm init 預設欄位來自定義 npm init 的內容
npm config set init.author.name "test"
npm config set init.author.email "test@gmail.com"
npm config set init.author.url "test.com"
npm config set init.license "MIT"
更多資訊見:npm-config
利用 npm link,高效率在本地除錯以驗證包的可用性
使用 npm link可以將模組連結到對應的業務專案中執行,從工作原理上總結,npm link 的本質就是軟連結,它主要做了兩件事:
- 為目標 npm 模組建立軟連結,將其連結到全域性 node 模組安裝路徑 C:\Users\使用者名稱\AppData\Roaming\npm\node_modules(mac在/usr/local/lib/node_modules/)中
- 為目標 npm 模組的可執行 bin 檔案建立軟連結,將其連結到全域性 node 命令安裝路徑 /usr/local/bin/ 中。
npx 的作用
npx 由 npm v5.2 版本引入,解決了 npm 的一些使用快速開發、除錯,以及專案內使用全域性模組的痛點。
在傳統 npm 模式下,如果我們需要使用程式碼檢測工具 ESLint,就要先透過 npm install 安裝
npm install eslint --save-dev
然後在專案根目錄下執行:
./node_modules/.bin/eslint --init
./node_modules/.bin/eslint yourfile.js
而使用 npx 就簡單多了,你只需要下面 2 個操作步驟:
npx eslint --init
npx eslint yourfile.js
這是因為它可以直接執行 node_modules/.bin 資料夾下的檔案。在執行命令時,npx 可以自動去 node_modules/.bin 路徑和環境變數 $PATH 裡面檢查命令是否存在,而不需要再在 package.json 中定義相關的 script。
npx 另一個更實用的好處是:npx 執行模組時會優先安裝依賴,但是在安裝執行後便刪除此依賴,這就避免了全域性安裝模組帶來的問題。
例如我們使用create-react-app建立工程。
npx create-react-app cra-project
npx 會將 create-react-app 下載到一個臨時目錄,使用以後再刪除