1. 程式人生 > >Node.js 專案的依賴管理

Node.js 專案的依賴管理

依賴管理

node 由於 npm 的存在,幾乎每個專案都有一大堆的依賴模組,我們要如何維護這些依賴模組呢?

原始的解決方案:手動管理

在最初接觸 node 的時候,我們通常通過 npm 安裝好依賴模組,然後就把這些依賴模組和我們自己的程式碼推上了github,甚至還會修改這些依賴模組的程式碼。

我的第一個 node 專案 nae 網站,就屬於這一類。不忍直視的把一部分依賴模組傳上了 git(可能是因為我修改了這些模組的內容),同時其他的依賴也沒有通過 package.json統一管理。

進階方案:package.json

當然,在摸索了一段時間之後,大部分的同學都開始知道,原來 npm 是需要和 package.json 一起玩兒的!

於是,我們開始把專案的依賴寫到package.json裡面,例如這個專案。於是,我們可以很方便的只需要執行:

$ git clone git[@github](/user/github).com:dead-horse/socket.io-sample.git
$ cd socket.io-sample
$ npm install

然後,依賴就按照我們在 package.json 裡面寫的裝好了。

同時稍微細心一點的同學可能會發現,現在我的這個專案裡面 node_modules 資料夾不見了,因為我把它加到了 .gitignore 檔案中去了。為什麼要這樣做?

  1. 保持程式碼庫的精簡。
  2. 每次更新依賴的變更會汙染提交之間的diff。
  3. 一些 c++ addon 在不同的環境和 node 版本下需要重新編譯,而如果別人從程式碼庫拉下來的程式碼已經有了你編譯好的程式碼,npm 是不會重新安裝它們的。

高階方案:使用專案構建工具

上面通過 package.json 管理依賴的這一套解決方案,在入門和學習的很長一段時間內可能都已經足夠了。但是,世界沒有這麼單純,當你真正開始使用 node 做一些實際的工作的時候,你會發現進階方案已經不太夠用了:

  1. npm 太慢,預設 python 版本太低,各種原因導致我們安裝依賴可能並不是簡單的一句 npm install 就可以解決的。
  2. 單元測試、覆蓋率報告、壓測報告,各種編譯,越來越多的命令需要執行。

是時候引入專案構建工具來幫我們解決這些問題了。這次出現在我們視野中的是 GNU make。它被廣泛應用在 c 和 c++ 的專案構建之中,而我們的是 node 專案,為什麼選擇它?

  1. 幾乎所有的伺服器,肯定都需要有 c / c++ 的編譯環境,所以 make 工具也會預設的出現在幾乎所有的伺服器上。
  2. 可以直接呼叫執行 shell 命令。
  3. 它具有依賴檢查的功能,且語法簡單。

cnpmjs.org 就是一個通過 make 來進行專案構建的 node 專案。我們稍微精簡一下它的 Makefile 檔案:

TESTS = $(shell ls -S `find test -type f -name "*.test.js" -print`)
REPORTER = tap
TIMEOUT =30000
MOCHA_OPTS =

install:[@npm](/user/npm) install --registry=http://registry.cnpmjs.org --cache=${HOME}/.npm/.cache/cnpm --disturl=http://dist.u.qiniudn.com

test: install
        [@NODE_ENV](/user/NODE_ENV)=test ./node_modules/mocha/bin/mocha \
                --reporter $(REPORTER) \
                --timeout $(TIMEOUT) \
                --require should \
                $(MOCHA_OPTS) \
                $(TESTS)

test-cov:@$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=travis-cov

test-cov-html:[@rm](/user/rm)-f coverage.html
        @$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=html-cov > coverage.html
        [@ls](/user/ls)-lh coverage.html

.PHONY: test

基於這個 Makefile 檔案,我們可以:

  • make install: 從 cnpmjs.org 快速安裝依賴
  • make test: 安裝所有的依賴並通過 mocha 執行單元測試。
  • make test-cov && make test-cov-html: 執行單元測試並通過 blanket 模組來跑專案的測試覆蓋率,生成 html 格式的測試報告。

版本管理

在前面,我們已經進化到了通過專案構建工具來管理專案依賴了,但是還有一個問題我們還沒有解決:依賴模組的版本

