Browse Source

Module system refactor

There is one major API change in the refactor: filename extensions are now
required when requiring or including modules.

Added extra test to test-module-loading.js.
v0.7.4-release
Ryan 16 years ago
parent
commit
b6fe4aec50
  1. 230
      src/node.js
  2. 11
      test/fixtures/a.js
  3. 8
      test/fixtures/b/c.js
  4. 4
      test/fixtures/b/d.js
  5. 2
      test/test-cat-noexist.js
  6. 2
      test/test-file-open.js
  7. 2
      test/test-http-server.js
  8. 23
      test/test-module-loading.js
  9. 2
      test/test-pingpong.js
  10. 2
      test/test-reconnecting-socket.js
  11. 2
      test/test-setTimeout.js
  12. 12
      test/test-test.js
  13. 9
      website/api.html

230
src/node.js

@ -1,16 +1,38 @@
// module search paths // Timers
node.includes = ["."];
function setTimeout (callback, delay) {
var timer = new node.Timer(callback, delay, 0);
timer.start();
return timer;
}
function setInterval (callback, delay) {
var timer = new node.Timer(callback, delay, delay);
timer.start();
return timer;
}
function clearTimeout (timer) {
timer.stop();
delete timer;
}
clearInterval = clearTimeout;
// This is useful for dealing with raw encodings. // This is useful for dealing with raw encodings.
Array.prototype.encodeUtf8 = function () { Array.prototype.encodeUtf8 = function () {
return String.fromCharCode.apply(String, this); return String.fromCharCode.apply(String, this);
} };
node.path = new function () { node.path = new function () {
this.join = function () { this.join = function () {
var joined = ""; var joined = "";
for (var i = 0; i < arguments.length; i++) { for (var i = 0; i < arguments.length; i++) {
var part = arguments[i].toString(); var part = arguments[i].toString();
if (part === ".") continue;
while (/^\.\//.exec(part)) part = part.replace(/^\.\//, "");
if (i === 0) { if (i === 0) {
part = part.replace(/\/*$/, "/"); part = part.replace(/\/*$/, "/");
} else if (i === arguments.length - 1) { } else if (i === arguments.length - 1) {
@ -32,149 +54,93 @@ node.path = new function () {
}; };
}; };
// Timers // Module
function setTimeout (callback, delay) { node.Module = function (o) {
var timer = new node.Timer(callback, delay, 0); this.parent = o.parent;
timer.start(); this.target = o.target || {};
return timer;
};
function setInterval (callback, delay) { if (!o.path) throw "path argument required";
var timer = new node.Timer(callback, delay, delay); if (o.path.charAt(0) == "/")
timer.start(); throw "Absolute module paths are not yet supported in Node";
return timer;
};
function clearTimeout (timer) { var dir = o.base_directory || ".";
timer.stop(); this.filename = node.path.join(dir, o.path);
delete timer;
};
clearInterval = clearTimeout;
// Modules this.loaded = false;
this.exited = false;
(function () { this.children = [];
function findScript(base_directory, name, callback) {
// in the future this function will be more complicated
if (name.charAt(0) == "/")
throw "absolute module paths are not yet supported.";
var filename = node.path.join(base_directory, name) + ".js";
node.fs.exists(filename, function (status) {
callback(status ? filename : null);
});
}
// Constructor for submodule.
// "name" is like a path but without .js. e.g. "database/mysql"
// "target" is an object into which the submodule will be loaded.
function Sub (name, target) {
this.name = name;
this.target = target;
this.load = function (base_directory, callback) {
//node.debug("sub.load from <" + base_directory + "> " + this.toString());
findScript(base_directory, name, function (filename) {
if (filename === null) {
stderr.puts("Cannot find a script matching: " + name);
node.exit(1);
}
loadScript(filename, target, callback);
});
}; };
this.toString = function () { node.Module.prototype.load = function (callback) {
return "[sub name=" + name + " target=" + target.toString() + "]"; var self = this;
} if (self.loaded)
} throw "Module '" + self.filename + "' is already loaded.";
function Scaffold (source, filename, module) {
// wrap the source in a strange function
var source = "function (__filename) {"
+ " var onLoad;"
+ " var exports = this;"
+ " var require = this.__require;"
+ " var include = this.__include;"
+ source
+ " this.__onLoad = onLoad;"
+ "};"
;
// returns the function
var compiled = node.compile(source, filename);
if (module.__onLoad) {
//node.debug("<"+ filename+"> has onload! this is bad");
}
module.__subs = []; node.fs.cat(self.filename, "utf8", function (status, content) {
module.__require = function (name) {
var target = {};
module.__subs.push(new Sub(name, target));
return target;
}
module.__include = function (name) {
module.__subs.push(new Sub(name, module));
}
// execute the script of interest
compiled.apply(module, [filename]);
// The module still needs to have its submodules loaded.
this.filename = filename;
this.module = module;
this.subs = module.__subs;
this.onLoad = module.__onLoad;
// remove these references so they don't get exported.
delete module.__subs;
delete module.__onLoad;
delete module.__require;
delete module.__include;
}
function loadScript (filename, target, callback) {
node.fs.cat(filename, "utf8", function (status, content) {
if (status != 0) { if (status != 0) {
stderr.puts("Error reading " + filename); stderr.puts("Error reading " + self.filename);
node.exit(1); node.exit(1);
} }
var scaffold = new Scaffold(content, filename, target); self.target.__require = function (path) { return self.newChild(path, {}); };
self.target.__include = function (path) { self.newChild(path, self.target); };
//node.debug("after scaffold <" + filename + ">");
// create wrapper function
function finish() { var wrapper = "function (__filename) {\n"
//node.debug("finish 1 load <" + filename + ">"); + " var onLoad;\n"
if (scaffold.onLoad instanceof Function) { + " var onExit;\n"
//node.debug("calling onLoad for <" + filename + ">"); + " var exports = this;\n"
scaffold.onLoad(); + " var require = this.__require;\n"
} + " var include = this.__include;\n"
//node.debug("finish 2 load <" + filename + ">"); + content
+ "\n"
+ " this.__onLoad = onLoad;\n"
+ " this.__onExit = onExit;\n"
+ "};\n"
;
var compiled_wrapper = node.compile(wrapper, self.filename);
if (callback instanceof Function) // execute the script of interest
callback(); compiled_wrapper.apply(self.target, [self.filename]);
} self.onLoad = self.target.__onLoad;
self.onExit = self.target.__onExit;
self.loadChildren(function () {
if (self.onLoad) self.onLoad();
self.loaded = true;
if (callback) callback();
});
});
};
// Each time require() or include() was called inside the script node.Module.prototype.newChild = function (path, target) {
// a key/value was added to scaffold.__subs. var child = new node.Module({
// Now we loop though each one and recursively load each. target: target,
if (scaffold.subs.length == 0) { path: path,
finish(); base_directory: node.path.dirname(this.filename),
} else { parent: this
var ncomplete = 0;
for (var i = 0; i < scaffold.subs.length; i++) {
var sub = scaffold.subs[i];
sub.load(node.path.dirname(filename), function () {
ncomplete += 1;
//node.debug("<" + filename + "> ncomplete = " + ncomplete.toString() + " scaffold.subs.length = " + scaffold.subs.length.toString());
if (ncomplete === scaffold.subs.length)
finish();
}); });
} this.children.push(child);
} return target;
};
node.Module.prototype.loadChildren = function (callback) {
var children = this.children;
if (children.length == 0 && callback) callback();
var nloaded = 0;
for (var i = 0; i < children.length; i++) {
var child = children[i];
child.load(function () {
nloaded += 1;
if (nloaded == children.length && callback) callback();
}); });
} }
};
node.Module.prototype.exit = function (callback) {
throw "not implemented";
};
loadScript(ARGV[1], this); // Load the root module. I.E. the command line argument.
})(); (new node.Module({ path: ARGV[1], target: this })).load();

11
test/fixtures/a.js

@ -1,5 +1,10 @@
var c = require("b/c"); var c = require("b/c.js");
exports.A = function () { exports.A = function () {
return "A"; return "A";
} };
exports.C = function () { return c.C(); } exports.C = function () {
return c.C();
};
exports.D = function () {
return c.D();
};

8
test/fixtures/b/c.js

@ -1,3 +1,9 @@
var d = require("d.js");
exports.C = function () { exports.C = function () {
return "C"; return "C";
} };
exports.D = function () {
return d.D();
};

4
test/fixtures/b/d.js

@ -0,0 +1,4 @@
exports.D = function () {
return "D";
};

2
test/test-cat-noexist.js

@ -1,4 +1,4 @@
include("mjsunit"); include("mjsunit.js");
function onLoad () { function onLoad () {
var dirname = node.path.dirname(__filename); var dirname = node.path.dirname(__filename);

2
test/test-file-open.js

@ -1,4 +1,4 @@
include("mjsunit"); include("mjsunit.js");
var assert_count = 0; var assert_count = 0;
function onLoad () { function onLoad () {

2
test/test-http-server.js

@ -1,4 +1,4 @@
include("mjsunit"); include("mjsunit.js");
var port = 8222; var port = 8222;

23
test/test-module-loading.js

@ -0,0 +1,23 @@
include("mjsunit.js");
var a = require("fixtures/a.js");
var d = require("fixtures/b/d.js");
var d2 = require("fixtures/b/d.js");
function onLoad () {
assertFalse(false, "testing the test program.");
assertInstanceof(a.A, Function);
assertEquals("A", a.A());
assertInstanceof(a.C, Function);
assertEquals("C", a.C());
assertInstanceof(a.D, Function);
assertEquals("D", a.D());
assertInstanceof(d.D, Function);
assertEquals("D", d.D());
assertInstanceof(d2.D, Function);
assertEquals("D", d2.D());
}

2
test/test-pingpong.js

@ -1,4 +1,4 @@
include("mjsunit"); include("mjsunit.js");
var port = 12123; var port = 12123;
var N = 1000; var N = 1000;

2
test/test-reconnecting-socket.js

@ -1,4 +1,4 @@
include("mjsunit"); include("mjsunit.js");
var port = 8921; var port = 8921;
function onLoad () { function onLoad () {

2
test/test-setTimeout.js

@ -1,4 +1,4 @@
include("mjsunit"); include("mjsunit.js");
function onLoad () { function onLoad () {
assertInstanceof(setTimeout, Function); assertInstanceof(setTimeout, Function);

12
test/test-test.js

@ -1,12 +0,0 @@
include("mjsunit");
var a = require("fixtures/a");
function onLoad () {
assertFalse(false, "testing the test program.");
assertInstanceof(a.A, Function);
assertEquals("A", a.A());
assertInstanceof(a.C, Function);
assertEquals("C", a.C());
}

9
website/api.html

@ -880,7 +880,7 @@ req.finish(function (res) {
<p>The contents of <code>foo.js</code>:</p> <p>The contents of <code>foo.js</code>:</p>
<pre> <pre>
include("mjsunit"); include("mjsunit.js");
function onLoad () { function onLoad () {
assertEquals(1, 2); assertEquals(1, 2);
}</pre> }</pre>
@ -900,12 +900,11 @@ exports.assertEquals = function (expected, found, name_opt) {
};</pre> };</pre>
<p> <p>
The module <code>mjsunit</code> has exported a function The module <code>mjsunit.js</code> has exported a function
<code>assertEquals()</code>. <code>mjsunit.js</code> must be <code>assertEquals()</code>. <code>mjsunit.js</code> must be
in the same directory as <code>foo.js</code> for in the same directory as <code>foo.js</code> for
<code>include()</code> to find it. The module path is relative <code>include()</code> to find it. The module path is relative
to the file calling <code>include()</code>. The module path does to the file calling <code>include()</code>.
not include filename extensions like <code>.js</code>.
</p> </p>
<p> <p>
@ -935,7 +934,7 @@ exports.assertEquals = function (expected, found, name_opt) {
after the <code>onLoad()</code> callback is made. For example: after the <code>onLoad()</code> callback is made. For example:
</p> </p>
<pre> <pre>
var mjsunit = require("mjsunit"); var mjsunit = require("mjsunit.js");
function onLoad () { function onLoad () {
mjsunit.assertEquals(1, 2); mjsunit.assertEquals(1, 2);
}</pre> }</pre>

Loading…
Cancel
Save