RequireJS 历史
I我在 Dojo 加载器方面做了大量工作。以前的普通 dojo 加载器使用的是同步 XMLHttpRequest(XHR)调用。然而,由于同源策略限制,XHR 加载器无法从其他域名加载 Dojo 模块。因此我创建了 xdomain 加载器,它需要通过构建步骤注入函数包装器,这与 RequireJS 所用的方法类似但更复杂,特别是因为 i18n 资源包加载和 dojo.requireIf 的行为。由于更加复杂的 i18n 和 requireIf 需求,以及世界上已经存在大量 dojo 模块,我觉得 Dojo 社区不会愿意手动编写带有函数包装器的模块。
然而,同步 XHR 加载器还存在其他问题,比如使调试变得更加困难。2009 年,David Mark 建议 Dojo 使用 document.write() 在页面加载前加载模块以解决这个问题,但这意味着依赖项直到当前模块执行后才会开始加载。如果模块在其定义中引用了某个依赖项,这可能会导致错误。因此需要函数包装器。Dojo 社区似乎更愿意考虑使用函数包装器,尤其是我们在讨论可以打破一些 API 的 Dojo 2.0 版本时更是如此。我在 dojo-contributors 邮件列表上详细阐述了关于 RequireJS(当时称为 RunJS)的一些细节,Mike Wilson 最初则推动了一个更加通用的加载器,该加载器不仅可以加载普通文件,还能支持不同的上下文。
YUI 3 的 use() 函数也与 require 非常相似,并且 use() 的 API(而非代码)也影响了 RequireJS 的结构。我相信 RequireJS 更加通用,因为 YUI 的模块使用标签命名,这些标签并不直接对应文件路径。我也喜欢将依赖模块显式地作为参数传入函数定义的方式,因为这使得 JSLint 这类代码检查工具更有效。
我最初希望找一个能够兼容 CommonJS 模块的解决方案,但那些模块的设计似乎是基于假设使用同步模块加载器,这在服务器端 JavaScript 环境下是可行的。不过我的主要关注点还是浏览器环境下的良好运行,这意味着我们需要使用函数包装器来配合 script 标签。使用同步 XHR 对于新开发者或希望跨浏览器轻松调试的人来说并不是友好选择,而且它的加载速度也可能比普通的 script 标签慢。某些环境如 Adobe AIR 不允许使用 eval(),而且大多数开发者都被告知应该避免使用它。
我最初将 RequireJS 的雏形命名为 RunJS。当我尝试使其与 CommonJS 模块更一致以便实现更多代码重用时,我提出了CommonJS Transport/C 提案。这种传输格式允许将传统的 CommonJS 模块映射为更适合浏览器使用的格式。随后我将 RunJS 的代码转换为 RequireJS,以匹配 Transport/C 提案中的 API。
在实现传输格式的过程中,我意识到 CommonJS 模块允许命令式的 require() 使用方式,在 Web 上这种方式显得很不自然。许多情况下在网页环境中无法正常工作,更好的解决方案是对这些情况采用基于回调的 require 方式。然而,CommonJS 列表中的一些参与者希望即使在基于回调的 require 中也保留命令式的使用方式,这使得该 API 变得比应有程度更冗长。
Transport/C 提案进行了一些调整,Kris Zyp 找到了在该格式中让匿名模块工作的办法。此时 Kris 认为它可以用作模块 API 提案而不只是一个传输格式,他在 CommonJS 维基上提出了一个异步模块定义(AMD)API 提案。在对该 API 的讨论过程中,Tom Robinson 提议使用 Function.prototype.toString() 来扫描工厂函数中的依赖项,尽管 Tom 本人总体上并不喜欢 AMD API。toString() 扫描方法被作为简化版的 CommonJS 包装包含进了 AMD API,因为不是所有 JS 环境都支持可用的 toString() 值。
CommonJS 列表中有一些参与者认为 AMD API 提案与原始的 CommonJS 模块目标不符,因为它没有保留 CommonJS 模块中完整的命令式 require() 风格。他们还认为该提案突然提出并被实施和推广,不够恰当,虽然从我的角度看它是标记为提案的,同时还有其他人也在讨论他们对其他 CommonJS 提案的实现。
由于沟通上的严重分歧,使得继续在 CommonJS 列表中讨论 AMD 变得困难。但是我们这群网页开发者中仍有足够多的人看到了其价值,并且围绕加载器插件和回传 require 的 API 工作仍需完成,于是成立了 amd-implement 邮件列表和 amdjs Github 小组以继续相关讨论。
通过 amd-implement 邮件列表,回传 require 和加载器插件 API 得到了进一步明确并形成了单元测试套件。更多的 AMD 实现也陆续出现。Dojo 已经开始了向 AMD 使用的代码转换过程,其他诸如 MooTools 和 EmbedJS 也开始采用它。在 jQuery 社区中,希望拥有模块化 JS 加载能力的开发者逐渐接受了 AMD 加载器。
AMD 现在拥有一个健康的生态系统。我继续通过在 RequireJS 中提供一个稳定的实现,并确保其与网络环境良好适配,来推动 AMD 向前发展。