Skip to content

Commit

Permalink
AMD compatibility for node, with docs and tests
Browse files Browse the repository at this point in the history
Closes nodejs#1173
Closes nodejs#1170
  • Loading branch information
isaacs committed Jun 13, 2011
1 parent 2eb1274 commit 41c143c
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 4 deletions.
32 changes: 32 additions & 0 deletions doc/api/modules.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,38 @@ Because `module` provides a `filename` property (normally equivalent to
`__filename`), the entry point of the current application can be obtained
by checking `require.main.filename`.

## AMD Compatibility

Node's modules have access to a function named `define`, which may be
used to specify the module's return value. This is not necessary in node
programs, but is present in the node API in order to provide
compatibility with module loaders that use the Asynchronous Module
Definition pattern.

The example module above could be structured like so:

define(function (require, exports, module) {
var PI = Math.PI;

exports.area = function (r) {
return PI * r * r;
};

exports.circumference = function (r) {
return 2 * PI * r;
};
});

* Only the last argument to `define()` matters. Other module loaders
sometimes use a `define(id, [deps], cb)` pattern, but since this is
not relevant in node programs, the other arguments are ignored.
* If the `define` callback returns a value other than `undefined`, then
that value is assigned to `module.exports`.
* **Important**: Despite being called "AMD", the node module loader **is
in fact synchronous**, and using `define()` does not change this fact.
Node executes the callback immediately, so please plan your programs
accordingly.

## Addenda: Package Manager Tips

The semantics of Node's `require()` function were designed to be general
Expand Down
39 changes: 38 additions & 1 deletion lib/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ Module.prototype._compile = function(content, filename) {
require.cache = Module._cache;

var dirname = path.dirname(filename);
var define = makeDefine(require, self);

if (Module._contextLoad) {
if (self.id !== '.') {
Expand All @@ -397,6 +398,7 @@ Module.prototype._compile = function(content, filename) {
sandbox.module = self;
sandbox.global = sandbox;
sandbox.root = root;
sandbox.define = define;

return runInNewContext(content, sandbox, filename, true);
}
Expand All @@ -408,6 +410,7 @@ Module.prototype._compile = function(content, filename) {
global.__filename = filename;
global.__dirname = dirname;
global.module = self;
global.define = define;

return runInThisContext(content, filename, true);
}
Expand All @@ -419,10 +422,44 @@ Module.prototype._compile = function(content, filename) {
if (filename === process.argv[1] && global.v8debug) {
global.v8debug.Debug.setBreakPoint(compiledWrapper, 0, 0);
}
var args = [self.exports, require, self, filename, dirname];

var args = [self.exports, require, self, filename, dirname, define];
return compiledWrapper.apply(self.exports, args);
};

// AMD compatibility
function makeDefine(require, module) {
var called = false;
function define() {
if (called) {
throw new Error("define() may only be called once.");
}
called = true;

// only care about the last argument
var cb = arguments[ arguments.length - 1 ];

// set exports immediately:
// define({ foo: "bar" })
if (typeof cb !== 'function') {
module.exports = cb;
return;
}

var ret = cb(require, module.exports, module);

if (ret !== undefined) {
// set exports with return statement
// define(function () { return { foo: "bar" } })
module.exports = ret;
}
}

return define;
}



// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
var content = NativeModule.require('fs').readFileSync(filename, 'utf8');
Expand Down
4 changes: 2 additions & 2 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1287,15 +1287,15 @@ void DisplayExceptionLine (TryCatch &try_catch) {
//
// When reporting errors on the first line of a script, this wrapper
// function is leaked to the user. This HACK is to remove it. The length
// of the wrapper is 62. That wrapper is defined in src/node.js
// of the wrapper is 70. That wrapper is defined in src/node.js
//
// If that wrapper is ever changed, then this number also has to be
// updated. Or - someone could clean this up so that the two peices
// don't need to be changed.
//
// Even better would be to get support into V8 for wrappers that
// shouldn't be reported to users.
int offset = linenum == 1 ? 62 : 0;
int offset = linenum == 1 ? 70 : 0;

fprintf(stderr, "%s\n", sourceline_string + offset);
// Print wavy underline (GetUnderline is deprecated).
Expand Down
2 changes: 1 addition & 1 deletion src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@
};

NativeModule.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'(function (exports, require, module, __filename, __dirname, define) { ',
'\n});'
];

Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/amd-modules/extra-args.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
define(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, function(r, e, m) {
exports.ok = require("./regular.js").ok;
});
3 changes: 3 additions & 0 deletions test/fixtures/amd-modules/module-exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
define(function(require, exports, module) {
module.exports = { ok: require("./regular.js").ok };
});
1 change: 1 addition & 0 deletions test/fixtures/amd-modules/object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
define({ ok: require("./regular.js").ok });
14 changes: 14 additions & 0 deletions test/fixtures/amd-modules/regular.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
var R = require;
var E = exports;
var M = module;

define(function(require, exports, module, nothingHere) {
if (R !== require) throw new Error("invalid require in AMD cb");
if (E !== exports) throw new Error("invalid exports in AMD cb");
if (M !== module) throw new Error("invalid module in AMD cb");
if (nothingHere !== undefined) {
throw new Error("unknown args to AMD cb");
}

exports.ok = { ok: true };
});
3 changes: 3 additions & 0 deletions test/fixtures/amd-modules/return.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
define(function() {
return { ok: require("./regular.js").ok };
});
20 changes: 20 additions & 0 deletions test/simple/test-module-loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,26 @@ try {
assert.equal(require(loadOrder + 'file8').file8, 'file8/index.reg', msg);
assert.equal(require(loadOrder + 'file9').file9, 'file9/index.reg2', msg);


// test the async module definition pattern modules
var amdFolder = '../fixtures/amd-modules';
var amdreg = require(amdFolder + '/regular.js');
assert.deepEqual(amdreg.ok, {ok: true}, 'regular amd module failed');

// make sure they all get the same 'ok' object.
var amdModuleExports = require(amdFolder + '/module-exports.js');
assert.equal(amdModuleExports.ok, amdreg.ok, 'amd module.exports failed');

var amdReturn = require(amdFolder + '/return.js');
assert.equal(amdReturn.ok, amdreg.ok, 'amd return failed');

var amdObj = require(amdFolder + '/object.js');
assert.equal(amdObj.ok, amdreg.ok, 'amd object literal failed');

var amdExtraArgs = require(amdFolder + '/extra-args.js');
assert.equal(amdExtraArgs.ok, amdreg.ok, 'amd extra args failed');


process.addListener('exit', function() {
assert.ok(common.indirectInstanceOf(a.A, Function));
assert.equal('A done', a.A());
Expand Down

0 comments on commit 41c143c

Please sign in to comment.