在 Node.js 上使用 Dojo
簡介: Node.js 讓服務器端和客戶端的編程語言得到了統一,而 Dojo 則讓開發框架得到了統一。得益于優良的架構,Dojo 能同時在服務器端和客戶端運行,從而讓 Web 開發人員用同一種語言,同一個框架就能實現完整的 Web 應用。
Node.js 最近非常火熱,不僅開源社區對其非常關注,甚至微軟對其也提供了官方的支持,從而讓 Node.js 也能運行于 Windows 系統,這為 Node.js 的進一步流行奠定了基礎。本文將介紹 Node.js 和 Dojo 的模塊管理機制,并在此基礎上詳細介紹在 Node.js 上運行 Dojo 框架的方案。最后通過一個例子,演示如何用 Dojo 的 DTL 模塊來解析一個基于 Django 模板語言的模板文件。
一個普遍的看法是 Node.js 讓 JavaScript 成為了服務器端語言,于是自然很多人都把 Node.js 看成了一個 Web Server。但實際上并非如此,Node.js 只是一個 JavaScript 宿主環境,它解釋并運行 JavaScript,同時提供了很多原生對象(Native Object)讓 JavaScript 可以做更多的事情,例如進行網絡通信、文件處理等等。就像,您可以用 Perl、Python 寫一個 Web Server 一樣,您也可以用 JavaScript 寫 Web Server。Node.js 適合創建 Web Server,因為它能高效的處理并發請求,但它自身并不是一個 Web Server。澄清這一點,我們就完全可以像用 Perl 一樣來用 JavaScript,讓它成為一種工具語言。當出現產品級的基于 Node.js 的 Web Server 時,我們也就能更加容易的上手進行 Web 服務器端開發。
JavaScript 本身是一個設計非常精簡的語言,功能相對簡單,因為它的初衷并不是用來開發復雜的應用,而只是用于自動化的來操縱瀏覽器。面對日益復雜的前端應用,已經出 現了很多類庫來解決這個問題,比如 jQuery、ExtJs、Dojo、YUI。這些類庫除了實現了瀏覽器兼容的 API,同時還有一大部分功能是純粹的用來增強 JavaScript 的功能。而這部分功能,正是服務器端和瀏覽器端通用的,我相信如果在這方面提供一致性,一定是廣大 Web 開發人員所喜聞樂見的,這也是我寫這篇文章的目的。
這么多類庫之中,Dojo 是所有當前類庫中最適合運行于 Node.js 的,原因有四:
- Dojo 和 Node.js 都基于 CommonJS 的 Module 規范。
- Dojo 天然支持 Node.js,很好的對瀏覽器相關代碼做了隔離。啟動腳本能自動檢測 Node.js 運行環境檢測,無需任何修改即可直接使用。
- Dojo 所采用的單元測試框架和打包工具都是用 JavaScript 寫成,可以直接在 Node.js 上運行。從而,Dojo+Node.js 可以完成整個應用開發的生命周期。
- Dojo 有完整的面向對象體系,適合大型應用的開發。
Dojo 一直以來都這樣定義自己:JavaScript toolkit,而并不是 Ajax Library 之類的簡單的以前端應用為目標的框架。可見增強 JavaScript 功能是 Dojo 的一個重要目標,它的強大的面向對象的機制,事件機制,豐富的工具庫,已經為大型前端應用的開發提供了很好的基礎。而現在,它將又能在 Node.js 上發揮作用。
Node.js 和 Dojo 都遵循 CommonJS 的模塊相關規范。Node.js 支持的是 Module1.0 規范,而 Dojo 支持的是 AMD(Asynchronous Module Definition,即異步模塊定義)規范。雖然是兩個規范,但它們都是描述了模塊的定義和加載機制,有很多共同點。這就為 Dojo 運行于 Node 提供了天然的基礎。在規范中,JavaScript 文件和模塊是一一對應的關系,每個文件就是一個模塊,模塊之間可以通過相對路徑來引用。
對于 Node.js,要使用一個模塊非常簡單,直接用 Module1.0 規范中定義的 require 函數即可,例如:
var fs = require('fs'); var content = fs.readFileSync('filePath' ,' utf8' ); |
這里的 fs 是 Node.js 自帶的文件系統操作的模塊,可以通過它的標識“fs”來載入它,從而可以調用其提供的方法。這種模塊依賴的方法和傳統編程語言類似,例如 Java 的 import,C# 的 use。
但對于 Dojo 支持的 AMD 規范,則定義的是一個異步載入機制,稍微復雜。因為這個規范強調的是異步,就需要通過一個回調函數來使用模塊,這個回調函數會在依賴的模塊載入完成之后被調用,例如:
define(['dojo/date'], function(date){ var zone = date.isLeapYear(new Date());// 獲取當前是否閏年 }); |
這里的 define 函數是由 AMD 規范定義的,其第一個參數是一個數組,包含了本模塊需要依賴的模塊,當這些模塊載入完成后,會調用第二個參數:一個回調函數。并按順序將依賴的模塊作為參 數傳遞給這個回調函數,供本模塊使用。關于 define 函數的詳細用法這里不多介紹,有興趣的可以去查看相關規范文檔。我們僅需要知道它是一個異步的模塊定義和加載機制。
Dojo 中所有的模塊都是通過 define 函數定義的,很顯然,不支持 AMD 規范的 Node.js 是不認識 define 函數,不能直接執行這些模塊的。因此,Dojo 必須自己維護模塊的加載和執行,這也完全符合 Dojo 在瀏覽器端的行為,只是讀取模塊代碼的邏輯從 HTTP 請求轉換到了讀取文件,其它邏輯維持不變。在 Dojo 的初始化代碼中已經包含了對 Node.js 環境的檢測,會自動根據環境使用不同的方法載入模塊。
在 CommonJS 規范的基礎上,Node.js 和 Dojo 還都另外引入了類似的包(package)的概念。所謂一個包就是一個文件夾,在 Node.js 下可以直接 require('packageIdentifer' ),而 Dojo 則是通過 define(['packageIdentifier' ], callback) 來使用一個包。這時 Node.js 會尋找文件夾下的 index.js 或者 index.node 模塊,而 Dojo 則尋找的是 main.js 模塊。同樣,因為應用是運行于 Dojo 框架之下,包的概念以 Dojo 的實現為準。
如果把模塊的加載理解為 Java 中的 ClassLoader,那么 Dojo 就是實現了自己的 ClassLoader,來取代 Node.js 自身的行為。因此,要在 Node.js 上運行一個基于 Dojo 的應用,用的是類似下面的命令:
node <dojoroot>/dojo.js load=xxx |
這個命令告訴了 Node.js 應該執行 dojo.js 這個模塊,從而啟動了 Dojo 框架。而后面的參數 load=xxx 則是告訴 Dojo 應該執行 xxx 這個包(package)。這里的 xxx 就是您的應用程序的入口位置。因為 Dojo 已經接管了模塊的管理,所以這里運行的就是 xxx 這個包下的 main.js 模塊。
理解了這些內容,我們就可以進一步了解如何配置 Dojo,讓其在 Node.js 上運行基于 Dojo 的應用程序。
下面通過一個簡單的例子來看,如何在 Node 上運行基于 Dojo 的程序。
1. 引入并配置 Dojo
我們知道,在瀏覽器端引入 Dojo 之類的框架,首先需要在頁面中引入框架自身,通常是通過 <script> 標簽引入,在引入的同時,還可以傳遞參數,對于 Dojo 來說,可以通過兩種途徑,一種是在 <script> 標簽中加入屬性,例如:
<script type=”text/javascript” src=”dojoroot/dojo/dojo.js” djConfig="isDebug:true"></script>
這里的 djConfig 就是傳遞給 Dojo 的配置參數,設置了 isDebug 開啟狀態,從而可以輸出調試信息。另一種方式是通過定義一個名為 dojoConfig 的全局變量,在其中對 Dojo 對其配置:
<script type=”text/javascript”> Var dojoConfig = { isDebug: true }; </script> <script type=”text/javascript” src=”dojoroot/dojo/dojo.js” ></script> |
在 Node.js 中,方法是類似的,需要一個 Node.js 可運行的模塊來引入 Dojo。例如,創建一個 bootstrap.js 文件,包含如下內容:
global.dojoConfig = { isDebug: true }; require('./dojo/dojo.js'); |
Node.js 中定義全局變量的方法和瀏覽器端略有區別,它是通過為 global 增加一個屬性來定義全局變量。隨后,通過 require 函數引入 Dojo,相當于 HTML 頁面中的 <script> 標簽。因此,在本質上,這個 bootstrap.js 文件就是包含配置信息的 Dojo。直接使用下面的代碼就可以在 Node 上運行 Dojo 了:
node <bootroot>/bootstrap.js |
當然,這行命令什么也沒有干,只是運行了一下 Dojo。底下我們來看如何在 Dojo 里運行自己的 Dojo 程序。
2. 定義應用程序入口
上面提到了通過 dojoConfig 這個全局變量來配置 Dojo,除此之外,其中還可以定義自己的 package,每個 package 都可能是一個應用,或者一個類庫。所有的 package 都放在 packages 數組中:
global.dojoConfig = { isDebug: true, packages: [{ name: 'mynode' ,location: '../mynode' }] }; |
在這里我們定義了一個名為 mynode 的 package,并指定了其位置。這是一個相對于 <dojoroot>/dojo/ 的位置。這樣,要執行這個 package,只要運行下面的命令:
node <bootroot>/bootstrap.js load=mynode |
Dojo 通過 load 參數來獲得應該執行的 package,在這個 package 下的 main.js 模塊會被執行,下面將會介紹。
3. 執行應用程序
如前面所述,package 本身并沒有特別的地方,它就是一個文件夾。當僅指定一個 package,而非具體的模塊時,Dojo 就會自動執行其下的 main.js 文件。這個 main.js 模塊和普通的模塊本質上也沒有任何區別,但一般這個模塊中僅包含應用初始化的邏輯,從而能夠啟動應用的執行。
現在我們先來看一個簡單的“Hello world”的例子。我們不使用任何 Dojo 的模塊,而是只用 define 來定義一個自己的模塊,讓它打印“Hello world”。我們在 main.js 里放如下的內容:
console.log(“Hello world”); |
這樣,當執行上面提到的命令:
node <bootroot>/bootstrap.js load=mynode |
這樣,我們將會在命令行窗口看到輸出的“Hello world”。這里我們并沒有用任何的 Dojo 模塊,而是僅僅讓 Dojo 去尋找到 main.js 并執行它。那下面來看如何去依賴 Dojo 的模塊來實現自己的應用邏輯。
這個例子將使用 Dojo 對 Django 模板語言(DTL)模塊的實現來做字符串的轉換:
define(['dojox/dtl', 'dojox/dtl/Context'], function(dtl, DtlContext){ var template = new dtl.Template("Hello {{ place }}!"); var context = new DtlContext({ place: "World" }); console.log(template.render(context)); return null; }); |
可以看到,通過使用 dojox.dtl 提供的功能,就能夠在服務器端將一個基于 DTL 模板的字符串轉換成 HTML。dojox.dtl 實現了完整的 DTL 語法,結合 Node.js 的 HTTP 模塊,就很容易創建一個自己的 Web Server,這在很大程度上得益于 Dojo 的 DTL 實現。
有人曾預言 Node.js 將取代 PHP 成為最流行的服務器端語言,無論您信不信,Node.js 正在快速的演變和發展,并獲得越來越多的關注。基于 Node.js 的一些 Web Server 已經可以用于商業用途,例如 http://expressjs.com。這讓 JavaScript 這個簡潔而優雅的語言發揮散發出越來越大的魅力。而 Dojo 作為一個功能強大的 JavaScript 框架,在企業級應用中一直備受青睞,借助其對 Node.js 的完美支持,相信也能發揮出更大的作用。
文章出處:IBM developerWorks