JavaScript does not come with support for modules. This blog post examines patterns and APIs that provide such support. It is split into the following parts:
var namespace = {
func: function() { ... },
value: 123
};
Approach 2: Assigning to properties.
var namespace = {};
namespace.func = function() { ... };
namespace.value = 123;
Accessing the content in either approach:
namespace.func();
console.log(namespace.value + 44);
Assessment:
var namespace = function() {
// set up private data
var arr = []; // not visible outside
for(var i=0; i<4; i++) {
arr.push(i);
}
return {
// read-only access via getter
get values() {
return arr;
}
};
}();
console.log(namespace.values); // [0,1,2,3]
Comments:
var namespace = {};
(function(ns) {
// (set up private data here)
ns.func = function() { ... };
ns.value = 123;
}(namespace));
Variation: this as the namespace identifier (cannot accidentally be assigned to).
var namespace = {};
(function() {
// (set up private data here)
this.func = function() { ... };
this.value = 123;
}).call(namespace); // hand in implicit parameter "this"
var namespace = {
_value: 123; // private via naming convention
getValue: function() {
return this._value;
}
anObject: {
aMethod: function() {
// "this" does not point to the module here
}
}
}
Global access. Cons: makes it harder to rename the namespace, verbose for nested namespaces.
var namespace = {
_value: 123; // private via naming convention
getValue: function() {
return namespace._value;
}
}
Custom identifier: The module pattern (see above) enables one to use a custom local identifier to refer to the current module.
var ns = {
getValue: function() {
var arr = []; // not visible outside
for(var i=0; i<4; i++) {
arr.push(i);
}
return function() { // actual property value
return arr;
};
}()
};
Read on for an application of this pattern.
var ns = {
Type: function() {
var constructor = function() {
// ...
};
constructor.prototype = {
// ...
};
return constructor; // value of Type
}()
};
var namespace = namespace || {};
Nested namespaces: With multiple modules, one can avoid a proliferation of global names by creating a single global namespace and adding sub-modules to it. Further nesting is not advisable, because it adds complexity and is slower. You can use longer names if name clashes are an issue.
var topns = topns || {};
topns.module1 = {
// content
}
topns.module2 = {
// content
}
YUI2 uses the following pattern to create nested namespaces.
YAHOO.namespace("foo.bar");
YAHOO.foo.bar.doSomething = function() { ... };
CommonJS defines a module format. Unfortunately, it was defined without giving browsers equal footing to other JavaScript environments. Because of that, there are CommonJS spec proposals for Transport formats and an asynchronous require.RequireJS projects have the following file structure:RequireJS tries to keep with the spirit of CommonJS, with using string names to refer to dependencies, and to avoid modules defining global objects, but still allow coding a module format that works well natively in the browser. RequireJS implements the Asynchronous Module Definition (formerly Transport/C) proposal.
If you have modules that are in the traditional CommonJS module format, then you can easily convert them to work with RequireJS.
project-directory/
project.html
legacy.js
scripts/
main.js
require.js
helper/
util.js
project.html:
<!DOCTYPE html>
<html>
<head>
<title>My Sample Project</title>
<!-- data-main attribute tells require.js to load
scripts/main.js after require.js loads. -->
<script data-main="scripts/main" src="scripts/require.js"></script>
</head>
<body>
<h1>My Sample Project</h1>
</body>
</html>
main.js: helper/util is resolved relative to data-main. legacy.js ends with .js and is assumed to not be in module format. The consequences are that its path is resolved relative to project.html and that there isn’t a function parameter to access its (module) contents.
require(["helper/util", "legacy.js"], function(util) {
//This function is called when scripts/helper/util.js is loaded.
require.ready(function() {
//This function is called when the page is loaded
//(the DOMContentLoaded event) and when all required
//scripts are loaded.
});
});
Other features of RequireJS:
YUI().use('dd', 'anim', function(Y) {
// Y.DD is available
// Y.Anim is available
});
Steps:
YUI.add('mymodules-mod1',
function(Y) {
Y.namespace('mynamespace');
Y.mynamespace.Mod1 = function() {
// expose an API
};
},
'0.1.1' // module version
);
YUI includes a loader for retrieving modules from external files. It is configured via a parameter to the API. The following example loads two modules: The built-in YUI module dd and the external module yui_flot that is available online.
YUI({
modules: {
yui2_yde_datasource: { // not used below
fullpath: 'http://yui.yahooapis.com/datasource-min.js'
},
yui_flot: {
fullpath: 'http://bluesmoon.github.com/yui-flot/yui.flot.js'
}
}
}).use('dd', 'yui_flot', function(Y) {
// do stuff
});