Dojo 和requireJS 集成之二



在成功了配置了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 得来的值。


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



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


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