定位 AMD 模块 Locating modules in AMD
在 模块标识符 一文中,我们注意到,斜杠用来分隔词条,表示模块的层次结构。不过说到底,AMD 环境必须能够定位到模块,标识符也必须以某种方式转换为 URI。URI 可能会被解析为数据库中的记录或 HTML5 localStorage 中的值,不会在大多数时候,URI 被解析为服务器上的一个文件路径或浏览器中的 URL。
默认位置和基础路径
对于非常简单的的应用程序,你可以把所有模块都放在同一个位置。这个位置称为“基础路径”。此时,模块标识符的解析仅仅是简单的字符串拼接:
模块 URL = 基础路径 + 模块标识符 + ".js"
默认情况下,大多数 AMD 加载器会把基础路径设置为当前 HTML 文档的地址。例如,如果 HTML 文档是“//know.cujojs.com/index.html”,标识符为“blog/controller”的模块应该位于“//know.cujojs.com/blog/controller.js”。当 AMD 环境解析模块 URL 时,扩展名“.js”被自动添加。
把模块和 HTML 文档放在同一位置中可能不太方便。因此,基本上所有的 AMD 环境都允许以配置的方式设置基本路径。例如,如果配置基本路径为“client/”,模块标识符“blog/controller”将被解析为“//know.cujojs.com/client/blog/controller.js”。
变量 require
除了可以注入模块外,还含有一个方法 toUrl(id)
,用于把一个模块标识符转换为一段 URL。可能你从没在应用程序的代码中使用过该方法,但对于探索标识符和 URL 之间的转换,该方法是一个很好的工具。
// module app/billing/billTo/Customer
// base url is client/
// document is //know.cujojs.com/index.html
define(function (require) {
// resolves to "//know.cujojs.com/client/app/billing/billTo/store"
var url = require.toUrl('./store');
});
标识符不等于 URL
设置基本路径,然后在基本路径对应的文件夹中放置几个模块,这些都很容易做到,但是不要被这种简单的模式迷惑了,然后想当然地认为标识符不过就是些短网址!这种简单的模式只适用于小型应用程序,无法扩展到较大些的应用程序中。较大些的应用程序需要有策略地组织层次结构。在另一篇教程中,我们讲讨论“包 package”的组织策略。
标识符有时候不就是 URL 吗?
假使模块依赖了 CDN 上的库,将会怎么样呢?
大多数 AMD 加载器允许用 URL 来代替标识符。下面的代码是完全合法的:
define(function (require) {
// attempt to get "moment" by url
var moment = require('//cdnjs.cloudflare.com/ajax/libs/moment.js/2.0.0/moment.min.js');
});
但是,这段代码有几个问题:
- 代码中的硬编码 URL 限制了可维护性。假使你要把它们更新到最新的版本,想想将会怎么样呢?
- 扩展名“.js”可能会触发 AMD 环境采用传统的方式加载文件。例如,RequireJS 就会这么做。
- 某些支持(可以感知) AMD 的库会硬编码一个标识符,这一点尤其不幸。例如,Moment.js 会在它的文件中把标识符硬编码为“moment”,即抢注了这个名字。在上面的例子中,AMD 环境期望获取一个名为“//cdnjs.cloudflare.com/ajax/libs/moment.js/2.0.0/moment.min.js”的模块,得到的却是一个名为“moment”的模块。AMD 环境可能会因为标识符不匹配而抛出一个错误。
那么,我们应该如何使用跨域的模块呢?例如 CDN 上的模块。
我们很快会给出答案!
配置 标识符-URI 映射
最终你还是不得不,告诉 AMD 环境如何把标识符映射为 URI。这个过程称为 路径映射 或 包映射,需要通过配置完成。
通过集中配置这些模块,而不是在模块中配置,可以降低维护成本,提高代码的可移植性。
在大多数 AMD 环境中,配置代码看起来像是下面这样:
var config = {
baseUrl: "client/apps",
paths: {
"blog": "blog", // this is redundant, unnecessary
"dont": "../dont"
}
};
在 curl.js 中,使用全局变量 curl
来执行配置。许多其他的 AMD 环境也采用了类似的 API:
// auto-sniff for an object literal:
curl(config);
// or, more explicitly:
curl.config(config);
选项 baseUrl
是一个 URL 路径,称为“基础路径”,AMD 环境在解析标识符时,会相对于该路径来解析。基础路径可以是绝对路径(以协议或 //
开头)、相对于 host 的路径(以 /
开头),或相对于当前页面的路径(如前面示例所示)。
选项 paths
是一个映射对象,其中包含了模块标识符到 URL 的映射。你不必为每个模块指定映射,而是可以简单地指定模块层级结构的顶层词条。在解析更深层次的模块时,会自动附加上相应的标识符层级。基于前面的配置,以模块“dont/even”为例,AMD 环境的解析过程如下:
把 baseUrl 附加到当前页面的地址,从而构建一个完整的基本路径:
"//know.cujojs.com/" + "client/" => "//know.cujojs.com/client/"
在选项 paths 中查找“dont/even”
- 因为没有找到映射关系,所以移除当前层级,转而查找“dont”。
- 发现“dont”映射到了“../dont”。
把“../dont”附加到基本路径:
"//know.cujojs.com/client/../dont/even"
选项 packages
也可以映射标识符到 URL,但是更加结构化。在 curl.js 中,该选项还提供了更高级的特性。在复杂一些的应用中,建议用 packages
代替 paths
。
避免出现多个 ../
对于 AMD 环境,基础路径指定了模块层级结构的“根”或“顶层”。试图遍历至根路径没有太大意义。假设有下面的场景:
// module blog/controller
// base url is //know.cujojs.com/client/
define(function (require) {
var dont = require("../../dont/please");
});
为了解析相对标识符“../../dont/please”,AMD 环境在查找“dont”和“please”之前,必须向让遍历两层:
- 从当前层级开始:“blog/controller”处于“blog/”层。
- 向上遍历一层:“blog/”-->“”(到达顶层路径)
- 再向上遍历一层:???(无法实现,因为已经处于最顶层了!)
任何规范都没有定义 AMD 环境该如何处理这种情况。curl.js 会假设该标识符实际上是一个 URL,并保留 ../
,这样就可以基于基础路径进行解析。言下之意是说,curl.js 会照常加载这样的模块,但不会认为“dont/please”和“../../dont/please”是同一个模块。这可能导致模块“dont/please”被加载两次,该模块的的工厂函数也将执行两次,进而引起各种各样的问题,例如,单例 singleton 不再是唯一的实例。
译注:Singleton,单例模式,确保只有一个实例,并提供一个全局访问点。
注:对于以 /
或协议(http:
、https:
等)开头的标识符,curl.js 也会把它们认为是 URL。
如果你需要用多个 ../
才能引用其他模块,请务必读一读有关包 package 的教程。包 package 可以解决所有的问题。
配置 标识符-远程URL 映射
回到 moment.js 示例,它存储在 CDN 上,并且看起来确实像一个 URL。然后,正如我们前面所提到的,moment.js 却声明了一个标识符为“moment”的模块。下面的代码演示了如何协调远程 URL:
var config = {
baseUrl: "client/apps",
paths: {
"moment": "//cdnjs.cloudflare.com/ajax/libs/moment.js/2.0.0/moment.min.js",
"blog": "blog", // this is redundant, unnecessary
"dont": "../dont"
}
};
curl.config(config);
现在,如果某个模块需要 moment.js,可以通过标识符来引用它:
define(function (require) {
// "moment" will resolve to //cdnjs.cloudflare.com/ajax/libs/moment.js/2.0.0/moment.min.js
var moment = require('moment');
});
blog comments powered by Disqus