npm 是 Node 的模塊管理器,功能極其強大。它是 Node 得到成功的重要緣由之一。 node
正由於有了npm,咱們只要一行命令,就能安裝別人寫好的模塊 。 react
$ npm install
本文介紹 npm 模塊安裝機制的細節,以及如何解決安裝速度慢的問題。 git
npm install 命令用來安裝模塊到node_modules目錄。 github
$ npm install <packageName>
安裝以前,npm install會先檢查,node_modules目錄之中是否已經存在指定模塊。若是存在,就再也不從新安裝了,即便遠程倉庫已經有了一個新版本,也是如此。 shell
若是你但願,一個模塊不論是否安裝過,npm 都要強制從新安裝,可使用-f或--force參數。 npm
$ npm install <packageName> --force
若是想更新已安裝模塊,就要用到npm update命令。 json
$ npm update <packageName>
它會先到遠程倉庫查詢最新版本,而後查詢本地版本。若是本地版本不存在,或者遠程版本較新,就會安裝。 緩存
npm update命令怎麼知道每一個模塊的最新版本呢? 服務器
答案是 npm 模塊倉庫提供了一個查詢服務,叫作 registry 。以 npmjs.org 爲例,它的查詢服務網址是 https://registry.npmjs.org/ 。 網絡
這個網址後面跟上模塊名,就會獲得一個 JSON 對象,裏面是該模塊全部版本的信息。好比,訪問 https://registry.npmjs.org/react,就會看到 react 模塊全部版本的信息。
它跟下面命令的效果是同樣的。
$ npm view react # npm view 的別名 $ npm info react $ npm show react $ npm v react
registry 網址的模塊名後面,還能夠跟上版本號或者標籤,用來查詢某個具體版本的信息。好比, 訪問 https://registry.npmjs.org/react/v0.14.6 ,就能夠看到 React 的 0.14.6 版。
返回的 JSON 對象裏面,有一個dist.tarball屬性,是該版本壓縮包的網址。
dist: { shasum: '2a57c2cf8747b483759ad8de0fa47fb0c5cf5c6a', tarball: 'http://registry.npmjs.org/react/-/react-0.14.6.tgz' },
到這個網址下載壓縮包,在本地解壓,就獲得了模塊的源碼。npm install和npm update命令,都是經過這種方式安裝模塊的。
npm install或npm update命令,從 registry 下載壓縮包以後,都存放在本地的緩存目錄。
這個緩存目錄,在 Linux 或 Mac 默認是用戶主目錄下的.npm目錄,在 Windows 默認是%AppData%/npm-cache。經過配置命令,能夠查看這個目錄的具體位置。
$ npm config get cache $HOME/.npm
你最好瀏覽一下這個目錄。
$ ls ~/.npm # 或者 $ npm cache ls
你會看到裏面存放着大量的模塊,儲存結構是{cache}/{name}/{version}。
$ npm cache ls react ~/.npm/react/react/0.14.6/ ~/.npm/react/react/0.14.6/package.tgz ~/.npm/react/react/0.14.6/package/ ~/.npm/react/react/0.14.6/package/package.json
每一個模塊的每一個版本,都有一個本身的子目錄,裏面是代碼的壓縮包package.tgz文件,以及一個描述文件package/package.json。
除此以外,還會生成一個{cache}/{hostname}/{path}/.cache.json文件。好比,從 npm 官方倉庫下載 react 模塊的時候,就會生成registry.npmjs.org/react/.cache.json文件。
這個文件保存的是,全部版本的信息,以及該模塊最近修改的時間和最新一次請求時服務器返回的 ETag 。
{ "time":{ "modified":"2016-01-06T23:52:45.571Z", // ... }, "_etag":"\"7S37I0775YLURCFIO8N85FO0F\"" }
對於一些不是很關鍵的操做(好比npm search或npm view),npm會先查看.cache.json裏面的模塊最近更新時間,跟當前時間的差距,是否是在可接受的範圍以內。若是是的,就再也不向遠程倉庫發出請求,而是直接返回.cache.json的數據。
.npm目錄保存着大量文件,清空它的命令以下。
$ rm -rf ~/.npm/* # 或者 $ npm cache clean
總結一下,Node模塊的安裝過程是這樣的。
- 發出npm install命令
- npm 向 registry 查詢模塊壓縮包的網址
- 下載壓縮包,存放在~/.npm目錄
- 解壓壓縮包到當前項目的node_modules目錄
注意,一個模塊安裝之後,本地其實保存了兩份。一份是~/.npm目錄下的壓縮包,另外一份是node_modules目錄下解壓後的代碼。
可是,運行npm install的時候,只會檢查node_modules目錄,而不會檢查~/.npm目錄。也就是說,若是一個模塊在~/.npm下有壓縮包,可是沒有安裝在node_modules目錄中,npm 依然會從遠程倉庫下載一次新的壓縮包。
這種行爲當然能夠保證老是取得最新的代碼,但有時並非咱們想要的。最大的問題是,它會極大地影響安裝速度。即便某個模塊的壓縮包就在緩存目錄中,也要去遠程倉庫下載,這怎麼可能不慢呢?
另外,有些場合沒有網絡(好比飛機上),可是你想安裝的模塊,明明就在緩存目錄之中,這時也沒法安裝。
爲了解決這些問題,npm 提供了一個--cache-min參數,用於從緩存目錄安裝模塊。
--cache-min參數指定一個時間(單位爲分鐘),只有超過這個時間的模塊,纔會從 registry 下載。
$ npm install --cache-min 9999999 <package-name>
上面命令指定,只有超過999999分鐘的模塊,才從 registry 下載。實際上就是指定,全部模塊都從緩存安裝,這樣就大大加快了下載速度。
它還有另外一種寫法。
$ npm install --cache-min Infinity <package-name>
可是,這並不等於離線模式,這時仍然須要網絡鏈接。由於如今的--cache-min實現有一些問題。
(1)若是指定模塊不在緩存目錄,那麼 npm 會鏈接 registry,下載最新版本。這沒有問題,可是若是指定模塊在緩存目錄之中,npm 也會鏈接 registry,發出指定模塊的 etag ,服務器返回狀態碼304,表示不須要從新下載壓縮包。
(2)若是某個模塊已經在緩存之中,可是版本低於要求,npm會直接報錯,而不是去 registry 下載最新版本。
npm 團隊知道存在這些問題,正在重寫 cache。而且,未來會提供一個--offline參數,使得 npm 能夠在離線狀況下使用。
不過,這些改進沒有日程表。因此,當前使用--cache-min改進安裝速度,是有問題的。
社區已經爲npm的離線使用,提出了幾種解決方案。它們能夠大大加快模塊安裝的速度。
解決方案大體分紅三類。
第一類,Registry 代理。
上面三個模塊的用法很相似,都是在本機起一個 Registry 服務,全部npm install命令都要經過這個服務代理。
# npm-proxy-cache $ npm --proxy http://localhost:8080 \ --https-proxy http://localhost:8080 \ --strict-ssl false \ install # local-npm $ npm set registry http://127.0.0.1:5080 # npm-lazy $ npm --registry http://localhost:8080/ install socket.io
有了本機的Registry服務,就能徹底實現緩存安裝,能夠實現離線使用。
第二類,npm install替代。
若是可以改變npm install的行爲,就能實現緩存安裝。npm-cache 工具就是這個思路。凡是使用npm install的地方,均可以使用npm-cache替代。
$ npm-cache install
第三類,node_modules做爲緩存目錄。
這個方案的思路是,不使用.npm緩存,而是使用項目的node_modules目錄做爲緩存。
上面兩個工具,都能將項目的node_modules目錄打成一個壓縮包,之後安裝的時候,就從這個壓縮包之中取出文件。
做者: 阮一峯