编写 UMD 模块 Authoring UMD modules
如果是在浏览器中运行代码,那么 AMD 模块 是个非常好的选择。如果运行在服务端环境,例如 RingoJS 或 node.js,那么 CommonJS 模块 是最简单的选择。
怎么才能写出可以同时运行在浏览器和服务端的代码呢?
也许你从没打算让代码运行在服务端环境中,但是在 node.js 中执行测试却是非常方便的。所以,为什么不同时支持两种环境呢?
问:怎么编写 JavaScript 模块,才能让它在多个环境中执行?
答:Universal Module Definition,UMD!
UMD 模式可以兼容多种环境。为了实现这一点,通常,但不总是,UMD 模式会把模块代码包裹进一个 立即调用的函数表达式(Immediately Invoked Function Expression,IIFE) 中。通过模拟或注入变量,IIFE 内部的环境最终被规格化为模块代码所期望的特定环境。IIFE 之外的代码则桥接了 IIFE 的内部环境和外部环境。规格化后的 IIFE 内部环境非常类似于 AMD 或 CommonJS,具体类似于哪种则取决于所采用的 UMD 风格。
UMD 有很多“野生”的变种。你可以在 UMD 仓库 以及 强大的 UMD 模式 找到一些示例。
许多模式还提供了一种方式使模块暴漏为全局变量(或全局变量上的属性)。不要这么干!应用代码不应该声明任何全局变量,因为全局变量会导致代码难以重用和难以测试。此外,模块化是 JavaScript 的趋势,为什么要和趋势对着干呢?
我们已经挑选了一些最喜欢的 UMD 模式,以帮助简化你的选择。下面章节中的通用模式可以运行在几乎所有的环境中:Netscape 7.2、IE6、Firefox 和 Chromium,以及服务端环境,例如 RingoJS 和 node.js。
如果你不知道我在说些什么,说明你尚未准备好,请先温习下教程 编写 AMD 模块 和 编写 CommonJS 模块,然后再继续这篇教程。
1. 重构为 AMD 经典格式 ⬆
define(dependencyIds, factoryFunction(dependencyModule1, dependencyModule2 ...))
如果你正在换转带有依赖列表的 AMD 模块,那么只需要做很少量的重构。
// app/CachingStore (function (define) { // dependencies are listed in the dependency array define(['./store', 'meld'], function (store, meld) { "use strict"; var cache = {}; // create the module meld.around(store, 'get', function (jp) { var key = jp.args.join('|'); return key in cache ? cache[key] : cache[key] = jp.proceed(); }; // return your module's exports return store; }); }( typeof define == 'function' && define.amd ? define : function (ids, factory) { // note: the lambda function cannot be removed in some CJS environments var deps = ids.map(function (id) { return require(id); }); module.exports = factory.apply(null, deps); } ));
整个模块被包裹在一个 IIFE 中,并且函数 define
被作为一个参数传入。文件最后的代码片段 typeof define == 'function' && define.amd
是嗅探 AMD 环境的标准方式。如果检测结果为 true
,则说明当前环境是 AMD,可以把全局函数 define
传入 IIFE。通过由工厂函数返回一个值,模块以正常的 AMD 方式输出。
如果 AMD 环境嗅探的结果为 false
,代码则模拟一个类似 node.js 的 CommonJS 环境。为了使 AMD 代码能够运行,IIFE 注入了一个行为类似于 AMD define
的函数:把所有的 ids
加载为模块,并把它们作为参数注入工厂函数。然后,函数 define
获取到工厂函数的返回值,并以经典的 node.js 方式赋值给 module.exports
。
2. 为 AMD 工厂函数注入 require() ⬆
define(factoryFunction(require))
如果已经通过 AMD 的“require”指定了依赖关系,那么重构过程也不需要太大的变化。
// app/CachingStore (function (define) { // using the define signature that triggers AMD-wrapped CommonJS define(function (require) { "use strict"; var store, meld, cache = {}; // use the injected require() to specify dependencies store = Object.create(require('./store')); meld = require('meld'); // create the module meld.around(store, 'get', function (jp) { var key = jp.args.join('|'); return key in cache ? cache[key] : cache[key] = jp.proceed(); }; // return your module's exports return store; }); }( typeof define == 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } ));
整个组件再次被包裹在一个 IIFE 中,并且函数 define
作为参数被注入。这次,IIFE 底部的代码稍微简单一些,因为在这种情况下,AMD 的参数签名原本就与 CommonJS 很相似。事实上,它只是注入了 CommonJS 的 require
,来替换掉 AMD 的 require
。最后,module.exports
被赋予了工厂函数的返回值。
对于这种模式,cujo.js 使用的很频繁。
3. 把 CommonJS 包裹为 AMD ⬆
define(factoryFunction(require, exports, module))
如果模块代码已经是 node.js 或 CommonJS 格式,可以用下面的包裹方式来格式化。
// app/CachingStore (function (define) { // note: we're injecting all three CommonJS scoped variables define(function (require, exports, module) { "use strict"; var store, meld, cache = {}; // use CommonJS patterns to require() dependencies store = Object.create(require('./store')); meld = require('meld'); // create the module meld.around(store, 'get', function (jp) { var key = jp.args.join('|'); return key in cache ? cache[key] : cache[key] = jp.proceed(); }; // you can use node.js and/or pure CommonJS export patterns! exports.store = store; }); }( typeof define == 'function' && define.amd ? define : function (factory) { factory(require, exports, module); } ));
这一次,三个模块作用域变量(require
、exports
和 module
)都被注入。IIFE 中的环境模仿了 CommonJS 环境,适用于所有不访问特定环境变量(例如 node.js 的 __dirname
)的模块。
需要注意的是,在这个变种中,工厂函数没有返回 exports。它期望你动态的将功能附加到对象 exports
到上,或者赋值给 module.exports
。
blog comments powered by Disqus