Make require() compliant with CommonJS Modules/1.1

This commit is contained in:
natecain 2013-10-12 13:44:09 -04:00
parent 539915d300
commit 951019eacc
34 changed files with 546 additions and 37 deletions

View File

@ -20,23 +20,16 @@ The module definitions on the browser look like this:
In practice, each module is wrapped in a separate script block.
\*/
(function() {
var _boot = (function($tw) {
/*jslint node: true, browser: true */
/*global modules: false, $tw: false */
"use strict";
/////////////////////////// Setting up $tw
// Set up $tw global for the server (set up for browser is in bootprefix.js)
if(typeof(window) === "undefined") {
global.$tw = global.$tw || {}; // No `browser` member for the server
exports.$tw = $tw; // Export $tw for when boot.js is required directly in node.js
}
// Include bootprefix if we're on the server
if(!$tw.browser) {
require("./bootprefix.js");
// Include bootprefix if we're not given module data
if(!$tw) {
$tw = require("./bootprefix.js").bootprefix();
}
$tw.utils = $tw.utils || {};
@ -313,6 +306,7 @@ name `.` refers to the current directory
*/
$tw.utils.resolvePath = function(sourcepath,rootpath) {
// If the source path starts with ./ or ../ then it is relative to the root
if(sourcepath.substr(0,2) === "./" || sourcepath.substr(0,3) === "../" ) {
var src = sourcepath.split("/"),
root = rootpath.split("/");
@ -332,7 +326,14 @@ $tw.utils.resolvePath = function(sourcepath,rootpath) {
return root.join("/");
} else {
// If it isn't relative, just return the path
return sourcepath;
if(rootpath) {
var root = rootpath.split("/");
// Remove the filename part of the root
root.splice(root.length - 1, 1);
return root.join("/") + "/" + sourcepath;
} else {
return sourcepath;
}
}
};
@ -362,9 +363,7 @@ $tw.utils.registerFileType = function(type,encoding,extension) {
Run code globally with specified context variables in scope
*/
$tw.utils.evalGlobal = function(code,context,filename) {
var contextCopy = $tw.utils.extend({},context,{
exports: {}
});
var contextCopy = $tw.utils.extend({},context);
// Get the context variables as a pair of arrays of names and values
var contextNames = [], contextValues = [];
$tw.utils.each(contextCopy,function(value,name) {
@ -389,9 +388,6 @@ Run code in a sandbox with only the specified context variables in scope
*/
$tw.utils.evalSandboxed = $tw.browser ? $tw.utils.evalGlobal : function(code,context,filename) {
var sandbox = $tw.utils.extend({},context);
$tw.utils.extend(sandbox,{
exports: {}
});
vm.runInNewContext(code,sandbox,filename);
return sandbox.exports;
};
@ -540,12 +536,14 @@ $tw.utils.Crypto = function() {
Execute the module named 'moduleName'. The name can optionally be relative to the module named 'moduleRoot'
*/
$tw.modules.execute = function(moduleName,moduleRoot) {
var name = moduleRoot ? $tw.utils.resolvePath(moduleName,moduleRoot) : moduleName,
moduleInfo = $tw.modules.titles[name],
tiddler = $tw.wiki.getTiddler(name),
var name = moduleName[0] === "." ? $tw.utils.resolvePath(moduleName,moduleRoot) : moduleName,
moduleInfo = $tw.modules.titles[name] || $tw.modules.titles[name + ".js"] || $tw.modules.titles[moduleName] || $tw.modules.titles[moduleName + ".js"] ,
tiddler = $tw.wiki.getTiddler(name) || $tw.wiki.getTiddler(name + ".js") || $tw.wiki.getTiddler(moduleName) || $tw.wiki.getTiddler(moduleName + ".js") ,
_exports = {},
sandbox = {
module: moduleInfo,
exports: {},
module: {},
//moduleInfo: moduleInfo,
exports: _exports,
console: console,
setInterval: setInterval,
clearInterval: clearInterval,
@ -553,18 +551,45 @@ $tw.modules.execute = function(moduleName,moduleRoot) {
clearTimeout: clearTimeout,
$tw: $tw,
require: function(title) {
return $tw.modules.execute(title,name);
return $tw.modules.execute(title, name);
}
};
Object.defineProperty(sandbox.module, "id", {
value: name,
writable: false,
enumerable: true,
configurable: false
});
if(!$tw.browser) {
$tw.utils.extend(sandbox,{
process: process
});
} else {
/*
CommonJS optional require.main property:
In a browser we offer a fake main module which points back to the boot function
(Theoretically, this may allow TW to eventually load itself as a module in the browser)
*/
Object.defineProperty(sandbox.require, "main", {
value: (typeof(require) !== "undefined") ? require.main : {TiddlyWiki: _boot},
writable: false,
enumerable: true,
configurable: false
});
}
if(!moduleInfo) {
// We could not find the module on this path
// Try to defer to browserify etc, or node
var deferredModule;
if($tw.browser) {
return $tw.utils.error("Cannot find module named '" + moduleName + "' required by module '" + moduleRoot + "', resolved to " + name);
if(window.require) {
try {
return window.require(moduleName);
} catch(e) {}
}
throw "Cannot find module named '" + moduleName + "' required by module '" + moduleRoot + "', resolved to " + name;
} else {
// If we don't have a module with that name, let node.js try to find it
return require(moduleName);
@ -575,10 +600,11 @@ $tw.modules.execute = function(moduleName,moduleRoot) {
try {
// Check the type of the definition
if(typeof moduleInfo.definition === "function") { // Function
moduleInfo.exports = {};
moduleInfo.exports = _exports;
moduleInfo.definition(moduleInfo,moduleInfo.exports,sandbox.require);
} else if(typeof moduleInfo.definition === "string") { // String
moduleInfo.exports = $tw.utils.evalSandboxed(moduleInfo.definition,sandbox,tiddler.fields.title);
moduleInfo.exports = _exports;
$tw.utils.evalSandboxed(moduleInfo.definition,sandbox,tiddler.fields.title);
} else { // Object
moduleInfo.exports = moduleInfo.definition;
}
@ -1405,4 +1431,15 @@ if($tw.browser) {
$tw.boot.boot();
}
})();
return $tw;
});
if(typeof(exports) !== "undefined") {
exports.TiddlyWiki = _boot;
} else {
_boot(window.$tw);
}

View File

@ -12,12 +12,11 @@ See Boot.js for further details of the boot process.
\*/
// Set up $tw global for the browser
if(typeof(window) === "undefined") {
global.$tw = global.$tw || {}; // No `browser` member for the server
} else {
window.$tw = window.$tw || {browser: {}};
}
var _bootprefix = (function($tw) {
"use strict";
$tw = $tw || {browser: typeof(window) !== "undefined" ? {} : null};
/*
Information about each module is kept in an object with these members:
@ -81,3 +80,18 @@ Convenience function for pushing a tiddler onto the preloading array
$tw.preloadTiddler = function(fields) {
$tw.preloadTiddlers.push(fields);
};
return $tw
});
if(typeof(exports) === "undefined") {
// Set up $tw global for the browser
window.$tw = _bootprefix();
} else {
// Export functionality as a module
exports.bootprefix = _bootprefix;
}

View File

@ -0,0 +1,7 @@
title: GettingStarted
This wiki instance contains the CommonJS Modules/1.0 unit tests.
To run them, open a console repl and execute "$tw.modules.execute('allTests')" there. You should see no exceptions or output starting with "FAIL" in the console.

View File

@ -0,0 +1,11 @@
/*\
title: absolute/b.js
type: application/javascript
module-type: library
Absolute require test
\*/
exports.foo = function() {};

View File

@ -0,0 +1,16 @@
/*\
title: absolute/program.js
type: application/javascript
module-type: library
Absolute require test
\*/
var test = require('test');
var a = require('./submodule/a');
var b = require('./b');
test.assert(a.foo().foo === b.foo, 'require works with absolute identifiers');
test.print('DONE', 'info');

View File

@ -0,0 +1,14 @@
/*\
title: absolute/submodule/a.js
type: application/javascript
module-type: library
Absolute require test
\*/
exports.foo = function () {
return require('../b');
};

View File

@ -0,0 +1,23 @@
/*\
title: allTests.js
type: application/javascript
module-type: library
Runs all CommonJS Modules tests
\*/
$tw.modules.execute('absolute/program.js');
$tw.modules.execute('cyclic/program.js');
$tw.modules.execute('determinism/program.js');
$tw.modules.execute('exactExports/program.js');
$tw.modules.execute('hasOwnProperty/program.js');
$tw.modules.execute('method/program.js');
$tw.modules.execute('missing/program.js');
$tw.modules.execute('monkeys/program.js');
$tw.modules.execute('nested/program.js');
$tw.modules.execute('relative/program.js');
$tw.modules.execute('transitive/program.js');

View File

@ -0,0 +1,15 @@
/*\
title: cyclic/a.js
type: application/javascript
module-type: library
Cycle require test A
\*/
exports.a = function () {
return b;
};
var b = require('./b');

View File

@ -0,0 +1,16 @@
/*\
title: cyclic/b.js
type: application/javascript
module-type: library
Cycle require test B
\*/
var a = require('./a');
exports.b = function () {
return a;
};

View File

@ -0,0 +1,22 @@
/*\
title: cyclic/program.js
type: application/javascript
module-type: library
Cycle require test
\*/
var test = require('test');
var a = require('./a');
var b = require('./b');
test.assert(a.a, 'a exists');
test.assert(b.b, 'b exists')
test.assert(a.a().b === b.b, 'a gets b');
test.assert(b.b().a === a.a, 'b gets a');
test.print('DONE', 'info');

View File

@ -0,0 +1,14 @@
/*\
title: determinism/program.js
type: application/javascript
module-type: library
Determinism test
\*/
var test = require('test');
require('submodule/a');
test.print('DONE', 'info');

View File

@ -0,0 +1,20 @@
/*\
title: determinism/submodule/a.js
type: application/javascript
module-type: library
Determinism require test A
\*/
var test = require('test');
var pass = false;
var test = require('test');
try {
require('a');
} catch (exception) {
pass = true;
}
test.assert(pass, 'require does not fall back to relative modules when absolutes are not available.')

View File

@ -0,0 +1,15 @@
/*\
title: exactExports/a.js
type: application/javascript
module-type: library
ExactExports test A
\*/
exports.program = function () {
return require('./program');
};

View File

@ -0,0 +1,16 @@
/*\
title: exactExports/program.js
type: application/javascript
module-type: library
ExactExports test
\*/
var test = require('test');
var a = require('./a');
test.assert(a.program() === exports, 'exact exports');
test.print('DONE', 'info');

View File

@ -0,0 +1,8 @@
/*\
title: hasOwnProperty.js
type: application/javascript
module-type: library
OwnProperty test A
\*/

View File

@ -0,0 +1,15 @@
/*\
title: hasOwnProperty/program.js
type: application/javascript
module-type: library
OwnProperty test
\*/
var hasOwnProperty = require('hasOwnProperty');
var toString = require('toString');
var test = require('test');
test.print('DONE', 'info');

View File

@ -0,0 +1,8 @@
/*\
title: toString.js
type: application/javascript
module-type: library
OwnProperty test B
\*/

View File

@ -0,0 +1,23 @@
/*\
title: method/a.js
type: application/javascript
module-type: library
Method test
\*/
exports.foo = function () {
return this;
};
exports.set = function (x) {
this.x = x;
};
exports.get = function () {
return this.x;
};
exports.getClosed = function () {
return exports.x;
};

View File

@ -0,0 +1,19 @@
/*\
title: method/program.js
type: application/javascript
module-type: library
Method test
\*/
var test = require('test');
var a = require('./a');
var foo = a.foo;
test.assert(a.foo() == a, 'calling a module member');
test.assert(foo() == (function (){return this})(), 'members not implicitly bound');
a.set(10);
test.assert(a.get() == 10, 'get and set')
test.print('DONE', 'info');

View File

@ -0,0 +1,19 @@
/*\
title: missing/program.js
type: application/javascript
module-type: library
Missing test
\*/
var test = require('test');
try {
require('bogus');
test.print('FAIL require throws error when module missing', 'fail');
} catch (exception) {
test.print('PASS require throws error when module missing', 'pass');
}
test.print('DONE', 'info');

View File

@ -0,0 +1,12 @@
/*\
title: monkeys/a.js
type: application/javascript
module-type: library
Monkeys test A
\*/
require('./program').monkey = 10;

View File

@ -0,0 +1,15 @@
/*\
title: monkeys/program.js
type: application/javascript
module-type: library
Monkeys test
\*/
var a = require('./a');
var test = require('test');
test.assert(exports.monkey == 10, 'monkeys permitted');
test.print('DONE', 'info');

View File

@ -0,0 +1,14 @@
/*\
title: a/b/c/d.js
type: application/javascript
module-type: library
Nested test
\*/
exports.foo = function () {
return 1;
};

View File

@ -0,0 +1,14 @@
/*\
title: nested/program.js
type: application/javascript
module-type: library
Nested test
\*/
var test = require('test');
test.assert(require('a/b/c/d').foo() == 1, 'nested module identifier');
test.print('DONE', 'info');

View File

@ -0,0 +1,16 @@
/*\
title: relative/program.js
type: application/javascript
module-type: library
Relative test
\*/
var test = require('test');
var a = require('submodule/a');
var b = require('submodule/b');
test.assert(a.foo == b.foo, 'a and b share foo through a relative require');
test.print('DONE', 'info');

View File

@ -0,0 +1,13 @@
/*\
title: submodule/a.js
type: application/javascript
module-type: library
Relative test A
\*/
exports.foo = require('./b').foo;

View File

@ -0,0 +1,12 @@
/*\
title: submodule/b.js
type: application/javascript
module-type: library
Relative test B
\*/
exports.foo = function () {
};

View File

@ -0,0 +1,23 @@
/*\
title: test.js
type: application/javascript
module-type: library
testing lib
\*/
exports.assert = function(cond, msg) {
if(!cond) {
if(msg) {
throw msg
} else {
throw "ASSERT FAILED"
}
}
}
exports.print = function() {
console.log.apply(console, arguments);
}

View File

@ -0,0 +1,11 @@
/*\
title: transitive/a.js
type: application/javascript
module-type: library
Transitive test A
\*/
exports.foo = require('./b').foo;

View File

@ -0,0 +1,12 @@
/*\
title: transitive/b.js
type: application/javascript
module-type: library
Transitive test B
\*/
exports.foo = require('./c').foo;

View File

@ -0,0 +1,14 @@
/*\
title: transitive/c.js
type: application/javascript
module-type: library
Transitive test C
\*/
exports.foo = function () {
return 1;
};

View File

@ -0,0 +1,13 @@
/*\
title: transitive/program.js
type: application/javascript
module-type: library
Transitive test
\*/
var test = require('test');
test.assert(require('./a').foo() == 1, 'transitive');
test.print('DONE', 'info');

View File

@ -0,0 +1,8 @@
{
"plugins": [
"tiddlywiki/fullscreen"
],
"themes": [
"tiddlywiki/snowwhite"
]
}

View File

@ -4,7 +4,7 @@
This is invoked as a shell script by NPM when the `tiddlywiki` command is typed
*/
var $tw = require("./boot/boot.js").$tw;
var $tw = require("./boot/boot.js").TiddlyWiki();
// Pass the command line arguments to the boot kernel
$tw.boot.argv = Array.prototype.slice.call(process.argv,2);