1.1 Node 和 Npm 命令
安裝完成後,你會發現運行 Node.js 非常簡單,它主要有兩部分組成:node 和 npm 命令。
Node 命令使用非常簡單。盡管它有很多參數,但是一般情況下你只會給 node 傳遞一個參數,就是 Node.js 源文档的文档名。例如:
node.js
node 命令會在源文档(本例中是 hello.js)中解析 Node.js 代碼,執行代碼,完成後退回到 shell 或者命令行。
注意到 hello.js 文档擴展名爲 .js 。.js 擴展名表示 JavaScript 代碼。好處在於,以 .js 爲擴展名的文档既可以是客戶耑 JavaScript,也可以是服務器耑 Node.js 代碼。不過,盡管這兩者都是用JavaScript,但是他們之間並沒有其他共同之處了。客戶耑JavaScript代碼需要服務於瀏覽器,而服務器耑Node.js代碼需要可執行node命令或者可以訪問通過node命令運行的main Node.js代碼。這導致了不必要的誤解。
在一些Node.js項目中,客戶耑的JavaScript文档放在同一個文档夾中,比如client文档夾,而Node.js文档放在另一個文档夾,比如server。通過文档夾方案來分離客戶耑JavaScript文档和Node.js文档仍然會存在一些問題,因爲很多代碼編輯器在標題欄或者標簽中只顯示文档名而不是全路徑。
所以,在我的項目中,對Node.js文档採用了.njs作爲擴展名,對客戶耑JavaScript文档則保留.js作爲擴展名。讓我說得更清楚一點,.njs文档名並不是標準擴展名,至少,現在還不是(也許永遠都不會)。我還用Google搜索了一下,一般做法是Node.js的擴展名爲.js。爲了避免一直混淆客戶耑和服務器耑的JavaScript,我使用了.njs作爲Node.js代碼的擴展名,在PHP到Node.js移植過程中,我也建議這麽做。
所以,我使用hello.njs代替之前的hello.js:
node hello.njs;
本書其餘部分都會使用.njs作爲Node.js文档擴展名。
簡單的hello.njs文档内容爲:
console.log('Hello World!');
如果你運行node hello.js,會在控制台輸出“Hello world!”然後退出。
爲了運行一個Web服務器,可以使用hellosvr.njs源文档:
var http = require('http'); http.createServer(function(req,res){ res.writeHead(200,{'Content-Type':'text/plain'}); res.end('Hello World!\n'); }).listen(1337,'127.0.0.1'); console.log('Server running ai http://127.0.0.1:1337/');
如果運行node hellosvr.njs,終耑命令行會掛起。服務器必須一直運行,才能接受來自頁面的請求並做出響應。
如果打開Firefox或者Chrome,在地址欄輸入http://127.0.0.1:1337,就可以看到一個簡單的頁面,顯示“Hello world!”。事實上,訪問http://127.0.0.1:1337/index.html,或者http://127.0.0.1/abc,甚至是http://127.0.0.1/abc/def/ghi,都會看到同一個顯示“Hello world!”的頁面。因爲我們現在的服務器對所有頁面請求都做出同樣的響應。
這段代碼中重要的是第一行,使用Node.js 全局函數require()。Require()方法用於加載一個你想要使用的的Node.js模塊。模塊是指數據和方法的一個集合,提供了某個特定方面的功能。在本例中,Node.js的http模塊用於提供HTTP服務器功能。
Node有很多内建的模塊:http、https、fs、path、crypo、url、net、dgram、dns、tls和child_process。這些内建模塊的功能隨著版本變化也有不同。
從設計角度來講,一個模塊代表了一個命名空間(namespace)。命名空間是指加在數據或者函數引用之前的額外描述。比如,http是createServer()方法的命名空間。在Node.js中,命名空間就是一個對象。當http模塊被加載時,require()方法返回一個對象並賦值給http變量,變量名並不一定非要是http,也可以是“xyzzybub”,如果是這樣,服務器創建的方法就是xyzzybub.createServer()。
爲什麽需要有命名空間呢?爲什麽不直接把所有數據和方法都作爲全局變量呢?
Node.js期望新的模塊代表新的功能,比如訪問MySQL數據庫,應該由其他人開發然後在Node.js安裝完成後集成在node命令中。而在這些模塊中的數據和方法名可能各種各樣,開發人員很可能不小心使用了別的開發人員在其他模塊中使用的函數名。但是由於模塊都存在於一個命名空間中,可以由命名空間來區分同名的函數。實際上,在之前的編程語言比如C++和JAVA的基礎上,Node.js的一個重要改進就是允許模塊的使用者自定義模塊的命名空間,用戶自己會將模塊賦值給自定義的變量,比如http或者xyzzybub。
這些具有新功能的新模塊可以看做是包(package)。包是指一個可以添加到node命令行但是不是默認集成到node的模塊。模塊和包的界線並不明顯,可以理解爲只是專業術語上的變化。
Npm(node package manager)命令可以爲node添加新的package。
要安裝一個package,首先使用Google或者其他搜索引擎找到你想要安裝的那個npm package,大部分時候,都在GitHub上。另一種不同於搜索引擎的方式是使用npm search命令來查找想要安裝的package。
設想一下我們需要一個Web服務器通過硬盤的文档提供靜態頁面,而不總是返回“Hello world!”。爲了找到一個Node.js靜態文档服務器模塊,一個好的搜索關鍵字是“nodejs static file server”。或者直接使用“npm search static”、“npm seach file”或者“npm search server”,會列出npm package名字或者描述中包含“static”、“file”或者“server”的package。不管使用哪一種方法,你都會找到Alexis Sellier,a.k.a cloudhead,創建的非常流行的靜態文档服務器模塊,地址是:https://github.com/cloudhead/node-static。
可以用以下命令安裝package(附加選項,比如-g或者--global,可以用於配置包的安裝方式):
npm install node-static
npm命令會獲取package,然後成功安裝(希望是這樣)。以下是安裝成功的一些輸出:
npm http GET https://registry.npmjs.org/node-static npm http 200 https://registry.npmjs.org/node-static npm http GET https://registry.npmjs.org/node-static/-/node-static-0.5.9.tgz npm http 200 https://registry.npmjs.org/node-static/-/node-static-0.5.9.tgz node-static@0.5.9 ./node_modules/node-static
GET表示使用HTTP GET方法來嘗試獲取package。200表示HTTP GET方法返回“HTTP status 200 OK”,意味著獲取文档成功。
現在有很多npm package,但是只有一部分非常流行,比如express、node-static、connect、sockets.io、underscore、async和optimist。
使用以下httpsvr.njs文档,來實現一個Web服務器提供靜態頁面:
var http = require('http'); var static = require('node-static'); var dile = new static.Server(); http.createServer(function(req.res){ file.serve(req,res); }).listen(1337,'127.0.0.1'); console.log('Server running ai http://127.0.0.1:1337/');
這就是Node.js開發的基本過程。需要編輯器來創建或者修改包含Node.js代碼的.njs文档。當需要新的功能而node中沒有時,npm命令可以下載並安裝包含所需功能的npm 包。node命令用於運行.njs文档來執行Node.js代碼,然後可以測試和使用該Web應用。
現在,我們已經看到過3個Node.js服務器了:hello.njs、hellosvr.njs還有httpsvr.njs。這些源文档都很簡單,不必關心它們是怎麽創建的。可以用任何文本編輯器來創建這些文档。如果什麽地方出錯了,很容易直接編輯修改。
我們可以放心地假設你已經有了一個複雜的Web應用,有很多文档,成千上萬行需要移植的PHP代碼。移植會是一個冗長的按部就班的過程。
第一個步驟需要創建一個Node.js的樣板文档用於保存新的Node.js代碼,具體在圖Node.js基本介紹中描述。這個樣板文档會被修改,用於響應用戶發起的特定URL請求。這種轉換是爲了使Node.js服務器對客戶耑的響應方式與PHP服務器相同。爲了做到這一點,需要修改Node.js樣本文档來處理每個HTTP請求,並將請求轉發到特定的Node.js代碼段進行處理,這些Node.js代碼段是之後用於在Node.js中實現特定PHP頁面的功能。
第二個步驟是重構PHP代碼,主要在第3章和第4章中介紹,使PHP更容易移植到Node.js,換句話說,使PHP代碼對Node.js更友好。轉換的過程可能有些讓人吃驚,它不僅僅是維持現有的PHP代碼狀態不變,複制到一個Node.js文档,然後一行一行地將PHP代碼轉換爲Node.js。PHP和Node.js代碼都會同步改進並且添加新的特性,所以鋻於兩種語言的不同工作方式,PHP和Node.js都需要保留一些各自的特性來克服這些差異,這是有意義的。PHP代碼需要做一些重構,並且爲了之後需要創建的Node.js代碼的功能做出一些犧牲。在移植結束的時候,兩個代碼庫看起來會非常相似,都使用一種混合元語言編寫,PHP的一系列慣例和算法都很容易移植到Node.js。這種元語言會使兩個代碼庫看起來有點奇怪,但是功能都是完備的,並且隨著時間推移,維護和改進這兩個代碼庫的開發人員會越來越熟悉和理解這種語言。即使你決定最終扔掉所有的PHP代碼只使用Node.js,也最好先從重構PHP代碼開始,將PHP和Node.js代碼都移植到那種奇怪的元編程語言,然後扔掉PHP,最後再將混合語言的Node.js重構爲純Node.js。不管你的目標是什麽,重構PHP代碼是將任何PHP移植到Node.js過程的關鍵步驟。
第三步就是將PHP頁面從PHP文档中複制到Node.js文档中。當然,Node.js服務器一定會掛掉,正在運行的node命令會立即退出,輸出錯誤的堆棧信息。
第四步就是轉換並修改新添加的Node.js文档,在剩下的章節中會介紹,然後就成爲可以工作的Node.js代碼了。最開始,Node.js服務器無法工作,並且立即退出、列印堆棧信息。堆棧追蹤信息可以指出錯誤發生的地方,可能是由於部分PHP代碼沒有完全轉換爲Node.js代碼或者轉換錯誤。分析清楚問題之後,可以對整個Node.js文档使用後面章節介紹的轉換技術。比如,第7章介紹了使用array()初始化數組的方式將PHP轉換爲Node.js對象的初始化方法,使用花括號({})。當Node.js服務器可以重新運行之後,可能可以運行,但是很可能又繼續出錯退出。最終,Node.js代碼完善,服務器可以運行。
令人驚訝的是有相當多的PHP代碼存在於Node.js代碼文档中,但不會導致Node.js服務器運行出錯退出。當你對轉換過程更熟悉之後,你會發現PHP和Node.js有很多相似,沒轉換的PHP代碼會被node命令解析,並在node運行中可以接收HTTP請求,直到真的需要執行一些PHP代碼時才會失敗。
一旦Node.js代碼沒有問題,服務器可以運行,就可以開始用客戶耑進行測試了。客戶耑一般都是瀏覽器,比如Firefox、Google Chrome。一般來說,使用客戶耑進行測試時,Node.js會在某個點上出錯退出,然後你需要分析堆棧追蹤信息,並做出修正。一段時間之後,你會形成一系列特定的測試用例,可以用於客戶耑執行以發現未定位的轉換問題,或者通過這些測試確保服務器工作正常。
有時,也可以用可視化的比對工具來比較PHP和Node.js代碼。通過在源PHP代碼中一步步地查看,可以更容易地定位新的Node.js代碼中的問題。這可以提醒你使用一些尚未使用但是需要的轉換技術,也可以追蹤轉換過程並很好地控制該過程。
剩下的PHP到Node.js的轉換過程就是將之前的步驟重複執行很多次,直到PHP代碼都轉換爲Node.js,並且Node.js代碼可以穩定地工作。根據PHP代碼庫的大小,轉換過程可能耗時幾個月,但是如果你下定決心,轉換過程可以很快結束。