深入Node.js的模塊機(jī)制(轉(zhuǎn))

1 Node.js模塊的實(shí)現(xiàn)

之前在網(wǎng)上查閱了許多介紹Node.js的文章,可惜對(duì)于Node.js的模塊機(jī)制大都著墨不多柴信。在后續(xù)介紹模塊的使用之前,我認(rèn)為有必要深入一下Node.js的模塊機(jī)制。

1.1 CommonJS規(guī)范

早在Netscape誕生不久后,JavaScript就一直在探索本地編程的路,Rhino是其代表產(chǎn)物毡证。無奈那時(shí)服務(wù)端JavaScript走的路均是參考眾多服務(wù)器端語言來實(shí)現(xiàn)的,在這樣的背景之下,一沒有特色,二沒有實(shí)用價(jià)值毒费。但是隨著JavaScript在前端的應(yīng)用越來越廣泛,以及服務(wù)端JavaScript的推動(dòng),JavaScript現(xiàn)有的規(guī)范十分薄弱,不利于JavaScript大規(guī)模的應(yīng)用墅茉。那些以JavaScript為宿主語言的環(huán)境中,只有本身的基礎(chǔ)原生對(duì)象和類型,更多的對(duì)象和API都取決于宿主的提供,所以,我們可以看到JavaScript缺少這些功能:

  • JavaScript沒有模塊系統(tǒng)凿跳。沒有原生的支持密閉作用域或依賴管理。

  • JavaScript沒有標(biāo)準(zhǔn)庫僵驰。除了一些核心庫外,沒有文件系統(tǒng)的API,沒有IO流API等棺滞。

  • JavaScript沒有標(biāo)準(zhǔn)接口。沒有如Web Server或者數(shù)據(jù)庫的統(tǒng)一接口矢渊。

  • JavaScript沒有包管理系統(tǒng)。不能自動(dòng)加載和安裝依賴枉证。

