Declaring Module Exports in Node.js and AMD
Modules are mostly the same in Node.js and AMD [1]:
A sequence of statements that assign internal values to variables and
exported values to an object. This blog post shows several patterns for
doing the latter. It also explains how ECMAScript.next handles exports.
Published at DZone with permission of Axel Rauschmayer, author and DZone MVB. (source)Node.js versus AMD
The main difference between Node.js and AMD is how the object with the exports is passed to the system: In Node.js, you assign to a variable exports that is global to the module:exports.value1 = ...;In an AMD, you return an object:
define(function () { // no imports
return {
value1: ...
};
});
Patterns for declaring exports
This section shows several patterns for declaring exports. Some of their pros and cons hinge on whether you think that having to refer differently to internal and exported values is a good thing or not. Here we assume that you will often change your mind as to what is exported and that you don’t like the added syntactic noise of referring to an exported value via an object. One can, however, also argue that it is desirable to see exported values marked everywhere. Accessing values in an object versus in local variables can make a difference in performance, but you would have to profile to be really sure. I’d expect it to rarely matter in practice. Pattern: fill object literal function funcInternal(...) { ... }
var e = {
otherFuncExported: ...,
funcExported: function (...) {
funcInternal(...);
e.otherFuncExported(...);
}
};
module.exports = e;
// AMD: return e;
Pro and cons:
- Pro: Compact syntax.
- Cons: You need to prefix “e.” to refer to exported values. Internal and exported values are separate; you cannot, e.g., put helper functions close to where they are used.
var e = exports;
// AMD: var e = {};
e.otherFuncExported = ...;
function funcInternal(...) { ... }
e.funcExported = function (...) {
funcInternal(...);
e.otherFuncExported(...);
};
// AMD: return e;
Pro and con:
- Pro: You can freely mix internal and exported values.
- Con: You need to prefix “e.” to refer to other exported values.
function otherFuncExported() { ... }
function funcInternal(...) { ... }
function funcExported(...) {
funcInternal(...);
otherFuncExported(...);
};
// AMD: return {
module.exports = {
otherFuncExported: otherFuncExported,
funcExported: funcExported
}
Pro and con:
- Pro: Can refer to exported and internal values in the same manner.
- Con: Redundant mention of exported identifiers (three times, in two different locations!).
var e = exports;
// AMD: var e = {};
var otherFuncExported = e.otherFuncExported = function () { ... };
function funcInternal(...) { ... }
var funcExported = e.funcExported = function (...) {
funcInternal(...);
otherFuncExported(...);
};
// AMD: return e
Pro and con:
- Pro: Can refer to exported and internal values in the same manner.
- Con: Some redundancy, but not as bad is in pattern #3.
ECMAScript.next
For ECMAScript.next modules, there are two ways of declaring an export. Approach 1: Prefix a declaration with “export”. export function otherFuncExported() { ... }
function funcInternal(...) { ... }
export function funcExported(...) {
funcInternal(...);
otherFuncExported(...);
};
Approach 2: Refer to exported values.
function otherFuncExported() { ... }
function funcInternal(...) { ... }
function funcExported(...) {
funcInternal(...);
otherFuncExported(...);
};
export otherFuncExported, funcExported;
It would be nice if one could emulate the latter approach in current
module systems, but there is no way to access the entries of an
environment in JavaScript. That would allow one to do the following
(using Underscore’s pick() function which clones an object, but only copies the properties whose names are mentioned):
module.exports = _.pick(environmentToObject(),
"otherFuncExported", "funcExported"
);
ECMAScript.next also has a shortcut for object literals that would be useful for current modules:
module.exports = { otherFuncExported, funcExported };
Which is syntactic sugar for:
module.exports = {
otherFuncExported: otherFuncExported,
funcExported: funcExported
};
Conclusion
I find it hard to commit to any single one of the shown patterns, as each of them has advantages and disadvantages. My current favorite is pattern #4. Do you use any of the patterns described here (which one?) or a different one? Let us know in the comments...References
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)
Tags:





