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 服务器端开发。
为什么要在 Node 上运行 Dojo
JavaScript 本身是一个设计非常精简的语言,功能相对简单,因为它的初衷并不是用来开发复杂的应用,而只是用于自动化的来操纵浏览器。面对日益复杂的前端应用,已经出现了很多类库来解决这个问题,比如 jQuery、ExtJs、Dojo、YUI。这些类库除了实现了浏览器兼容的 API,同时还有一大部分功能是纯粹的用来增强 JavaScript 的功能。而这部分功能,正是服务器端和浏览器端通用的,我相信如果在这方面提供一致性,一定是广大 Web 开发人员所喜闻乐见的,这也是我写这篇文章的目的。
这么多类库之中,Dojo 是所有当前类库中最适合运行于 Node.js 的,原因有四:
Node.js 和 Dojo 的模块加载机制
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 模块。
在 Node 上运行 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=mynodeDojo 通过 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
这个例子将使用 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,原文地址:http://www.ibm.com/developerworks/cn/web/1203_wangpei_dojonodejs/