ES5 Module Systems (was: Alternative proposal to privateName.public)

# Mark S. Miller (13 years ago)

[Note change of Subject:]

On Mon, Dec 26, 2011 at 12:15 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

For another the modules infrastructure that you have on

node is (for good reasons, I suspect) completely different to the infrastructure available on the client, which makes a difference to the way you structure your program.

I’ve heard that claim many times, but I have yet to see compelling evidence [1]. I find the competing module standards Common JS Modules and Asynchronous Module Definitions (AMDs) to be very similar. Some argue that AMDs are too complicated, but I don’t think they could be any simpler (in their basic version).

Hi Axel, a nice demonstration of the simplicity of the core of AMD is the simplicity with which it can be implemented using a promise library. From < strawman:concurrency#amd_loader_lite

:

function makeSimpleAMDLoader(fetch, moduleMap = Map()) { var loader;

 function rawLoad(id) {
   return Q(fetch(id)).when(function(src) {
     var result = Q.reject(new Error('"define" not called by: ' + id));
     function define(deps, factory) {
       result = Q.all(...deps.map(loader)).when(function(imports) {
         return factory(...imports);
       });
     }
     define.amd = {lite: true};

     Function('define', src)(define);
     return result;
   });
 }
 return loader = Q.memoize(rawLoad, moduleMap);

}

Having two competing module standards hurts the JavaScript ecosystem, but with ECMAScript.next modules that problem will hopefully soon be gone.

AMD (Asynchronous Module Definition) as supported on the client by require.js and curl.js is catching on for precisely that reason. There is no shortage of adapters between AMD and CommonJS modules, so AMD modules are also quite usable on the server.

The adapters are all a bit cumbersome to use. IMHO, the best option is still boilerplate (to conditionally turn an AMD into a Node.js module):

({ define: typeof define === "function"
    ? define
    : function(A,F) { module.exports = F.apply(null, A.map(require)) } }).
define([ "./module1", "./module2" ],
    function (module1, module2) {
        return ...
    }
);

[1] www.2ality.com/2011/11/module-gap.html

I like this adapter, and have just changed < code.google.com/p/es-lab/source/browse/trunk/src/ses/amdTest3.js> to

test that it works with our simple AMD Loader in translation-free SES-on-ES5 (similar to the above, at < code.google.com/p/es-lab/source/browse/trunk/src/ses/makeSimpleAMDLoader.js>).

You can run this test by visiting < es-lab.googlecode.com/svn/trunk/src/ses/explicit.html> in a modern

browser. If you see the line "AMD loader test...succeeded", then it did.

However, I am confused by the "module.exports = ..." part of your boilerplate. The main CommonJS wiki seems down at the moment, but looking at wiki.commonjs.org.mirrors.page.ca/articles/m/o/d/Modules.html

on the mirror site, I could not find any support for this idiom. The closest I could find was < wiki.commonjs.org.mirrors.page.ca/articles/m/o/d/Modules_SetExports_9215.html>,

which suggests it should read "module.setExports(...);" instead. Where does "module.exports = ..." come from?

# Jake Verbaten (13 years ago)

However, I am confused by the "module.exports = ..." part of your boilerplate. The main CommonJS wiki seems down at the moment, but looking at wiki.commonjs.org.mirrors.page.ca/articles/m/o/d/Modules.html on the mirror site, I could not find any support for this idiom. The closest I could find was < wiki.commonjs.org.mirrors.page.ca/articles/m/o/d/Modules_SetExports_9215.html>, which suggests it should read "module.setExports(...);" instead. Where does "module.exports = ..." come from?

module.exports = ... is a mechanism in node.js to overwrite the exports object entirely. (useful when you want to export, say a function rather then an object)

It seems to be similar to module.setExports and is there purely to support the implementation of the module system in node.js

# Axel Rauschmayer (13 years ago)

Hi Axel, a nice demonstration of the simplicity of the core of AMD is the simplicity with which it can be implemented using a promise library.

Exactly; nice demo. Doing something comparably asynchronous with Node.js modules (without a compilation step) is much more hacky: www.2ality.com/2011/11/lobrow.html

({ define: typeof define === "function"
    ? define
    : function(A,F) { module.exports = F.apply(null, A.map(require)) } }).
define([ "./module1", "./module2" ],
    function (module1, module2) {
        return ...
    }
);

[1] www.2ality.com/2011/11/module-gap.html

I like this adapter, and have just changed code.google.com/p/es-lab/source/browse/trunk/src/ses/amdTest3.js to test that it works with our simple AMD Loader in translation-free SES-on-ES5 (similar to the above, at code.google.com/p/es-lab/source/browse/trunk/src/ses/makeSimpleAMDLoader.js). You can run this test by visiting es-lab.googlecode.com/svn/trunk/src/ses/explicit.html in a modern browser. If you see the line "AMD loader test...succeeded", then it did.

However, I am confused by the "module.exports = ..." part of your boilerplate. The main CommonJS wiki seems down at the moment, but looking at wiki.commonjs.org.mirrors.page.ca/articles/m/o/d/Modules.html on the mirror site, I could not find any support for this idiom. The closest I could find was wiki.commonjs.org.mirrors.page.ca/articles/m/o/d/Modules_SetExports_9215.html, which suggests it should read "module.setExports(...);" instead. Where does "module.exports = ..." come from?

