在 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