在講下面的東西之前,我們先來看看所有 npm 中模組版本遵循的規範:semver 2.0

  1. 所有的版本都是 MAJOR.MINOR.PATCH 形式。
  2. package.json 中 可以指定依賴模組為特定版本或者特定的版本範圍。
    • 1.2.3=1.2.3:指定版本為1.2.3
    • >1.2.3<1.2.3:大於/小於 1.2.3
    • >=1.2.3<=1.2.3:大於等於/小於等於 1.2.3
    • 1.2.3 - 2.3.4:大於1.2.3並且小於2.3.4
    • ~1.2.3:合理的靠近1.2.3,等價於 >=1.2.3-0 <1.3.0-01.3.0-beta不滿足這個判斷條件
    • ~1.2: 等價於 >=1.2.0-0 <1.3.0-0,所有以1.2開頭的版本,同樣等價於 1.2.x
    • ~1:等價於 >=1.0.0-0 <2.0.0-0,所有以1開頭的版本,等價於 1.x
    • *:任意版本

我們有了這些管理版本的限定方法,看看我們能夠怎麼來控制依賴的版本。

豪放派

"dependencies":{"connect":"2.x","mysql":"2.x","redis":"*","debug":"*","eventproxy":"*","connect-redis":"*"}

這一種風格,最大的好處是不太需要修改這些依賴模組的版本了。但是隱藏的風險卻很大:

  1. npm 有快取機制,所以如果用 *,不能保證從 npm 安裝到的是最新版本。
  2. 你本地安裝完依賴,開發並測試通過了,可能生產環境安裝到的依賴的版本和開發時可能不一樣,一旦因此引入了隱藏的 bug,將會非常難發現。

因此,線上專案不太建議通過此種方式來管理依賴的版本。

婉約派

"dependencies":{"connect":"~2.12.0","mysql":"~2.0.0","redis":"~0.10.0","debug":"*","eventproxy":"~0.2.6","connect-redis":"~1.4.6"}

採用這種風格時,你需要跟蹤你的依賴的版本,來決定你是不是要升級到新的版本。線上依賴的版本和本地依賴的版本的相差,也被限定到了最小的級別。當然還是有一定的風險。

保守派

"dependencies":{"connect":"2.12.0","mysql":"2.0.1","redis":"0.10.0","debug":"*","eventproxy":"0.2.6","connect-redis":"1.4.6"}

這種風格的好處在於,它嚴格的限定了版本,線上依賴和本地依賴的差異基本已經降到了最低。當然壞處也很明顯,你幾乎要跟蹤所有依賴的版本情況,來決定是不是要升級你的依賴。

實際應用

在我們 node 的實際應用中,我們選擇了第三種也就是最保守的方案,這樣可以讓我們儘量不會引入那些莫名其妙的bug。當然,我們是很難堅持手工去維護這些模組的版本的,經常在過了很長一段時間後,突然發現專案的依賴都已經很舊了,這些版本升級帶來的 bug fix 我們都沒有享受到。

我們需要一個工具來幫助我們維護專案的依賴:

autod

autod:一個自動分析專案所有的檔案,獲取所有的專案依賴和它們的版本的工具。

autod 同時可以根據我們傳遞的一些選項和引數,來直接更新 package.json 檔案:

  • -w: 獲取依賴並更新寫入 package.json 檔案
  • -e public,view: 不分析 public 和 view 中的檔案
  • -k connect: 保持 connect 模組在 package.json 中的版本不被 autod 改變
  • -d nan: 無論有沒有在專案中直接 require nan 這個模組,也會獲取它的最新版本寫入 package.dependencies 中

通過這個工具,我們可以很輕鬆的跟蹤到所有依賴的最新版本,同時可以自動更新我們的 package.json 檔案,新引入的模組也不需要手動去更新 package.json 檔案了,一切都可以交給 autod 來完成。

autod pic

整合到 make

通常,我們會在 Makefile 中加入 autod 相關的配置項,來自動化完成這個過程:

# in Makefile
autod: install
        @./node_modules/.bin/autod -w -e public,view,docs,backup
        @$(MAKE) install

# in package.json"devDependencies":{"autod":"~0.0.11"}

這樣,我們只需要執行 make autod,就會按照我們的配置,更新 package.json 檔案,並重新安裝這些模組了。

例如,我在程式碼裡面需要引入 async 模組,這時候,只需要在程式碼裡面:

var async =require('async');

然後在終端行執行:

$ make autod

就完成了 async 模組的安裝,並將最新的 async 版本寫入到了 package.json 檔案中。

最後,來看看 cnpmjs.org 通過 autod 管理依賴版本的效果吧!