于是便有了CommonJS(http://www.commonjs.org)規(guī)范的出現(xiàn),其目標(biāo)是為了構(gòu)建JavaScript在包括Web服務(wù)器,桌面,命令行工具,及瀏覽器方面的生態(tài)系統(tǒng)矮男。CommonJS其實(shí)不是一門新的語言,甚至都不能說它是一個(gè)新的解釋器——實(shí)際上它只是一個(gè)概念或者是一個(gè)規(guī)范室谚。

在這個(gè)規(guī)范中毡鉴,它定義了很多 API ,講通俗點(diǎn)或者直截了當(dāng)點(diǎn)就是函數(shù)啊類啊什么的秒赤,而這些 API 是為那些普通應(yīng)用程序(Native App)而非瀏覽器應(yīng)用使用猪瞬。它的終極目標(biāo)就是提供一個(gè)類似于 Python、Ruby 之類的腳本一樣的標(biāo)準(zhǔn)庫入篮,開發(fā)者可以用這樣的東西一樣來做到 Python陈瘦、Ruby 能做到的事,而非僅僅局限于網(wǎng)頁中的效果或者功能實(shí)現(xiàn)潮售,它也可以跑在本地痊项。所以說下面的事情對(duì)于 JavaScript 來說不再是夢:

服務(wù)端JavaScript應(yīng)用

命令行工具

圖形界面應(yīng)用

混合應(yīng)用(Titanium、Adobe AIR等)

那么酥诽,它具體彌補(bǔ)了前端JavaScript 的哪些空白呢鞍泉?其實(shí)這也涉及了很多前端JavaScript 所沒有涉及的東西,如二進(jìn)制肮帐、編碼咖驮、IO、文件训枢、系統(tǒng)托修、斷言測試、套接字肮砾、事件隊(duì)列诀黍、Worker、控制臺(tái)等等仗处。

CommonJS制定了解決這些問題的一些規(guī)范,而Node.js就是這些規(guī)范的一種實(shí)現(xiàn)眯勾。Node.js自身實(shí)現(xiàn)了require方法作為其引入模塊的方法,同時(shí)NPM也基于CommonJS定義的包規(guī)范,實(shí)現(xiàn)了依賴管理和模塊自動(dòng)安裝等功能枣宫。這里我們將深入一下Node.js的require機(jī)制和NPM基于包規(guī)范的應(yīng)用。

1.2 簡單模塊定義和使用

在Node.js中,定義一個(gè)模塊十分方便吃环。我們以計(jì)算圓形的面積和周長兩個(gè)方法為例,來表現(xiàn)Node.js中模塊的定義方式也颤。

var PI = Math.PI; 
exports.area = function (r) {
    return PI * r * r;     
};
exports.circumference = function (r) { 
    return 2 * PI * r;
};

將這個(gè)文件存為circle.js,并新建一個(gè)app.js文件,并寫入以下代碼:

var circle = require('./circle.js');
console.log('The area of a circle of radius 4 is' + circle.area(4));

可以看到模塊調(diào)用也十分方便,只需要require需要調(diào)用的文件即可

在require了這個(gè)文件之后,定義在exports對(duì)象上的方法便可以隨意調(diào)用郁轻。Node.js將模塊的定義和調(diào)用都封裝得極其簡單方便,從API對(duì)用戶友好這一個(gè)角度來說,Node.js的模塊機(jī)制是非常優(yōu)秀的翅娶。

例如把我們的服務(wù)器腳本放到一個(gè)叫做 start 的函數(shù)里,然后我們會(huì)導(dǎo)出這個(gè)函數(shù)好唯。代碼放在server.js文件:

var http = require("http");

function start() {
    function onRequest(request, response) {
        console.log("Request received.");
        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello World");
        response.end();
    }

    http.createServer(onRequest).listen(8888);
    console.log("Server has started.");
}

exports.start = start;

這樣竭沫,我們現(xiàn)在就可以創(chuàng)建我們的主文件 index.js 并在其中啟動(dòng)我們的HTTP了,雖然服務(wù)器的代碼還在 server.js 中骑篙。創(chuàng)建 index.js 文件并寫入以下內(nèi)容:

var server = require("./server");

server.start();

1.3 exports與module.exports的區(qū)別

上一節(jié)已經(jīng)使用了用來創(chuàng)建函數(shù)的exports對(duì)象蜕提,來導(dǎo)出一個(gè)模塊(假設(shè)一個(gè)名為rocker.js的文件):

exports.name = function() {
    console.log('My name is Lemmy Kilmister');
};

然后你在另一個(gè)文件中調(diào)用:

var rocker = require('./rocker.js');
rocker.name(); // 'My name is Lemmy Kilmister'

但是module.exports到底是個(gè)什么玩意兒? 它合法嗎?

令人吃驚的是module.exports是真實(shí)存在的東西靶端。exports只是module.exports的輔助方法谎势。你的模塊最終返回module.exports給調(diào)用者,而不是exports杨名。exports所做的事情是收集屬性脏榆,如果module.exports當(dāng)前沒有任何屬性的話,exports會(huì)把這些屬性賦予module.exports台谍。如果module.exports已經(jīng)存在一些屬性的話须喂,那么exports中所用的東西都會(huì)被忽略

把下面的內(nèi)容放到rocker.js:

module.exports = 'ROCK IT!';
exports.name = function() {
    console.log('My name is Lemmy Kilmister');
};

然后把下面的內(nèi)容放到另一個(gè)文件中趁蕊,執(zhí)行它:

var rocker = require('./rocker.js');
rocker.name(); // TypeError: Object ROCK IT! has no method 'name'

rocker模塊完全忽略了exports.name镊折,然后返回了一個(gè)字符串'ROCK IT!'。通過上面的例子介衔,你可能認(rèn)識(shí)到你的模塊不一定非得是模塊實(shí)例(module instances)恨胚。你的模塊可以是任何合法的JavaScript對(duì)象 - boolean,number炎咖,date赃泡,JSON, string乘盼,function升熊,array和其他。你的模塊可以是任何你賦予module.exports的值绸栅。如果你沒有明確的給module.exports設(shè)置任何值级野,那么exports中的屬性會(huì)被賦給module.exports中,然后并返回它粹胯。

在下面的情況下蓖柔,你的模塊是一個(gè)類:

module.exports = function(name, age) {
    this.name = name;
    this.age = age;
    this.about = function() {
        console.log(this.name +' is '+ this.age +' years old');
    };
};

然后你應(yīng)該這樣使用它:

var Rocker = require('./rocker.js');
var r = new Rocker('Ozzy', 62);
r.about(); // Ozzy is 62 years old

在下面的情況下辰企,你的模塊是一個(gè)數(shù)組:

module.exports = ['Lemmy Kilmister', 'Ozzy Osbourne', 'Ronnie James Dio', 'Steven Tyler', 'Mick Jagger'];

然后你應(yīng)該這樣使用它:

var rocker = require('./rocker.js');
console.log('Rockin in heaven: ' + rocker[2]); //Rockin in heaven: Ronnie James Dio

現(xiàn)在你應(yīng)該找到要點(diǎn)了 - 如果你想要你的模塊成為一個(gè)特別的對(duì)象類型,那么使用module.exports况鸣;如果你希望你的模塊成為一個(gè)傳統(tǒng)的模塊實(shí)例(module instance)牢贸,使用exports

把屬性賦予module.exports的結(jié)果與把屬性賦予給exports是一樣的镐捧∏彼鳎看下面這個(gè)例子:

module.exports.name = function() {
    console.log('My name is Lemmy Kilmister');
};

下面這個(gè)做的是一樣的事情:

exports.name = function() {
    console.log('My name is Lemmy Kilmister');
};

但是請(qǐng)注意,它們并不是一樣的東西懂酱。就像我之前說的module.exports是真實(shí)存在的東西竹习,exports只是它的輔助方法。話雖如此列牺,exports還是推薦的對(duì)象由驹,除非你想把你模塊的對(duì)象類型從傳統(tǒng)的模塊實(shí)例(module instance)修改為其他的

1.4 模塊載入策略

Node.js的模塊分為兩類,一類為原生(核心)模塊,一類為文件模塊昔园。原生模塊在Node.js源代碼編譯的時(shí)候編譯進(jìn)了二進(jìn)制執(zhí)行文件,加載的速度最快。另一類文件模塊是動(dòng)態(tài)加載的,加載速度比原生模塊慢并炮。但是Node.js對(duì)原生模塊和文件模塊都進(jìn)行了緩存,于是在第二次require時(shí),是不會(huì)有重復(fù)開銷的默刚。其中原生模塊都被定義在lib這個(gè)目錄下面,文件模塊則不定性

node app.js

由于通過命令行加載啟動(dòng)的文件幾乎都為文件模塊逃魄。我們從Node.js如何加載文件模塊開始談起荤西。加載文件模塊的工作,主要由原生模塊module來實(shí)現(xiàn)和完成,該原生模塊在啟動(dòng)時(shí)已經(jīng)被加載,進(jìn)程直接調(diào)用到runMain靜態(tài)方法。

// bootstrap main module.
Module.runMain = function () {
    // Load the main module--the command line arg
    Module._load(process.argv[1], null, true); 
};

_load靜態(tài)方法在分析文件名之后執(zhí)行:

var module = new Module(id, parent);

并根據(jù)文件路徑緩存當(dāng)前模塊對(duì)象,該模塊實(shí)例對(duì)象則根據(jù)文件名加載伍俘。

module.load(filename);

實(shí)際上在文件模塊中,又分為3類模塊邪锌。這三類文件模塊以后綴來區(qū)分,Node.js會(huì)根據(jù)后綴名來決定加載方法:

.js。通過fs模塊同步讀取js文件并編譯執(zhí)行癌瘾。

.node觅丰。通過C/C++進(jìn)行編寫的Addon。通過dlopen方法進(jìn)行加載妨退。

.json妇萄。讀取文件,調(diào)用JSON.parse解析加載。

這里我們將詳細(xì)描述js后綴的編譯過程咬荷。Node.js在編譯js文件的過程中實(shí)際完成的步驟有對(duì)js文件內(nèi)容進(jìn)行頭尾包裝冠句。以app.js為例,包裝之后的app.js將會(huì)變成以下形式:

(function (exports, require, module, __filename, __dirname) {
    var circle = require('./circle.js');
    console.log('The area of a circle of radius 4 is ' + circle.area(4));
});

這段代碼會(huì)通過vm原生模塊的runInThisContext方法執(zhí)行(類似eval,只是具有明確上下文,不污染全局),返回為一個(gè)具體的function對(duì)象。最后傳入module對(duì)象的exports,require方法,module,文件名,目錄名作為實(shí)參并執(zhí)行幸乒。

這就是為什么require并沒有定義在app.js文件中,但是這個(gè)方法卻存在的原因懦底。從Node.js的API文檔中可以看到還有__filename、__dirname聚唐、module、 exports幾個(gè)沒有定義但是卻存在的變量拱层。其中__filename和__dirname在查找文件路徑的過程中分析得到后傳入的。module變量是這個(gè)模塊對(duì)象自身,exports是在module的構(gòu)造函數(shù)中初始化的一個(gè)空對(duì)象({},而不是 null)径缅。

在這個(gè)主文件中,可以通過require方法去引入其余的模塊纳猪。而其實(shí)這個(gè)require方法實(shí)際調(diào)用的就是load方法氏堤。

load方法在載入鼠锈、編譯星著、緩存了module后,返回module的exports對(duì)象同欠。這就是circle.js文件中只有定義在exports對(duì)象上的方法才能被外部調(diào)用的原因铺遂。

以上所描述的模塊載入機(jī)制均定義在lib/module.js中襟锐。

1.5 require方法中的文件查找策略

由于Node.js中存在4類模塊(原生模塊和3種文件模塊),盡管require方法極其簡單,但是內(nèi)部的加載卻是十分復(fù)雜的,其加載優(yōu)先級(jí)也各自不同捌斧。

image
  1. 從文件模塊緩存中加載

    盡管原生模塊與文件模塊的優(yōu)先級(jí)不同,但是都不會(huì)優(yōu)先于從文件模塊的緩存中加載已經(jīng)存在的模塊捞蚂。

  2. 從原生模塊加載

    原生模塊的優(yōu)先級(jí)僅次于文件模塊緩存的優(yōu)先級(jí)。require方法在解析文件名之后,優(yōu)先檢查模塊是否在原生模塊列表中丁存。以http模塊為例,盡管在目錄下存在一個(gè)http/http.js/http.node/http.json文件,require(“http”)都不會(huì)從這些文件中加載,而是從原生模塊中加載解寝。

    原生模塊也有一個(gè)緩存區(qū),同樣也是優(yōu)先從緩存區(qū)加載聋伦。如果緩存區(qū)沒有被加載過,則調(diào)用原生模塊的加載方式進(jìn)行加載和執(zhí)行兵拢。

  3. 從文件加載

    當(dāng)文件模塊緩存中不存在,而且不是原生模塊的時(shí)候,Node.js會(huì)解析require方法傳入的參數(shù),并從文件系統(tǒng)中加載實(shí)際的文件,加載過程中的包裝和編譯細(xì)節(jié)在前一節(jié)中已經(jīng)介紹過,這里我們將詳細(xì)描述查找文件模塊的過程,其中, 也有一些細(xì)節(jié)值得知曉说铃。

require方法接受以下幾種參數(shù)的傳遞:

http腻扇、fs幼苛、path等,原生模塊。

/mod或../mod,相對(duì)路徑的文件模塊济锄。

/pathtomodule/mod,絕對(duì)路徑的文件模塊荐绝。

mod,非原生模塊的文件模塊低滩。

在進(jìn)入路徑查找之前有必要描述一下modulepath這個(gè)Node.js中的概念恕沫。對(duì)于每一個(gè)被加載的文件模塊,創(chuàng)建這個(gè)模塊對(duì)象的時(shí)候,這個(gè)模塊便會(huì)有一個(gè)paths屬性,其值根據(jù)當(dāng)前文件的路徑計(jì)算得到婶溯。我們創(chuàng)建modulepath.js這樣一個(gè)文件,其內(nèi)容為:

console.log(module.paths);

我們將其放到任意一個(gè)目錄中執(zhí)行node modulepath.js命令,將得到以下的輸出結(jié)果:

[
    '/home/jackson/research/node_modules',
    '/home/jackson/node_modules',
    '/home/node_modules',
    '/node_modules'
]

可以看出module path的生成規(guī)則為:從當(dāng)前文件目錄開始查找node_modules目錄;然后依次進(jìn)入父目錄,查找父目錄下的node_modules目錄;依次迭代, 直到根目錄下的node_modules目錄褐筛。

除此之外還有一個(gè)全局module path,是當(dāng)前node執(zhí)行文件的相對(duì)目錄 (../../lib/node)渔扎。如果在環(huán)境變量中設(shè)置了HOME目錄和NODE_PATH目錄的話,整個(gè)路徑還包含NODE_PATH和HOME目錄下的.node_libraries 與.node_modules晃痴。其最終值大致如下:

[
    NODE_PATH,
    HOME/.node_modules,
    HOME/.node_libraries,
    execPath/../../lib/node
]

下圖是筆者從源代碼中整理出來的整個(gè)文件查找流程:

![image](http://upload-images.jianshu.io/upload_images/5420078-1f19f476cf5e1d3d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

簡而言之,如果require絕對(duì)路徑的文件,查找時(shí)不會(huì)去遍歷每一個(gè)node_modules目錄,其速度最快。其余流程如下:

  1. 從modulepath數(shù)組中取出第一個(gè)目錄作為查找基準(zhǔn)笤虫。
  1. 直接從目錄中查找該文件,如果存在,則結(jié)束查找琼蚯。如果不存在,則進(jìn)行下一條查找。
  1. 嘗試添加.js峦睡、.json榨了、.node后綴后查找,如果存在文件,則結(jié)束查找龙屉。如果不存在,則進(jìn)行下一條。
  1. 嘗試將require的參數(shù)作為一個(gè)包來進(jìn)行查找,讀取目錄下的package.json文件,取得main參數(shù)指定的文件唆垃。
  1. 嘗試查找該文件,如果存在,則結(jié)束查找与柑。如果不存在,則進(jìn)行第3條查找。
  1. 如果繼續(xù)失敗,則取出modulepath數(shù)組中的下一個(gè)目錄作為基準(zhǔn)查找,循環(huán)第1至5個(gè)步驟丑念。
  1. 如果繼續(xù)失敗,循環(huán)第1至6個(gè)步驟,直到modulepath中的最后一個(gè)值结蟋。
  1. 如果仍然失敗,則拋出異常推正。

整個(gè)查找過程十分類似原型鏈的查找和作用域的查找植榕。所幸Node.js對(duì)路徑查找實(shí)現(xiàn)了緩存機(jī)制,否則由于每次判斷路徑都是同步阻塞式進(jìn)行,會(huì)導(dǎo)致嚴(yán)重的性能消耗尼夺。

1.6 包結(jié)構(gòu)

前面提到JavaScript缺少包結(jié)構(gòu)尊残。CommonJS致力于改變這種現(xiàn)狀,于是定義了包的結(jié)構(gòu)規(guī)范(http://wiki.commonjs.org/wiki/Packages/1.0)。而NPM的出現(xiàn)則是為了在CommonJS規(guī)范的基礎(chǔ)上,實(shí)現(xiàn)解決包的安裝卸載,依賴管理,版本管理等問題淤堵。require的查找機(jī)制明了之后,我們來看一下包的細(xì)節(jié)寝衫。

一個(gè)符合CommonJS規(guī)范的包應(yīng)該是如下這種結(jié)構(gòu):

一個(gè)package.json文件應(yīng)該存在于包頂級(jí)目錄下。

二進(jìn)制文件應(yīng)該包含在bin目錄下拐邪。

JavaScript代碼應(yīng)該包含在lib目錄下慰毅。

文檔應(yīng)該在doc目錄下。

單元測試應(yīng)該在test目錄下扎阶。

由上文的require的查找過程可以知道,Node.js在沒有找到目標(biāo)文件時(shí),會(huì)將當(dāng)前目錄當(dāng)作一個(gè)包來嘗試加載,所以在package.json文件中最重要的一個(gè)字段就是main。而實(shí)際上,這一處是Node.js的擴(kuò)展,標(biāo)準(zhǔn)定義中并不包含此字段, 對(duì)于require,只需要main屬性即可井赌。但是在除此之外包需要接受安裝流部、卸載球切、依賴管理,版本管理等流程,所以CommonJS為package.json文件定義了如下一些必須的字段:

name : 包名,需要在NPM上是唯一的户辱。不能帶有空格必逆。

description : 包簡介。通常會(huì)顯示在一些列表中。

version : 版本號(hào)。一個(gè)語義化的版本號(hào)(http://semver.org/),通常為x.y.z。該版本號(hào)十分重要,常常用于一些版本控制的場合。

keywords : 關(guān)鍵字?jǐn)?shù)組。用于NPM中的分類搜索。

maintainers : 包維護(hù)者的數(shù)組送膳。數(shù)組元素是一個(gè)包含name晒奕、email、web三個(gè)屬性的JSON對(duì)象。

contributors : 包貢獻(xiàn)者的數(shù)組冀值。第一個(gè)就是包的作者本人抵栈。在開源社區(qū),如果提交的patch被merge進(jìn)master分支的話,就應(yīng)當(dāng)加上這個(gè)貢獻(xiàn)patch的人产艾。格式包含name和email棘幸。

 "contributors": [{
         "name": "Jackson Tian", "email": "mail @gmail.com"
     }, {
         "name": "fengmk2", "email": "mail2@gmail.com"
 }],

bugs : 一個(gè)可以提交bug的URL地址扫茅∠侔欤可以是郵件地址 (mailto:mailxx@domain),也可以是網(wǎng)頁地址(http://url)。%E3%80%82)

licenses : 包所使用的許可證船响。例如:

 "licenses": [{
     "type": "GPLv2",
     "url": "http://www.example.com/licenses/gpl.html",
 }]

repositories : 托管源代碼的地址數(shù)組躬拢。

dependencies : 當(dāng)前包需要的依賴聊闯。這個(gè)屬性十分重要,NPM會(huì)通過這個(gè)屬性,幫你自動(dòng)加載依賴的包。

以下是Express框架的package.json文件,值得參考:

{
  "_args": [
    [
      "express@~4.13.1",
      "/Users/TaoBangren/git@osc/king-node"
    ]
  ],
  "_from": "express@>=4.13.1 <4.14.0",
  "_id": "express@4.13.3",
  "_inCache": true,
  "_installable": true,
  "_location": "/express",
  "_npmUser": {
    "email": "doug@somethingdoug.com",
    "name": "dougwilson"
  },
  "_npmVersion": "1.4.28",
  "_phantomChildren": {},
  "_requested": {
    "name": "express",
    "raw": "express@~4.13.1",
    "rawSpec": "~4.13.1",
    "scope": null,
    "spec": ">=4.13.1 <4.14.0",
    "type": "range"
  },
  "_requiredBy": [
    "/"
  ],
  "_resolved": "https://registry.npmjs.org/express/-/express-4.13.3.tgz",
  "_shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",
  "_shrinkwrap": null,
  "_spec": "express@~4.13.1",
  "_where": "/Users/TaoBangren/git@osc/king-node",
  "author": {
    "email": "tj@vision-media.ca",
    "name": "TJ Holowaychuk"
  },
  "bugs": {
    "url": "https://github.com/strongloop/express/issues"
  },
  "contributors": [
    {
      "name": "Aaron Heckmann",
      "email": "aaron.heckmann+github@gmail.com"
    },
    {
      "name": "Ciaran Jessup",
      "email": "ciaranj@gmail.com"
    },
    {
      "name": "Douglas Christopher Wilson",
      "email": "doug@somethingdoug.com"
    },
    {
      "name": "Guillermo Rauch",
      "email": "rauchg@gmail.com"
    },
    {
      "name": "Jonathan Ong",
      "email": "me@jongleberry.com"
    },
    {
      "name": "Roman Shtylman",
      "email": "shtylman+expressjs@gmail.com"
    },
    {
      "name": "Young Jae Sim",
      "email": "hanul@hanul.me"
    }
  ],
  "dependencies": {
    "accepts": "~1.2.12",
    "array-flatten": "1.1.1",
    "content-disposition": "0.5.0",
    "content-type": "~1.0.1",
    "cookie": "0.1.3",
    "cookie-signature": "1.0.6",
    "debug": "~2.2.0",
    "depd": "~1.0.1",
    "escape-html": "1.0.2",
    "etag": "~1.7.0",
    "finalhandler": "0.4.0",
    "fresh": "0.3.0",
    "merge-descriptors": "1.0.0",
    "methods": "~1.1.1",
    "on-finished": "~2.3.0",
    "parseurl": "~1.3.0",
    "path-to-regexp": "0.1.7",
    "proxy-addr": "~1.0.8",
    "qs": "4.0.0",
    "range-parser": "~1.0.2",
    "send": "0.13.0",
    "serve-static": "~1.10.0",
    "type-is": "~1.6.6",
    "utils-merge": "1.0.0",
    "vary": "~1.0.1"
  },
  "description": "Fast, unopinionated, minimalist web framework",
  "devDependencies": {
    "after": "0.8.1",
    "body-parser": "~1.13.3",
    "connect-redis": "~2.4.1",
    "cookie-parser": "~1.3.5",
    "cookie-session": "~1.2.0",
    "ejs": "2.3.3",
    "express-session": "~1.11.3",
    "istanbul": "0.3.17",
    "jade": "~1.11.0",
    "marked": "0.3.5",
    "method-override": "~2.3.5",
    "mocha": "2.2.5",
    "morgan": "~1.6.1",
    "multiparty": "~4.1.2",
    "should": "7.0.2",
    "supertest": "1.0.1",
    "vhost": "~3.0.1"
  },
  "directories": {},
  "dist": {
    "shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",
    "tarball": "http://registry.npmjs.org/express/-/express-4.13.3.tgz"
  },
  "engines": {
    "node": ">= 0.10.0"
  },
  "files": [
    "History.md",
    "LICENSE",
    "Readme.md",
    "index.js",
    "lib/"
  ],
  "gitHead": "ef7ad681b245fba023843ce94f6bcb8e275bbb8e",
  "homepage": "http://expressjs.com/",
  "keywords": [
    "api",
    "app",
    "express",
    "framework",
    "rest",
    "restful",
    "router",
    "sinatra",
    "web"
  ],
  "license": "MIT",
  "maintainers": [
    {
      "name": "tjholowaychuk",
      "email": "tj@vision-media.ca"
    },
    {
      "name": "jongleberry",
      "email": "jonathanrichardong@gmail.com"
    },
    {
      "name": "dougwilson",
      "email": "doug@somethingdoug.com"
    },
    {
      "name": "rfeng",
      "email": "enjoyjava@gmail.com"
    },
    {
      "name": "aredridel",
      "email": "aredridel@dinhe.net"
    },
    {
      "name": "strongloop",
      "email": "callback@strongloop.com"
    },
    {
      "name": "defunctzombie",
      "email": "shtylman@gmail.com"
    }
  ],
  "name": "express",
  "optionalDependencies": {},
  "readme": "ERROR: No README data found!",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/strongloop/express.git"
  },
  "scripts": {
    "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",
    "test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/",
    "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
    "test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/"
  },
  "version": "4.13.3"
}

除了前面提到的幾個(gè)必選字段外,我們還發(fā)現(xiàn)了一些額外的字段,如bin汗销、 scripts茧跋、engines芥永、devDependencies、author麻捻。這里可以重點(diǎn)提及一下scripts字段纲仍。包管理器(NPM)在對(duì)包進(jìn)行安裝或者卸載的時(shí)候需要進(jìn)行一些編譯或者清除的工作,scripts字段的對(duì)象指明了在進(jìn)行操作時(shí)運(yùn)行哪個(gè)文件,或者執(zhí)行拿條命令。如下為一個(gè)較全面的scripts案例:

"scripts": {
    "install": "install.js",
    "uninstall": "uninstall.js",
    "build": "build.js",
    "doc": "make-doc.js",
    "test": "test.js"
}

如果你完善了自己的JavaScript庫,使之實(shí)現(xiàn)了CommonJS的包規(guī)范,那么你可以通過NPM來發(fā)布自己的包,為NPM上5000+的基礎(chǔ)上再加一個(gè)模塊贸毕。

npm publish <folder>

命令十分簡單郑叠。但是在這之前你需要通過npm adduser命令在NPM上注冊一個(gè)帳戶,以便后續(xù)包的維護(hù)。NPM會(huì)分析該文件夾下的package.json文件,然后上傳目錄到NPM的站點(diǎn)上明棍。用戶在使用你的包時(shí),也十分簡明:

npm install <package>

甚至對(duì)于NPM無法安裝的包(因?yàn)槟承┢婀值木W(wǎng)絡(luò)原因),可以通過github手動(dòng)下載其穩(wěn)定版本,解壓之后通過以下命令進(jìn)行安裝:

npm install <package.json folder>

只需將路徑指向package.json存在的目錄即可乡革。然后在代碼中require('package')即可使用。

Node.js中的require內(nèi)部流程之復(fù)雜,而方法調(diào)用之簡單,實(shí)在值得嘆為觀止摊腋。更多NPM使用技巧可以參見http://www.infoq.com/cn/articles/msh-using-npm-manage-node.js-dependence沸版。

2 Node.js模塊與前端模塊的異同

通常有一些模塊可以同時(shí)適用于前后端,但是在瀏覽器端通過script標(biāo)簽的載入JavaScript文件的方式與Node.js不同

Node.js在載入到最終的執(zhí)行中,進(jìn)行了包裝,使得每個(gè)文件中的變量天然的形成在一個(gè)閉包之中,不會(huì)污染全局變量兴蒸。

瀏覽器端則通常是裸露的JavaScript代碼片段视粮。

所以為了解決前后端一致性的問題,類庫開發(fā)者需要將類庫代碼包裝在一個(gè)閉包內(nèi)。以下代碼片段抽取自著名類庫underscore的定義方式橙凳。

(function () {
    // Establish the root object, `window` in the browser, or `global` on the server.
    var root = this;
    var _ = function (obj) {
        return new wrapper(obj);   
    };
    if (typeof exports !== 'undefined') {
        if (typeof module !== 'undefined' && module.exports) {
            exports = module.exports = _;
        }
        exports._ = _;
    } else if (typeof define === 'function' && define.amd) {
        // Register as a named module with AMD.
        define('underscore', function () {
            return _;
        });    
    } else {
        root['_'] = _;        
    }
}).call(this);

首先,它通過function定義構(gòu)建了一個(gè)閉包,將this作為上下文對(duì)象直接call調(diào)用,以避免內(nèi)部變量污染到全局作用域蕾殴。進(jìn)而通過判斷exports是否存在來決定將局部變量_綁定給exports,并且根據(jù)define變量是否存在,作為處理在實(shí)現(xiàn)了AMD規(guī)范環(huán)境(http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition)下的使用案例。僅只當(dāng)處于瀏覽器的環(huán)境中的時(shí)候,this指向的是全局對(duì)象(window對(duì)象),才將_變量賦在全局對(duì)象上,作為一個(gè)全局對(duì)象的方法導(dǎo)出,以供外部調(diào)用岛啸。

所以在設(shè)計(jì)前后端通用的JavaScript類庫時(shí),都有著以下類似的判斷:

if (typeof exports !== "undefined") {
    exports.EventProxy = EventProxy;
} else {
    this.EventProxy = EventProxy;
}

即,如果exports對(duì)象存在,則將局部變量掛載在exports對(duì)象上,如果不存在,則掛載在全局對(duì)象上钓觉。

轉(zhuǎn)自作者:陶邦仁
鏈接:http://www.reibang.com/p/3cfa31ae5631
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)坚踩,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處荡灾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子批幌,更是在濱河造成了極大的恐慌础锐,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逼裆,死亡現(xiàn)場離奇詭異,居然都是意外死亡赦政,警方通過查閱死者的電腦和手機(jī)胜宇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恢着,“玉大人桐愉,你說我怎么就攤上這事£桑” “怎么了从诲?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長靡羡。 經(jīng)常有香客問我系洛,道長,這世上最難降的妖魔是什么略步? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任描扯,我火速辦了婚禮,結(jié)果婚禮上趟薄,老公的妹妹穿的比我還像新娘绽诚。我一直安慰自己,他們只是感情好杭煎,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布恩够。 她就那樣靜靜地躺著,像睡著了一般羡铲。 火紅的嫁衣襯著肌膚如雪蜂桶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天也切,我揣著相機(jī)與錄音屎飘,去河邊找鬼。 笑死贾费,一個(gè)胖子當(dāng)著我的面吹牛钦购,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播褂萧,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼押桃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了导犹?” 一聲冷哼從身側(cè)響起唱凯,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤羡忘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后磕昼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卷雕,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年票从,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了漫雕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡峰鄙,死狀恐怖浸间,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吟榴,我是刑警寧澤魁蒜,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站吩翻,受9級(jí)特大地震影響兜看,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜狭瞎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一铣减、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧脚作,春花似錦葫哗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至亿扁,卻和暖如春捺典,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背从祝。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工襟己, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牍陌。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓擎浴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親毒涧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贮预,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1 Node.js模塊的實(shí)現(xiàn)# 之前在網(wǎng)上查閱了許多介紹Node.js的文章,可惜對(duì)于Node.js的模塊機(jī)制大都...
    七寸知架構(gòu)閱讀 2,053評(píng)論 1 50
  • Node.js是目前非常火熱的技術(shù),但是它的誕生經(jīng)歷卻很奇特仿吞。 眾所周知滑频,在Netscape設(shè)計(jì)出JavaScri...
    w_zhuan閱讀 3,607評(píng)論 2 41
  • topics: 1.The Node.js philosophy 2.The reactor pattern 3....
    宮若石閱讀 1,057評(píng)論 0 1
  • Node.js是目前非常火熱的技術(shù)唤冈,但是它的誕生經(jīng)歷卻很奇特峡迷。 眾所周知,在Netscape設(shè)計(jì)出JavaScri...
    Myselfyan閱讀 4,064評(píng)論 2 58
  • 今天的晨讀文章談關(guān)于商業(yè)模式的《簡單思考》你虹,書中用簡單的形式來解密“商業(yè)是什么”绘搞、“經(jīng)營是什么”、“怎樣才能引發(fā)創(chuàng)...
    流油果閱讀 109評(píng)論 2 1