It’s necessary for Node.js.

Node.js:

var module1 = require("./module1");
module1.foo();
var module2 = require("./module2");
export.bar = function() {
    module2.baz();
}

AMD:

define([ "./module1", "./module2" ],
    function (module1, module2) {
        module1.foo();
        return { // export
            bar: function () {
                module2.baz();
            }
        };
    }
);

Hence, unless you add to exports, Node.js won’t see your module’s contributions.

IIRC, Node.js deviates from CJS in that module.exports is an alias for exports. Without that alias, the boilerplate would be more complicated (you’d have to copy the properties of the object produced by F() over to exports). But I am focusing on Node.js, at the moment, so I can afford to rely on this functionality.

# Axel Rauschmayer (13 years ago)

IMHO, the best option is still boilerplate (to conditionally turn an AMD into a Node.js module): ({ define: typeof define === "function" ? define : function(A,F) { module.exports = F.apply(null, A.map(require)) } }). define([ "./module1", "./module2" ], function (module1, module2) { return ... } );

[1] www.2ality.com/2011/11/module-gap.html

I like this adapter, and have just changed code.google.com/p/es-lab/source/browse/trunk/src/ses/amdTest3.js to test that it works with our simple AMD Loader in translation-free SES-on-ES5 (similar to the above, at code.google.com/p/es-lab/source/browse/trunk/src/ses/makeSimpleAMDLoader.js). You can run this test by visiting es-lab.googlecode.com/svn/trunk/src/ses/explicit.html in a modern browser. If you see the line "AMD loader test...succeeded", then it did.

As an aside, one benefit of the above boilerplate is that it is just a prefix (easier to copy and paste than solutions that are wrapped around an AMD).

# James Burke (13 years ago)

On Mon, Dec 26, 2011 at 1:46 PM, Mark S. Miller <erights at google.com> wrote:

On Mon, Dec 26, 2011 at 12:15 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

The adapters are all a bit cumbersome to use. IMHO, the best option is still boilerplate (to conditionally turn an AMD into a Node.js module):

({ define: typeof define === "function"
    ? define
    : function(A,F) { module.exports = F.apply(null, A.map(require)) }

}). define([ "./module1", "./module2" ], function (module1, module2) { return ... } );

This type of boilerplate may not be processed properly by AMD optimizers that insert module IDs into the define call when multiple modules are combined together. While the above may work with the requirejs optimizer, it is not guaranteed to work in the future. I still prefer the define() call is a non-property function call and not a call to an object method, to avoid possible conflicts with a module export value (uglifyjs has one such conflict). A bit more background in this message:

groups.google.com/group/nodejs-dev/msg/aaaf40dfeca04314

In that message I also mention encouraging the use of the "amdefine" adapter module in Node. Use of this module will help test drive a define() implementation that could be integrated into Node later, and by having Node packages use the module, it would give Node committers a way to scan the package.json info to find out if define() use is used enough to warrant consideration in their core.

"amdefine" also supports a callback-style require (with callback fired on nextTick()) and most of the loader plugin API, so it gives an idea how big a complete define() implementation in Node might be:

jrburke/amdefine

But if inline boilerplate is desired instead of a dependency on an npm-installed module, there are a set of options in this project:

umdjs/umd

which include boilerplates that also work in a "just use globals with ordered HTML script elements" browser setup. I personally prefer this one if AMD+Node support is desired:

umdjs/umd/blob/master/returnExports.js

I also encourage any ES.next module system to allow opt-in to an ES.next module system using a similar type of boilerplate. However, I am concerned that the declarative nature and new syntax needed for ES.next modules would make this difficult to do. I have given feedback offline to Dave Herman about it and was not going to bring it up more publicly until later, but given the talk of boilerplate, seems appropriate to mention it here.

James

# Axel Rauschmayer (13 years ago)

This type of boilerplate may not be processed properly by AMD optimizers that insert module IDs into the define call when multiple modules are combined together. While the above may work with the requirejs optimizer, it is not guaranteed to work in the future. I still prefer the define() call is a non-property function call and not a call to an object method, to avoid possible conflicts with a module export value (uglifyjs has one such conflict).

I’d expect to remove my boilerplate either via an extra build step or via generic boilerplate support in the RequireJS optimizer (“remove all lines where '//amd-boilerplate' appears”).

I’ve chosen my format after considering two alternatives (which both had disadvantages).

Alternative 1: if (typeof define !== 'function') { var define = (require('amdefine'))(module); } define(...);

The above code relies on a quirk of of var. If you wrap it in an IIFE then it will stop working (because the inner var define will always shadow an outer value).

Alternative 2: (function (define) { define(...); }(typeof define === 'function' ? define : require('amdefine'))(module));

Here the boilerplate appears both before and after the AMD code. That’s more intrusive and harder to copy/paste.

In that message I also mention encouraging the use of the "amdefine" adapter module in Node. Use of this module will help test drive a define() implementation that could be integrated into Node later, and by having Node packages use the module, it would give Node committers a way to scan the package.json info to find out if define() use is used enough to warrant consideration in their core.

I’m still examining all available, but eventually a campaign might be in order.