实战Dojo与RequireJS集成 之二

Dojo 和requireJS 集成之二

作者: feijia ([email protected])

在成功了配置了dojo和requireJS之后,我们要仔细分析一下这个模板项目的源代码, 看看Ben提供的程序框架究竟是怎么实现的。

首先我们会注意到 index.html

<!DOCTYPE html> <html lang="en-us"> <head> <meta charset="utf-8" /> <title>dojo with requirejs test page</title> <link rel="stylesheet" href="dojo/resources/dojo.css" mce_href="dojo/resources/dojo.css" type="text/css"> <mce:script type="text/javascript"><!-- // configure require.js require = { // point to the dojo and dijit packages packages: [ { name: 'dojo', location: 'dojo/dojo', main:'lib/main-browser', lib: '.' }, { name: 'dijit', location: 'dojo/dijit', main:'lib/main', lib: '.' } ], // set the path for the require plugins paths: { require: 'requirejs/require' }, ready: function () { require(['app/App', 'app/config'], function (App, config) { var app = new App(config, 'app'); app.startup(); }); } }; // --></mce:script> <mce:script type="text/javascript" src="requirejs/require.js" mce_src="requirejs/require.js"></mce:script> </head> <body> <h1>dojo with requirejs test page</h1> <div id="app"><p>if you're seeing this - it didn't work</p></div> </body> </html>

这是整个应用的入口。 在这里首先会看到一段JavaScript

// configure require.js require = { // 设置dojo和dijit两个模块的相对路径信息,以及每个模块中的主入口 packages: [ { name: 'dojo', location: 'dojo/dojo', main:'lib/main-browser', lib: '.' }, { name: 'dijit', location: 'dojo/dijit', main:'lib/main', lib: '.' } ], // 设置依赖路径上的名字的映射 paths: { require: 'requirejs/require' }, //当DOM装载完成后执行的回调函数。 在这个回调函数中,我们会调用require 来装入并执行我们的app ready: function () { require(['app/App', 'app/config'], function (App, config) { var app = new App(config, 'app'); app.startup(); }); } };


这段脚本主要做了两个动作:

1. 配置reuqireJS 所要加载的包的名称路径 (有点类似于配置Java的classpath,告诉加载器 模块的位置)

2. 核心逻辑是在ready:function() 它指定了requireJS在整个页面DOM装载完成后要执行的逻辑。 这里就是装入app的依赖模块('app/App'和‘app/config' 。 ),并启动app.startup。

在requireJS 被加载后,它会解析我们这里定义的require对象。并在页面DOM装入完毕后执行ready属性所指定的回调函数。


除了这段脚本, index.html 中还有一段HTML定义了一个名为"app" 的div节点。这个节点就是将由App所创建的Widget所用的节点。 如果App成功运行,则这个Div中的html代码将会替换成由Widget定义的内容。如果App装入失败,用户就会看到写在index.html中的这段静态内容。" if you're seeing this - it didn't work"

下面我们就来看一下app/App是什么内容。 从路径上可以判断,这个模块指向我们项目中的app/App.js
===App.js
define([ 'dojo', 'dijit/_Widget', 'dijit/_Templated', 'i18n!./nls/App', 'text!./templates/App.html', 'dojo/date/locale', 'i18n!dojo/cldr/nls/fr-ch/gregorian' ], function (d, Widget, Templated, i18n, template, locale) { return d.declare([ Widget, Templated ], { templateString: template, i18n: i18n, buildRendering: function () { // this is just to test some locale specific stuff with dojo this.defaultMonths = locale.getNames('months', 'wide', 'standAlone', d.locale).join(', '); this.frchMonths = locale.getNames('months', 'wide', 'standAlone', 'fr-ch').join(', '); this.inherited(arguments); }, startup: function () { this.inherited(arguments); if (this.alertWhenStarted) { alert('App is started!'); } } }); });

App.js 中,它先声明了自己的依赖模块,包括了dojo, dijit/_widget dojo/date/locale 等。 要特别注意的是,它还有一些特别的依赖 例如 'i18n!.nls/App' 这是requireJS的插件定义的格式。 表示这个依赖需要由一个名为i18n的requireJS插件去加载,而所加载的资源的路径为./nsl/App.js

requireJS 的plugin其实本身也是一个标准AMD模块。 ! 之前的部分就是这个模块的名字。 因为这里我们使用的是i18n,因此requireJS会试图先去加载一个名为i18n的模块。 默认的,它会在项目根目录去寻找名为i18n.js的文件加载。 如果我们希望把这个插件的位置移动一下到requirejs目录中,可以这样写

requirejs/i18n!.nls/App

类似的,我们看到另一个'text!./templates/App.html'依赖, text也是一个requireJS的标准插件, 这个插件的作用很简单就是把 一个文件作为字符串加载进来。 这里就是用来加载了一个我们接下来会用到的widget 模板:./templates/App.html
使用requireJS text插件的示例:
require(["some/module", "text!some/module.html", "text!some/module.css"], function(module, html, css) { //参数HTML中将会包含some/module.html的文本 //of the some/module.html file //参数css中将会包含some/module.css的文本 //of the som/module.css file. } );

分析完了该程序的依赖,我们再来看App的逻辑是什么。 它只有一个return 语句, 内容是一个d.declare调用. 参数d就是dojo。我们知道
dojo.declare 是用来创建类的。 这里我们创建了一个基于widget和Templated的子类. 而参数Widget 对应 dijit/_Widget, Templated 对应 dijit/_Templated , 因此该类继承自dijit/_Widget 和dijit/_Templated。 该类还自

定义了一个模板字符串,和一个i18n 包。 buildRendering 是复写dojo._Widget基类的方法。主要就是获取了2个字符串。一个是用当前默认locale 的月份字符串,另一个是用法语的。 然后就执行父类的方法 this.inherited(arguments)

startup也是dijit._Widget 的基类方法,在一个Widget启动时调用。这里也直接调用了父类的方法,并弹出对话框(App is Started)

所以App实际就是声明了一个新的Widget类并指定了Widget的模板。 那么这个模板是怎么样的?

<div>
<h1>${i18n.heading}</h1>
<p>${i18n.welcome}</p>
<ul>
<li>default locale: ${defaultMonths}</li>
<li>locale 'fr-ch': ${frchMonths}</li>
</ul>
</div>



稍微了解一下dijit的模板机制就可以看懂这个非常简单的Widget模板了。 所有${xxx}的内容都会被相应的变量值替换。 我们在前面dojo.declare 中看到, ${defaultMonths} 和 ${frchMonths} 是自定义的两个字符串. 而i18n.heading 和 i18n.welcome 是从 'i18n!./nls/App 得来的值。



到这里我们已经看完了所有的代码。 下面是该程序的架构示意图:

实战Dojo与RequireJS集成 之二

这个例子看起来有些复杂,其实如果你只是想用requireJS 和dojo 可以非常简单。 几行代码就可以了,但是我们要反问自己AMD的优势究竟是什么?那就是更清晰的模块化代码结构和解藕。 因此这个应用程序模板提供了一个很好的例子,你可以在它的基础上进一步去搭建自己的前端应用。

在架构图中你可以看到它清晰的把加载器,程序入口和配置,程序的主要业务逻辑,以及应用所依赖的类库和资源都做了清晰的隔离. 而所有这些模块和资源全部都是使用requireJS来统一管理和加载的。当应用程序的逻辑逐渐变得复杂,使用各种的本地化资源和第三方类库时,这样模块化的优势就会体现出来.