Both AMD and CommonJS are valid module formats with different end goals.
AMD adopts a browser-first approach to development, opting for asynchronous behavior and simplified backward compatibility, but it doesnât have any concept of file I/O. It supports objects, functions, constructors, strings, JSON and many other types of modules, running natively in the browser. Itâs incredibly flexible.
CommonJS on the other hand takes a server-first approach, assuming
synchronous behavior, no global baggage, and attempts
to cater for the future (on the server). What we mean by this is that
because CommonJS supports unwrapped modules, it can feel a little more
close to the ES.next/Harmony specifications, freeing us of the define()
wrapper that AMD enforces. CommonJS
modules however only support objects as modules.
For developers wishing to create modules that can work in both browser and server-side environments, existing solutions could be considered a little lacking. To help alleviate this, James Burke, a number of other developers, and I created Universal Module Definition (UMD; https://github.com/umdjs/umd).
UMD is an experimental module format that allows the definition of modules that work in both client and server environments with all or most of the popular script-loading techniques available at the time of writing. Although the idea of (yet) another module format may be daunting, we will cover UMD briefly for the sake of thoroughness.
We originally began defining UMD by taking a look at the simplified CommonJS wrapper supported in the AMD specification. For developers wishing to write modules as if they were CommonJS modules, the following CommonJS-compatible format could be used:
define
(
function
(
require
,
exports
,
module
){
var
shuffler
=
require
(
"lib/shuffle"
);
exports
.
randomize
=
function
(
input
){
return
shuffler
.
shuffle
(
input
);
}
});
Itâs important however to note that a module is really only treated as a CommonJS module if it doesnât contain a dependency array and the definition function contains one parameter at minimum. This also wonât work correctly on some devices (e.g., the PS3). For further information about the above wrapper, see http://requirejs.org/docs/api.html#cjsmodule.
Taking this further, we wanted to provide a number of different patterns that not just worked with AMD and CommonJS, but also solved common compatibility problems developers wishing to develop such modules had with other environments.
One such variation we can see below allows us to use CommonJS, AMD, or browser globals to create a module.
Define a module commonJsStrict
, which depends on another
module called b
. The name of the
module is implied by the filename, and itâs best practice for the file
name and the exported global to have the same name.
If the module b
also uses the
same type of boilerplate in the browser, it will create a global
.b
that is used. If we donât wish
to support the browser global patch, we can remove the root
and pass this
as the first argument to the top
function.
(
function
(
root
,
factory
)
{
if
(
typeof
exports
===
'object'
)
{
// CommonJS
factory
(
exports
,
require
(
'b'
)
);
}
else
if
(
typeof
define
===
'function'
&&
define
.
amd
)
{
// AMD. Register as an anonymous module.
define
(
[
'exports'
,
'b'
],
factory
);
}
else
{
// Browser globals
factory
(
(
root
.
commonJsStrict
=
{}),
root
.
b
);
}
}(
this
,
function
(
exports
,
b
)
{
//use b in some fashion.
// attach properties to the exports object to define
// the exported module properties.
exports
.
action
=
function
()
{};
}));
The UMD repository contains variations covering modules that work optimally in the browser, those best for providing exports, those optimal for CommonJS runtimes, and even those that work best for defining jQuery plug-ins, which we will look at next.
UMD provides two patterns for working with jQuery plug-ins: one that defines plug-ins that work well with AMD and browser globals and another that can also work in CommonJS environments. jQuery is not likely to be used in most CommonJS environments, so keep this in mind, unless weâre working with an environment that does play well with it.
We will now define a plug-in composed of a core and an extension
to that core. The core plug-in is loaded into a $.core
namespace, which can then be easily
extended using plug-in extensions via the namespacing pattern.
Plug-ins loaded via script tags automatically populate a plugin
namespace under core
(i.e., $.core.plugin.methodName()
).
The pattern can be quite nice to work with, because plug-in extensions can access properties and methods defined in the base or, with a little tweaking, override default behavior so that it can be extended to do more. A loader is also not required to make any of this fully functional.
For more details of what is being done, please see the inline comments in the code samples below.
usage.html:
<
script
type
=
"text/javascript"
src
=
"jquery-1.7.2.min.js"
><
/script>
<
script
type
=
"text/javascript"
src
=
"pluginCore.js"
><
/script>
<
script
type
=
"text/javascript"
src
=
"pluginExtension.js"
><
/script>
<
script
type
=
"text/javascript"
>
$
(
function
(){
// Our plug-in "core" is exposed under a core namespace in
// this example, which we first cache
var
core
=
$
.
core
;
// Then use use some of the built-in core functionality to
// highlight all divs in the page yellow
core
.
highlightAll
();
// Access the plug-ins (extensions) loaded into the "plugin"
// namespace of our core module:
// Set the first div in the page to have a green background.
core
.
plugin
.
setGreen
(
"div:first"
);
// Here we're making use of the core's "highlight" method
// under the hood from a plug-in loaded in after it
// Set the last div to the "errorColor" property defined in
// our core module/plug-in. If we review the code further down,
// we can see how easy it is to consume properties and methods
// between the core and other plug-ins
core
.
plugin
.
setRed
(
"div:last"
);
});
<
/script>
pluginCore.js:
// Module/plug-in core
// Note: the wrapper code we see around the module is what enables
// us to support multiple module formats and specifications by
// mapping the arguments defined to what a specific format expects
// to be present. Our actual module functionality is defined lower
// down, where a named module and exports are demonstrated.
//
// Note that dependencies can just as easily be declared if required
// and should work as demonstrated earlier with the AMD module examples.
(
function
(
name
,
definition
){
var
theModule
=
definition
(),
// this is considered "safe":
hasDefine
=
typeof
define
===
"function"
&&
define
.
amd
,
// hasDefine = typeof define === "function",
hasExports
=
typeof
module
!==
"undefined"
&&
module
.
exports
;
if
(
hasDefine
){
// AMD Module
define
(
theModule
);
}
else
if
(
hasExports
)
{
// Node.js Module
module
.
exports
=
theModule
;
}
else
{
// Assign to common namespaces or simply the global object (window)
(
this
.
jQuery
||
this
.
ender
||
this
.
$
||
this
)[
name
]
=
theModule
;
}
})(
"core"
,
function
()
{
var
module
=
this
;
module
.
plugins
=
[];
module
.
highlightColor
=
"yellow"
;
module
.
errorColor
=
"red"
;
// define the core module here and return the public API
// This is the highlight method used by the core highlightAll()
// method and all of the plug-ins highlighting elements different
// colors
module
.
highlight
=
function
(
el
,
strColor
){
if
(
this
.
jQuery
){
jQuery
(
el
).
css
(
"background"
,
strColor
);
}
}
return
{
highlightAll
:
function
(){
module
.
highlight
(
"div"
,
module
.
highlightColor
);
}
};
});
pluginExtension.js:
// Extension to module core
(
function
(
name
,
definition
)
{
var
theModule
=
definition
(),
hasDefine
=
typeof
define
===
"function"
,
hasExports
=
typeof
module
!==
"undefined"
&&
module
.
exports
;
if
(
hasDefine
)
{
// AMD Module
define
(
theModule
);
}
else
if
(
hasExports
)
{
// Node.js Module
module
.
exports
=
theModule
;
}
else
{
// Assign to common namespaces or simply the global object (window)
// account for for flat-file/global module extensions
var
obj
=
null
,
namespaces
,
scope
;
obj
=
null
;
namespaces
=
name
.
split
(
"."
);
scope
=
(
this
.
jQuery
||
this
.
ender
||
this
.
$
||
this
);
for
(
var
i
=
0
;
i
<
namespaces
.
length
;
i
++
)
{
var
packageName
=
namespaces
[
i
];
if
(
obj
&&
i
==
namespaces
.
length
-
1
)
{
obj
[
packageName
]
=
theModule
;
}
else
if
(
typeof
scope
[
packageName
]
===
"undefined"
)
{
scope
[
packageName
]
=
{};
}
obj
=
scope
[
packageName
];
}
}
})(
"core.plugin"
,
function
()
{
// Define our module here and return the public API.
// This code could be easily adapted with the core to
// allow for methods that overwrite and extend core functionality
// in order to expand the highlight method to do more if we wish.
return
{
setGreen
:
function
(
el
)
{
highlight
(
el
,
"green"
);
},
setRed
:
function
(
el
)
{
highlight
(
el
,
errorColor
);
}
};
});
UMD doesnât aim to replace AMD nor CommonJS but merely offers some supplemental assistance for developers wishing to get their code working in more environments today. For further information or to contribute suggestions toward this experimental format, see https://github.com/umdjs/umd.
Using AMD Loaders to Write and Manage Modular JavaScript, John Hann
Demystifying CommonJS Modules, Alex Young
AMD Module Patterns: Singleton, John Hann
Standards And Proposals for JavaScript Modules And jQuery, James Burke
Get Learning JavaScript Design Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.