├── .gitignore ├── Makefile ├── README.md ├── bin └── browser-require ├── browser-require.old.js ├── examples ├── npm │ ├── index.html │ ├── index.jade │ ├── js │ │ └── app.js │ └── server.js └── relative │ ├── index.html │ ├── index.jade │ ├── js │ ├── above.js │ ├── app.js │ ├── nested │ │ └── nest.js │ └── rel.js │ └── server.js ├── lib ├── browser-require.js ├── npm_module.js └── templates │ ├── boilerplate.js │ └── inject_module.js ├── package.json └── test ├── compiled ├── index.html ├── js │ ├── above.js │ ├── level1.js │ ├── level2.js │ ├── level2 │ │ └── level3.js │ ├── nested │ │ ├── aboveNested.js │ │ └── nest.js │ ├── rel.js │ └── test.js └── server.js ├── hell.js └── nested ├── css └── qunit.css ├── index.html ├── js ├── above.js ├── level1.js ├── level2.js ├── level2 │ └── level3.js ├── nested │ ├── aboveNested.js │ └── nest.js ├── qunit.js ├── rel.js └── test.js └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | **.swp 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | start-test-server: 2 | @cd test/nested && $(shell which node) server.js 2>&1 & 3 | stop-test-server: 4 | @pkill brtest 5 | test-chrome: 6 | @$(shell which google-chrome) http://localhost:1234/?v=$(shell date +%s) 7 | test-firefox: 8 | @$(shell which firefox) http://localhost:1234/?v=$(shell date +%s) 9 | 10 | .PHONY: start-test-server stop-test-server test-chrome test-firefox 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | browser-require 2 | =============== 3 | 4 | #### The easiest way to require and use CommonJS and NPM modules from your browser 5 | 6 | Npm makes it easy to share code on your server. But how many times have you 7 | wanted to share Javascript code easily between the server and the browser? 8 | `browser-require` allows you to easily use CommonJS and NPM modules defined 9 | on the server in your browser. 10 | 11 | browser-require enables you to require both relative (local to your project) 12 | CommonJS modules as well as global NPM modules. 13 | 14 | ### Installation 15 | To install: 16 | $ npm install browser-require 17 | 18 | ### Using browser-require within your connect app 19 | 20 | Currently, browser-require depends on the 21 | [connect](https://github.com/visionmedia/connect/) middleware framework, 22 | if you want to serve client javascript files that contain `require`s. 23 | 24 | First, add in the browser-require middleware into your `connect` server: 25 | var connect = require('connect') 26 | , app = connect.createServer() 27 | , exposeRequire = require('browser-require'); 28 | 29 | // The following line "app.use(..." is what you want to add to your project 30 | // Make sure the browser-require middleware comes before staticProvider middleware 31 | app.use(exposeRequire({ 32 | base: __dirname // This is where we look to find your non-global modules 33 | }); 34 | 35 | app.use(connect.staticProvider(__dirname)); 36 | app.listen(3000); 37 | 38 | On the browser, this is what your index.html might look like: 39 | 40 | 41 | 42 | 43 | browser-require example 44 | 45 | 46 | 47 | 48 | 49 | 50 | The script src "/js/app.js" is where your custom JavaScript code resides. 51 | 52 | Then in `/js/app.js`, you can require CommonJS and NPM modules as if you are on the server: 53 | 54 | var _ = require('underscore'); // browser-side requires FTW!!!! 55 | 56 | // This should alert "10" 57 | alert(_.reduce([1, 2, 3, 4], function (sum, num) { 58 | sum += num; 59 | return sum; 60 | })); 61 | 62 | ### How it works 63 | When you request a javascript file: 64 | 65 | 1. The server looks up the source and its module dependencies (if any) recursively. 66 | 2. Once the server has collected all dependencies, it compiles the top-level file plus 67 | its dependencies into a file that gets sent back to the browser. 68 | 69 | ### Command line binary 70 | Sometimes you need to statically compile a set of javascript client files from the command line. 71 | For example, this is necessary if you are building a Chrome plugin. A Chrome plugin can use JavaScript 72 | files that exist inside the Chrome plugin (as opposed to fetching a JavaScript file that exists on the 73 | server). Therefore, it is necessary in this case to compile your JavaScript files and their dependencies 74 | outside of the context of a server. 75 | 76 | `browser-require` supports this via a command line binary. You can use it in the following way: 77 | 78 | $ browser-require path/to/js/file.js > path/to/compiled/js/file.js 79 | 80 | ### Examples 81 | There are examples in the [./examples](https://github.com/bnoguchi/browser-require/tree/master/examples) directory. 82 | 83 | To run the relative modules example: 84 | 85 | $ cd examples/relative 86 | $ node server.js 87 | 88 | To run the npm modules example: 89 | 90 | $ npm install underscore 91 | $ npm install data-structures-js 92 | $ npm install validator 93 | $ cd examples/npm 94 | $ node server.js 95 | 96 | ### Running the tests 97 | First, make sure the following npm modules are installed, since we will be 98 | using them to test browser-require: 99 | 100 | $ npm install underscore 101 | $ npm install data-structures-js 102 | $ npm install validator 103 | 104 | First, start up the test server: 105 | 106 | $ make start-test-server 107 | 108 | To run tests in Chrome: 109 | 110 | $ make test-chrome 111 | 112 | To run tests in Firefox: 113 | 114 | $ make test-firefox 115 | 116 | Finally, stop the test server: 117 | 118 | $ make stop-test-server 119 | 120 | ### Planning on implementing 121 | - A middleware filter mechanism to include things such as a Google Closure Compiler filter. 122 | 123 | ### Contributors 124 | - [Brian Noguchi](https://github.com/bnoguchi) 125 | 126 | ### License 127 | MIT License 128 | 129 | --- 130 | ### Author 131 | Brian Noguchi 132 | -------------------------------------------------------------------------------- /bin/browser-require: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var br = require('../lib/browser-require') 4 | , compileAll = br.compileAll 5 | , ScriptPromise = br.ScriptPromise 6 | , fs = require('fs'); 7 | 8 | var fname = process.argv[2]; 9 | 10 | ScriptPromise.base = process.cwd(); 11 | fname = fs.realpathSync(fname); 12 | fname = fname.replace(ScriptPromise.base, '.'); 13 | ScriptPromise.from(fname, null, function (script) { 14 | compileAll(script, function (compiled) { 15 | console.log(compiled); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /browser-require.old.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , path = require('path') 3 | , npm = require('npm'); 4 | 5 | module.exports = function (opts) { 6 | var baseDir = opts.base 7 | , compile = opts.compile 8 | 9 | , cache = {} // {moduleName: { mtime: ..., compiled: ...}} 10 | , templates = { 11 | response: fs.readFileSync(__dirname + '/templates/response.js', 'utf8') 12 | , src: fs.readFileSync(__dirname + '/templates/src.js', 'utf8') 13 | }; 14 | 15 | /** 16 | * Loads the module's parameters into the template. 17 | * In dynamic mode, the template is sent to the browser. 18 | * In compile mode, the template is used as a fragment in the aggregate compiled output. 19 | * 20 | * @param {String} module is the name of the module (e.g., 'somemodule.js') 21 | * @param {String} src is the src of the module 22 | * @param {Array} deps is the array of the module's dependencies, as Strings 23 | * @param {Boolean} isIndex is true if module's src existed at an index.js 24 | * @return {String} the template that is filled in with the module's information 25 | */ 26 | function fillinTemplate (module, src, deps, isIndex) { 27 | var dir = isIndex 28 | ? module.replace(/\.js$/, '').split('/') 29 | : module.split('/').slice(0, -1) 30 | , src = templates.src 31 | .replace('$src', src) 32 | .replace(/\$dir/g, JSON.stringify(dir)); 33 | return templates.response 34 | .replace('$module', JSON.stringify(module)) 35 | .replace('$src', JSON.stringify(src)) 36 | .replace('$deps', JSON.stringify(deps)) 37 | .replace('$isIndex', isIndex); 38 | } 39 | 40 | /** 41 | * Returns the names of the dependencies found in the source text. 42 | * 43 | * @param {String} src 44 | * @return {Array} dependencies 45 | */ 46 | function depsFor (src) { 47 | var re = /require\(['"]([^'"]+)['"]\)/g 48 | , match 49 | , deps = []; 50 | while (match = re.exec(src)) { 51 | deps.push(match[1]); 52 | } 53 | return deps; 54 | } 55 | 56 | function NpmModule (url) { 57 | var modulePath = url.replace(NpmModule.npmFlag, '').replace(/\.js$/, '') 58 | , moduleChain = modulePath.split('/') 59 | , pkgName = this.pkgName = moduleChain[0] 60 | , relChain = this.relChain = moduleChain.slice(1); 61 | this.isSubmodule = !!relChain.length; 62 | var nv = pkgName.split('@') 63 | , n = this.name = nv[0] 64 | , v = this.ver = nv[1] || 'active'; 65 | this.dir = path.join(npm.dir, n, v, 'package'); 66 | } 67 | NpmModule.npmFlag = /^\/NPM\//; 68 | 69 | NpmModule.prototype = { 70 | __pkgJson: function (fn) { 71 | if (this._pkg) return fn(null, this._pkg); 72 | if (this._pkgerr) return fn(this._pkgerr); 73 | var self = this; 74 | fs.readFile(this.dir + '/package.json', 'utf8', function (err, body) { 75 | if (err) { 76 | self._pkgerr = err; 77 | console.error("package.json missing for " + self.pkgName); 78 | return fn(err); 79 | } 80 | var pkg = self._pkg = JSON.parse(body); 81 | fn(null, pkg); 82 | }); 83 | }, 84 | __mainSrc: function (fn) { 85 | if (this._mainSrc) return fn(null, this._mainSrc); 86 | if (this._mainSrcErr) return fn(this._mainSrcErr); 87 | var self = this; 88 | this.pkgJson( function (err, pkg) { 89 | if (err) return fn(self._mainSrcErr = err); 90 | if (pkg.main) { 91 | fs.stat(self.dir + '/' + pkg.main, function (err, stat) { 92 | if (err) { 93 | console.error(err); 94 | fn(self._mainSrcErr = err); 95 | } else if (stat.isDirectory()) { 96 | self._mainSrc = fs.readFileSync(self.dir + '/' + pkg.main + '/index.js', 'utf8'); 97 | fn(null, self._mainSrc); 98 | } else { 99 | self._mainSrc = fs.readFileSync(self.dir + '/' + pkg.main, 'utf8'); 100 | fn(null, self._mainSrc); 101 | } 102 | }); 103 | } else { 104 | fn(self.mainSrcErr = "Missing main in package.json for " + self.pkgName); 105 | } 106 | }); 107 | }, 108 | __relSrc: function (fn) { 109 | if (this._relSrc) return fn(null, this._relSrc); 110 | if (this._relSrcErr) return fn(this._relSrcErr); 111 | var self = this; 112 | this.pkgJson( function (err, pkg) { 113 | if (err) return fn(self._relSrcErr = err); 114 | var directories = pkg.directory || pkg.directories 115 | , lib = directories && directories.lib 116 | , chain = [self.dir], direct, index; 117 | if (lib) chain.push(lib); 118 | direct = path.join.apply(this, chain.concat([self.relChain.join('/') + '.js']) ); 119 | index = path.join.apply(this, chain.concat([self.relChain.join('/'), 'index.js']) ); 120 | if (path.existsSync(direct)) { 121 | fn(null, fs.readFileSync(direct, 'utf8'), false); 122 | } else if (path.existsSync(index)) { 123 | fn(null, fs.readFileSync(index, 'utf8'), true); 124 | } else { 125 | if (lib) { 126 | throw new Error("Unimplemented - could not find package " + self.relChain.join('/')); 127 | } else { 128 | self._relSrcErr = 'Missing ' + self.relChain.join('/') + ' in ' + self.pkgName + ' package'; 129 | fn(self._relSrcErr); 130 | } 131 | } 132 | }); 133 | }, 134 | __src: function (fn) { 135 | if (this._src) return fn(null, this._src); 136 | if (this._srcerr) return fn(this._srcerr); 137 | var self = this; 138 | if (this.isSubmodule) { // Handle e.g., require("npm-module/sub-module") 139 | this.relSrc( function (err, relSrc, isIndex) { 140 | if (err) return fn(self._srcerr = err); 141 | fn(null, self._src = relSrc, isIndex); 142 | }); 143 | } else { // Handle e.g., require("npm-module") 144 | this.mainSrc( function (err, mainSrc) { 145 | if (err) return fn(self._srcerr = err); 146 | fn(null, self._src = mainSrc); 147 | }); 148 | } 149 | }, 150 | __isInstalled: function (fn) { 151 | var self = this; 152 | fs.stat(this.dir, function (err, stat) { 153 | if (err || !stat.isDirectory()) { 154 | console.error(self.name + " is not installed via npm."); 155 | fn(err, false); 156 | } else { 157 | fn(null, true); 158 | } 159 | }); 160 | } 161 | }; 162 | 163 | // Wrap npm in a promise, for manageable async middleware 164 | NpmModule.isNpmLoaded = false; 165 | NpmModule.callbacks = []; // Callbacks for loaded event 166 | var proto = NpmModule.prototype; 167 | for (var k in proto) { 168 | proto[k.slice(2)] = (function (k) { 169 | return function () { 170 | if (NpmModule.isNpmLoaded) return proto[k].apply(this, arguments); 171 | NpmModule.callbacks.push([proto[k], this, arguments]); 172 | }; 173 | })(k); 174 | } 175 | npm.load( function () { 176 | NpmModule.isNpmLoaded = true; 177 | var callbacks = NpmModule.callbacks; 178 | for (var i = 0, l = callbacks.length; i < l; i++) { 179 | callbacks[i][0].apply(callbacks[i][1], callbacks[i][2]); 180 | } 181 | }); 182 | 183 | /** 184 | * The incoming url does not always verbatim point to the src file. 185 | * This is not the case for 186 | */ 187 | function extractUrl (url) { 188 | // The following continuous block handles incoming requires 189 | // that exist above the base dir 190 | var uq = url.split('?') 191 | , chain = uq[0].split('/') 192 | , q = uq[1] 193 | , match 194 | , nAboveBase; 195 | // prefix carries ..,..,.. information - i.e., how many levels above 196 | if (q) { 197 | match = q.match(/n=([^&]+)/); 198 | if (match) nAboveBase = parseInt(match[1], 10); 199 | } 200 | if (nAboveBase) { 201 | url = chain.join('/'); 202 | while (nAboveBase--) url = '/..' + url; 203 | } 204 | return url; 205 | } 206 | 207 | return function (req, res, next) { 208 | var src 209 | , url = extractUrl(req.url) 210 | , body 211 | , filepath; 212 | 213 | if (src = cache[url]) { 214 | res.writeHead(200, {'Content-Type': 'text/javascript'}); 215 | res.end(src); 216 | } else if ('.js' === path.extname(url)) { 217 | if (url === '/browser_require.js') { 218 | src = 219 | cache[url] = fs.readFileSync( 220 | path.dirname(__filename) + 221 | '/client/browser_require.js', 'utf8'); 222 | 223 | res.writeHead(200, {'Content-Type': 'text/javascript'}); 224 | res.end(src); 225 | } else if (NpmModule.npmFlag.test(url)) { // Handle npm modules 226 | var npmModule = new NpmModule(url); 227 | npmModule.isInstalled(function (err, isInstalled) { 228 | if (isInstalled) { 229 | npmModule.src(function (err, body, isIndex) { 230 | var src = 231 | cache[url] = fillinTemplate(url, body, depsFor(body), isIndex); 232 | res.writeHead(200, {'Content-Type': 'text/javascript'}); 233 | res.end(src); 234 | }); 235 | } else { 236 | console.error("Could not find " + npmModule.pkgName + 237 | ". Make sure it's installed via npm."); 238 | res.writeHead(404); 239 | res.end(); 240 | } 241 | }); 242 | } else { // Handle local, relative modules 243 | filepath = path.join(baseDir, url); 244 | if (path.existsSync(filepath)) { 245 | body = fs.readFileSync(filepath, 'utf8'); 246 | src = 247 | cache[url] = fillinTemplate(url, body, depsFor(body)); 248 | res.writeHead(200, {'Content-Type': 'text/javascript'}); 249 | res.end(src); 250 | } else { 251 | console.error("Could not find " + filepath); 252 | next(); 253 | } 254 | } 255 | } else { 256 | next(); 257 | } 258 | }; 259 | }; 260 | -------------------------------------------------------------------------------- /examples/npm/index.html: -------------------------------------------------------------------------------- 1 | browser-require relative module example
-------------------------------------------------------------------------------- /examples/npm/index.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | html 3 | head 4 | title browser-require relative module example 5 | body 6 | #answer 7 | script(type: 'text/javascript', src: 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js') 8 | script(type: 'text/javascript', src: '/js/app.js') 9 | -------------------------------------------------------------------------------- /examples/npm/js/app.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore') 2 | , Set = require('data-structures-js/set') 3 | , xss = require('validator/xss'); 4 | 5 | console.log(Set); 6 | console.log(xss); 7 | 8 | $( function () { 9 | var str = _.reduce( [1, 2, 3, 4], function (str, num) { 10 | str += num; 11 | return str; 12 | }, ''); 13 | $('#answer').text(str); 14 | }); 15 | -------------------------------------------------------------------------------- /examples/npm/server.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | require.paths.unshift(path.join(__dirname, '..', '..')); 3 | var connect = require('connect'); 4 | var app = connect.createServer(); 5 | var exposeRequire = require('browser-require'); 6 | app.use(exposeRequire({ 7 | base: __dirname 8 | })); 9 | app.use(connect.staticProvider(__dirname)); 10 | app.listen(1234); 11 | console.log("Run the example at http://127.0.0.1:1234/?v=" + (+new Date)); 12 | -------------------------------------------------------------------------------- /examples/relative/index.html: -------------------------------------------------------------------------------- 1 | browser-require relative module example
-------------------------------------------------------------------------------- /examples/relative/index.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | html 3 | head 4 | title browser-require relative module example 5 | body 6 | #answer 7 | script(type: 'text/javascript', src: 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js') 8 | script(type: 'text/javascript', src: '/js/app.js') 9 | -------------------------------------------------------------------------------- /examples/relative/js/above.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return "in the trees above"; 3 | }; 4 | -------------------------------------------------------------------------------- /examples/relative/js/app.js: -------------------------------------------------------------------------------- 1 | var rel = require('./rel'); 2 | 3 | $( function () { 4 | $('#answer').text(rel()); 5 | }); 6 | -------------------------------------------------------------------------------- /examples/relative/js/nested/nest.js: -------------------------------------------------------------------------------- 1 | var above = require('../above'); 2 | 3 | module.exports = function () { 4 | return "Bird's nest " + above(); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/relative/js/rel.js: -------------------------------------------------------------------------------- 1 | var nest = require('./nested/nest'); 2 | 3 | module.exports = function () { 4 | return "Hello World! with " + nest(); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/relative/server.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | require.paths.unshift(path.join(__dirname, '..', '..')); 3 | var connect = require('connect'); 4 | var app = connect.createServer(); 5 | var exposeRequire = require('browser-require'); 6 | app.use(exposeRequire({ 7 | base: __dirname 8 | })); 9 | app.use(connect.staticProvider(__dirname)); 10 | app.listen(1234); 11 | console.log("Run the example at http://127.0.0.1:1234/?v=" + (+new Date)); 12 | -------------------------------------------------------------------------------- /lib/browser-require.js: -------------------------------------------------------------------------------- 1 | // 1. Read the top file 2 | // 2. Parse out the dependency names 3 | // 3. Resolve the file locations based on name + file depending on it 4 | // 4. For each dependency, read it and go to (2). Rinse and repeat 5 | // until no more files to read 6 | // 5. Compile the top file and all dependencies into a single file 7 | 8 | var fs = require('fs') 9 | , path = require('path') 10 | , EventEmitter = require('events').EventEmitter 11 | , cache = {} 12 | , dependencyPromise = require('dependency-promise') 13 | , libdir = path.join(path.dirname(__filename)) 14 | , NpmModule = require('./npm_module'); 15 | 16 | function succeed (res, src) { 17 | res.writeHead(200, {'Content-Type': 'text/javascript'}); 18 | res.end(src); 19 | } 20 | 21 | function fail(res, err) { 22 | res.writeHead(404); 23 | res.end(); 24 | } 25 | var templates = { 26 | boilerplate: fs.readFileSync(libdir + '/templates/boilerplate.js', 'utf8') 27 | , injectModule: fs.readFileSync(libdir + '/templates/inject_module.js', 'utf8') 28 | } 29 | 30 | function ScriptPromise (name, location, npmBase) { 31 | EventEmitter.call(this); 32 | this.setMaxListeners(0); 33 | this.name = name; 34 | if (npmBase) { 35 | if (name.indexOf('/') === -1) 36 | this.nextRelTo = name.slice(0, -3); 37 | else 38 | this.nextRelTo = name.split('/').slice(0, -1).join('/'); 39 | this.npmBase = npmBase; 40 | } else { 41 | this.nextRelTo = path.dirname(name); 42 | } 43 | this.location = location; 44 | this.addListener('error', this.onError); 45 | } 46 | 47 | ScriptPromise.prototype.__proto__ = EventEmitter.prototype; 48 | for (var k in dependencyPromise) { 49 | ScriptPromise.prototype[k] = dependencyPromise[k]; 50 | } 51 | 52 | 53 | ScriptPromise.modules = {}; 54 | 55 | ScriptPromise.from = function (name, parent, fn) { 56 | var self = this; 57 | if (path.extname(name) === '.js') name = name.slice(0, -3); 58 | this.lookup( name, parent, function (err, normalized, location, npmBase) { 59 | var mod = self.modules[normalized] || (self.modules[normalized] = new ScriptPromise(normalized, location, npmBase)); 60 | if (err) return mod.emit('error', err); 61 | fn(mod); 62 | }); 63 | }; 64 | 65 | ScriptPromise.lookup = function (name, parent, fn) { 66 | var char0 = name.charAt(0); 67 | if (char0 === '.' || char0 === '/') { 68 | if (parent && parent.npmBase) { 69 | // In case we have e.g., 'some-npm-module.js' as parent.name 70 | // Then we don't want to pass along '.'. We want to pass along 71 | // 'some-npm-module' 72 | this.lookupRel(name, parent, fn); 73 | } else { 74 | this.lookupRel(name, parent, fn); 75 | } 76 | } else { 77 | this.lookupNpm(name, fn); 78 | } 79 | }; 80 | 81 | ScriptPromise.lookupRel = function (name, parent, fn) { 82 | var normDirect, normIndex 83 | , directLocation 84 | , parentdir 85 | , base 86 | , dir 87 | , self = this; 88 | if (name.charAt(0) === '/') name = '.' + name; 89 | if (!parent) { 90 | normDirect = name + '.js'; 91 | normIndex = name + '/index.js'; 92 | dir = this.base; 93 | } else { 94 | parentdir = parent.nextRelTo; 95 | normDirect = this.normalizeToParent(name + '.js', parentdir); 96 | normIndex = this.normalizeToParent(name + '/index.js', parentdir); 97 | dir = path.dirname(parent.location); 98 | } 99 | directLocation = path.join(dir, name + '.js'); 100 | path.exists(directLocation, function (doesExist) { 101 | if (doesExist) return fn(null, normDirect, directLocation, parent && parent.npmBase); 102 | var indexLocation = path.join(dir, name + '/index.js'); 103 | path.exists(indexLocation, function (doesExist) { 104 | if (doesExist) return fn(null, normIndex, indexLocation, parent && parent.npmBase); 105 | fn(new Error("module " + name + " seems to be missing" + 106 | (parent ? " relative to parent " + parent.name : ''))); 107 | }); 108 | }); 109 | }; 110 | 111 | ScriptPromise.normalizeToParent = function (name, parentdir) { 112 | if (parentdir === '/') parentdir = '.'; 113 | else if (parentdir.charAt(0) === '/') parentdir = '.' + parentdir; 114 | var parts = name.split('/') 115 | , dirparts = parentdir.split('/') 116 | , aboveBase = false; 117 | for (var i = 0, l = parts.length, part; i < l; i++) { 118 | part = parts[i]; 119 | if (part === '.') continue; 120 | else if (part === '..') { 121 | if (dirparts.length === 1 && dirparts[0] === '.') { 122 | dirparts.pop(); 123 | aboveBase = true; 124 | } 125 | if (!aboveBase) dirparts.pop(); 126 | else dirparts.push(part); 127 | } else dirparts.push(part); 128 | } 129 | return dirparts.join("/"); 130 | }; 131 | 132 | ScriptPromise.lookupNpm = function (name, fn) { 133 | var npmModule = new NpmModule(name); 134 | npmModule.isInstalled(function (err, isInstalled) { // Handle npm modules 135 | if (err) return fn(err); 136 | if (isInstalled) { 137 | npmModule.locate(function (err, location, isIndex) { 138 | if (err) return fn(err); 139 | var normalized = name + (isIndex ? '/index.js' : '.js'); 140 | fn(null, normalized, location, npmModule.dir); 141 | }); 142 | } else { 143 | err = new Error("Could not find " + npmModule.pkgName + 144 | ". Make sure it's installed via npm."); 145 | fn(err); 146 | } 147 | }); 148 | }; 149 | 150 | ScriptPromise.prototype.onError = function (err) { 151 | throw err; 152 | }; 153 | 154 | ScriptPromise.prototype.load = function () { 155 | var self = this; 156 | fs.readFile(this.location, 'utf8', function (err, src) { 157 | if (err) return self.emit('error', err); 158 | self.src = src; 159 | var depNames = self.depsFor(src); 160 | if (!depNames.length) 161 | return self.trigger('loaded', true); 162 | var deps = []; 163 | depNames.forEach( function (name) { 164 | ScriptPromise.from(name, self, function (script) { 165 | if (!script.isTriggered('loaded')) script.load(); 166 | deps.push(script); 167 | // TODO 168 | script.addListener('reloaded', function (src) { 169 | }); 170 | if (deps.length === depNames.length) { 171 | self.dependsOn('loaded', deps); 172 | } 173 | }); 174 | }); 175 | }); 176 | 177 | // For --watch 178 | // fs.watchFile(this.location, function (curr, prev) { 179 | // if (curr.mtime.getTime() > prev.mtime.getTime()) { 180 | // self.reload(); 181 | // } else { 182 | // throw new Error("Times are weird"); 183 | // } 184 | // }); 185 | } 186 | ScriptPromise.prototype.depsFor = function (src) { 187 | var re = /^[^(?:\*|\/)]*\s*require\(['"]([^'"]+)['"]\)/gm 188 | , match 189 | , deps = []; 190 | while (match = re.exec(src)) { 191 | deps.push(match[1]); 192 | } 193 | return deps; 194 | }; 195 | ScriptPromise.prototype.reload = function (src) { 196 | var self = this; 197 | fs.readFile(this.location, 'utf8', function (err, src) { 198 | if (err) return self.emit('error', err); 199 | self.src = src; 200 | // Check for dependency additions or removals 201 | var depNames = self.depsFor(src); 202 | // TODO 203 | 204 | // Notify anyone who depends on me 205 | self.emit('reloaded', src); 206 | }); 207 | }; 208 | 209 | function wrapDeps (script) { 210 | var src = {} 211 | , deps = script.dependenciesFor('loaded') 212 | , i = deps.length, dep; 213 | while (i--) { 214 | dep = deps[i]; 215 | if (src[dep.name]) continue; 216 | src[dep.name] = wrapScript(dep); 217 | var wrapped = wrapDeps(dep); 218 | for (var k in wrapped) { 219 | src[k] = wrapped[k]; 220 | } 221 | } 222 | return src; 223 | } 224 | 225 | function wrapScript (script) { 226 | return templates.injectModule 227 | .replace(/\$module/g, JSON.stringify(script.name)) 228 | .replace(/\$dir/g, JSON.stringify(script.nextRelTo)) 229 | .replace('$src', script.src); 230 | } 231 | 232 | exports = module.exports = function exposeRequire (opts) { 233 | ScriptPromise.base = opts.base; 234 | 235 | return function (req, res, next) { 236 | var url = req.url; 237 | if (cache[url]) return succeed(res, cache[url]); 238 | if ('.js' !== path.extname(url)) return next(); 239 | url = url.replace(/\.js$/, ''); 240 | 241 | ScriptPromise.from(url, null, function (script) { 242 | compileAll(script, function (compiled) { 243 | if (!compiled) return next(); 244 | 245 | cache[url] = compiled; 246 | succeed(res, compiled); 247 | }); 248 | }); 249 | }; 250 | }; 251 | 252 | var compileAll = exports.compileAll = function compileAll (script, fn) { 253 | script.on('loaded', function (noDeps) { 254 | if (noDeps) { 255 | return fn(script.src); 256 | } 257 | var compiled = [templates.boilerplate] 258 | , deps = wrapDeps(script); 259 | for (var k in deps) { 260 | compiled.push(deps[k]); 261 | } 262 | // TODO Make scope be window, not module.exports 263 | compiled.push(wrapScript(script)); 264 | compiled.push("browserRequire('" + script.name.slice(0, -3) + "');"); 265 | fn(compiled.join("\n")); 266 | }); 267 | script.load(); 268 | }; 269 | 270 | exports.ScriptPromise = ScriptPromise; 271 | -------------------------------------------------------------------------------- /lib/npm_module.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , path = require('path') 3 | , npm = require('npm'); 4 | 5 | function NpmModule (url) { 6 | var modulePath = url.replace(/\.js$/, '') 7 | , moduleChain = modulePath.split('/') 8 | , pkgName = this.pkgName = moduleChain[0] 9 | , relChain = this.relChain = moduleChain.slice(1); 10 | this.isSubmodule = !!relChain.length; 11 | var nv = pkgName.split('@') 12 | , n = this.name = nv[0] 13 | , v = this.ver = nv[1] || 'active'; 14 | } 15 | 16 | // Different situations: 17 | // requires from `main` 18 | // requires like 'module-name/sub' 19 | // relative requires (./etc) from a file in `lib` directory 20 | NpmModule.prototype = { 21 | __locate: function (fn) { 22 | if (this._location) return fn(null, this._location, this._isIndex); 23 | if (this._locationErr) return fn(this._locationErr); 24 | var self = this; 25 | if (this.isSubmodule) { 26 | this.relLocate(function (err, location, isIndex) { 27 | self._locationerr = err; 28 | self._location = location; 29 | self._isIndex = isIndex; 30 | fn(err, location, isIndex); 31 | }); 32 | } else { 33 | this.mainLocate(function (err, location) { 34 | self._locationErr = err; 35 | self._location = location; 36 | fn(err, location); 37 | }); 38 | } 39 | }, 40 | __relLocate: function (fn) { 41 | if (this._relLocation) return fn(null, this._relLocation); 42 | if (this._relLocationErr) return fn(this._relLocationErr); 43 | var self = this, err; 44 | this.pkgJson(function (err, pkg) { 45 | if (err) return fn(self._relLocationErr = err); 46 | var directories = pkg.directory || pkg.directories 47 | , lib = directories && directories.lib 48 | , chain = [self.dir], direct, index; 49 | if (lib) chain.push(lib); 50 | direct = path.join.apply(this, chain.concat([self.relChain.join('/') + '.js']) ); 51 | index = path.join.apply(this, chain.concat([self.relChain.join('/'), 'index.js']) ); 52 | if (path.existsSync(direct)) { 53 | fn(null, direct, false); 54 | } else if (path.existsSync(index)) { 55 | fn(null, index, true); 56 | } else { 57 | if (lib) { 58 | err = self._relLocationErr = 59 | new Error('Unimplemented - could not find package ' + 60 | self.relChain.join('/') + ' relative to ' + 61 | path.join.apply(path, chain)); 62 | } else { 63 | err = self._relLocationErr = 64 | new Error('Missing ' + self.relChain.join('/') + 65 | ' in ' + self.pkgName + ' package'); 66 | } 67 | fn(err); 68 | } 69 | }); 70 | }, 71 | __mainLocate: function (fn) { 72 | if (this._mainLocation) return fn(null, this._mainLocation); 73 | if (this._mainLocateErr) return fn(this._mainLocateErr); 74 | var self = this; 75 | this.pkgJson( function (err, pkg) { 76 | if (err) return fn(self._mainLocateErr = err); 77 | if (pkg.main) { 78 | pkg.main = (path.extname(pkg.main) === '.js') 79 | ? pkg.main.slice(0, -3) 80 | : pkg.main; 81 | fs.stat(self.dir + '/' + pkg.main, function (err, stat) { 82 | if (err) { 83 | path.exists(self.dir + '/' + pkg.main + '.js', function (exists) { 84 | self._mainLocation = self.dir + '/' + pkg.main + '.js'; 85 | fn(null, self._mainLocation); 86 | }); 87 | } else if (stat.isDirectory()) { 88 | self._mainLocation = self.dir + '/' + pkg.main + '/index.js'; 89 | fn(null, self._mainLocation); 90 | } 91 | }); 92 | } else { 93 | fn(self._mainLocateErr = "Missing main in package.json for " + self.pkgName); 94 | } 95 | }); 96 | }, 97 | __pkgJson: function (fn) { 98 | if (this._pkg) return fn(null, this._pkg); 99 | if (this._pkgerr) return fn(this._pkgerr); 100 | var self = this; 101 | fs.readFile(this.dir + '/package.json', 'utf8', function (err, body) { 102 | if (err) { 103 | self._pkgerr = err = 104 | new Error("package.json missing for " + self.pkgName); 105 | return fn(err); 106 | } 107 | var pkg = self._pkg = JSON.parse(body); 108 | fn(null, pkg); 109 | }); 110 | }, 111 | __isInstalled: function (fn) { 112 | var self = this; 113 | fs.stat(this.dir, function (err, stat) { 114 | if (err || !stat.isDirectory()) { 115 | err = new Error(self.name + " is not installed via npm."); 116 | fn(err, false); 117 | } else { 118 | fn(null, true); 119 | } 120 | }); 121 | } 122 | }; 123 | 124 | Object.defineProperty(NpmModule.prototype, 'dir', { 125 | get: function () { 126 | return path.join(npm.dir, this.name, this.ver, 'package'); 127 | } 128 | }); 129 | 130 | // Wrap npm in a promise, for manageable async middleware 131 | NpmModule.isNpmLoaded = false; 132 | NpmModule.callbacks = []; // Callbacks for loaded event 133 | var proto = NpmModule.prototype; 134 | for (var k in proto) { 135 | proto[k.slice(2)] = (function (k) { 136 | return function () { 137 | if (NpmModule.isNpmLoaded) return proto[k].apply(this, arguments); 138 | NpmModule.callbacks.push([proto[k], this, arguments]); 139 | }; 140 | })(k); 141 | } 142 | npm.load( function () { 143 | NpmModule.isNpmLoaded = true; 144 | var callbacks = NpmModule.callbacks; 145 | for (var i = 0, l = callbacks.length; i < l; i++) { 146 | callbacks[i][0].apply(callbacks[i][1], callbacks[i][2]); 147 | } 148 | }); 149 | 150 | module.exports = NpmModule; 151 | -------------------------------------------------------------------------------- /lib/templates/boilerplate.js: -------------------------------------------------------------------------------- 1 | function browserRequire (path) { 2 | var mod = browserRequire.modules[path + '.js'] || 3 | browserRequire.modules[path + '/index.js']; 4 | if (!mod) throw new Error("Missing module " + path); 5 | return mod.cached || mod(); 6 | } 7 | browserRequire.modules = {}; 8 | -------------------------------------------------------------------------------- /lib/templates/inject_module.js: -------------------------------------------------------------------------------- 1 | browserRequire.modules[$module] = function () { 2 | var module = {} 3 | , exports = module.exports = {} 4 | , require = function (module) { 5 | if (module.charAt(0) !== '.' && module.charAt(0) !== '/') 6 | return browserRequire(module); 7 | var dir = ($dir === '/' ? '.' : $dir) 8 | , parts = module.split('/') 9 | , dirparts = dir.split('/') 10 | , aboveBase = false; 11 | for (var i = 0, l = parts.length, part; i < l; i++) { 12 | part = parts[i]; 13 | if (part === '.') continue; 14 | else if (part === '..') { 15 | if (dirparts.length === 1) { 16 | dirparts.pop(); 17 | aboveBase = true; 18 | } 19 | if (!aboveBase) dirparts.pop(); 20 | else dirparts.push(part); 21 | } else dirparts.push(part); 22 | } 23 | return browserRequire(dirparts.join('/')); 24 | }; 25 | 26 | (function () { 27 | $src 28 | }).call(module.exports); 29 | 30 | return browserRequire.modules[$module].cached = module.exports; 31 | }; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser-require", 3 | "description": "Use CommonJS and NPM modules from the browser", 4 | "version": "0.1.6", 5 | "homepage": "https://github.com/bnoguchi/browser-require", 6 | "repository": "https://github.com/bnoguchi/browser-require.git", 7 | "author": "Brian N Noguchi (https://github.com/bnoguchi)", 8 | "main": "./lib/browser-require.js", 9 | "directories": { 10 | "lib": "" 11 | }, 12 | "bin": { 13 | "browser-require": "./bin/browser-require" 14 | }, 15 | "scripts": { 16 | "test": "make test" 17 | }, 18 | "devDependencies": { 19 | "expresso": ">= 0.7.2" 20 | }, 21 | "dependencies": { 22 | "dependency-promise": ">=0.2.0" 23 | }, 24 | "engines": { 25 | "node": ">=0.2.6" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/compiled/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QUnit Test Suite 5 | 6 | 7 | 8 | 9 | 10 |

QUnit Test Suite

11 |

12 |
13 |

14 |
    15 | 16 | 17 | -------------------------------------------------------------------------------- /test/compiled/js/above.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return "I'm an ancestor of the descendant"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/compiled/js/level1.js: -------------------------------------------------------------------------------- 1 | var level3 = require('./level2/level3'); 2 | 3 | module.exports = function () { 4 | return "level 1 => " + level3(); 5 | }; 6 | -------------------------------------------------------------------------------- /test/compiled/js/level2.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return "level 2"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/compiled/js/level2/level3.js: -------------------------------------------------------------------------------- 1 | var level2 = require('../level2'); 2 | 3 | module.exports = function () { 4 | return "level 3 => " + level2(); 5 | }; 6 | -------------------------------------------------------------------------------- /test/compiled/js/nested/aboveNested.js: -------------------------------------------------------------------------------- 1 | var above = require('../above'); 2 | module.exports = function () { 3 | return above(); 4 | }; 5 | -------------------------------------------------------------------------------- /test/compiled/js/nested/nest.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return "I'm a descendant"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/compiled/js/rel.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return "Hello World!"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/compiled/js/test.js: -------------------------------------------------------------------------------- 1 | var rel = require('./rel') 2 | , nested = require('./nested/nest') 3 | , aboveNested = require('./nested/aboveNested') 4 | , elevator = require('./level1') 5 | , hell = require('../../hell'); 6 | 7 | window.module('require relatives'); 8 | asyncTest('relative sibling requires', function () { 9 | equals(rel(), 'Hello World!'); 10 | start(); 11 | }); 12 | asyncTest('relative nested requires', function () { 13 | equals(nested(), "I'm a descendant"); 14 | start(); 15 | }); 16 | asyncTest('relative above nested requires', function () { 17 | equals(aboveNested(), "I'm an ancestor of the descendant"); 18 | start(); 19 | }); 20 | asyncTest('elevator requires', function () { 21 | equals(elevator(), "level 1 => level 3 => level 2"); 22 | start(); 23 | }); 24 | asyncTest('level before base dir require', function () { 25 | equals(hell(), "PYRO"); 26 | start(); 27 | }); 28 | 29 | window.module('require npm'); 30 | asyncTest('simple npm modules', function () { 31 | var _ = require('underscore'); 32 | equals(10, _.reduce([1, 2, 3, 4], function (sum, num) { 33 | sum += num; 34 | return sum; 35 | })); 36 | start(); 37 | }); 38 | asyncTest('npm submodules', function () { 39 | var Set = require('data-structures-js/set') 40 | , s = new Set(['look', 'ma', 'no', 'hands']); 41 | s.add('look'); 42 | ok(s.contains('ma')); 43 | start(); 44 | }); 45 | asyncTest('npm modules that use relative modules', function () { 46 | // The following file requires ./entities; 47 | // See https://github.com/chriso/node-validator/blob/master/lib/xss.js 48 | var clean = require('validator/xss').clean; 49 | equals('function', typeof clean); 50 | start(); 51 | }); 52 | -------------------------------------------------------------------------------- /test/compiled/server.js: -------------------------------------------------------------------------------- 1 | require.paths.unshift('..'); 2 | var path = require('path'); 3 | require.paths.unshift(path.join(__dirname, '..', '..')); 4 | var connect = require('connect'); 5 | var app = connect.createServer(); 6 | var exposeRequire = require('browser-require'); 7 | app.use(exposeRequire({ 8 | base: __dirname 9 | , compiled: true 10 | })); 11 | app.use(connect.staticProvider(__dirname)); 12 | app.listen(4321); 13 | console.log("Server running at http://127.0.0.1:4321"); 14 | process.title = "brtest"; 15 | -------------------------------------------------------------------------------- /test/hell.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return "PYRO"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/nested/css/qunit.css: -------------------------------------------------------------------------------- 1 | /** Font Family and Sizes */ 2 | 3 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 4 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 5 | } 6 | 7 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 8 | #qunit-tests { font-size: smaller; } 9 | 10 | 11 | /** Resets */ 12 | 13 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | 19 | /** Header */ 20 | 21 | #qunit-header { 22 | padding: 0.5em 0 0.5em 1em; 23 | 24 | color: #8699a4; 25 | background-color: #0d3349; 26 | 27 | font-size: 1.5em; 28 | line-height: 1em; 29 | font-weight: normal; 30 | 31 | border-radius: 15px 15px 0 0; 32 | -moz-border-radius: 15px 15px 0 0; 33 | -webkit-border-top-right-radius: 15px; 34 | -webkit-border-top-left-radius: 15px; 35 | } 36 | 37 | #qunit-header a { 38 | text-decoration: none; 39 | color: #c2ccd1; 40 | } 41 | 42 | #qunit-header a:hover, 43 | #qunit-header a:focus { 44 | color: #fff; 45 | } 46 | 47 | #qunit-banner { 48 | height: 5px; 49 | } 50 | 51 | #qunit-testrunner-toolbar { 52 | padding: 0.5em 0 0.5em 2em; 53 | color: #5E740B; 54 | background-color: #eee; 55 | } 56 | 57 | #qunit-userAgent { 58 | padding: 0.5em 0 0.5em 2.5em; 59 | background-color: #2b81af; 60 | color: #fff; 61 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 62 | } 63 | 64 | 65 | /** Tests: Pass/Fail */ 66 | 67 | #qunit-tests { 68 | list-style-position: inside; 69 | } 70 | 71 | #qunit-tests li { 72 | padding: 0.4em 0.5em 0.4em 2.5em; 73 | border-bottom: 1px solid #fff; 74 | list-style-position: inside; 75 | } 76 | 77 | #qunit-tests.hidepass li.pass { 78 | display: none; 79 | } 80 | 81 | #qunit-tests li strong { 82 | cursor: pointer; 83 | } 84 | 85 | #qunit-tests ol { 86 | margin-top: 0.5em; 87 | padding: 0.5em; 88 | 89 | background-color: #fff; 90 | 91 | border-radius: 15px; 92 | -moz-border-radius: 15px; 93 | -webkit-border-radius: 15px; 94 | 95 | box-shadow: inset 0px 2px 13px #999; 96 | -moz-box-shadow: inset 0px 2px 13px #999; 97 | -webkit-box-shadow: inset 0px 2px 13px #999; 98 | } 99 | 100 | #qunit-tests table { 101 | border-collapse: collapse; 102 | margin-top: .2em; 103 | } 104 | 105 | #qunit-tests th { 106 | text-align: right; 107 | vertical-align: top; 108 | padding: 0 .5em 0 0; 109 | } 110 | 111 | #qunit-tests td { 112 | vertical-align: top; 113 | } 114 | 115 | #qunit-tests pre { 116 | margin: 0; 117 | white-space: pre-wrap; 118 | word-wrap: break-word; 119 | } 120 | 121 | #qunit-tests del { 122 | background-color: #e0f2be; 123 | color: #374e0c; 124 | text-decoration: none; 125 | } 126 | 127 | #qunit-tests ins { 128 | background-color: #ffcaca; 129 | color: #500; 130 | text-decoration: none; 131 | } 132 | 133 | /*** Test Counts */ 134 | 135 | #qunit-tests b.counts { color: black; } 136 | #qunit-tests b.passed { color: #5E740B; } 137 | #qunit-tests b.failed { color: #710909; } 138 | 139 | #qunit-tests li li { 140 | margin: 0.5em; 141 | padding: 0.4em 0.5em 0.4em 0.5em; 142 | background-color: #fff; 143 | border-bottom: none; 144 | list-style-position: inside; 145 | } 146 | 147 | /*** Passing Styles */ 148 | 149 | #qunit-tests li li.pass { 150 | color: #5E740B; 151 | background-color: #fff; 152 | border-left: 26px solid #C6E746; 153 | } 154 | 155 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 156 | #qunit-tests .pass .test-name { color: #366097; } 157 | 158 | #qunit-tests .pass .test-actual, 159 | #qunit-tests .pass .test-expected { color: #999999; } 160 | 161 | #qunit-banner.qunit-pass { background-color: #C6E746; } 162 | 163 | /*** Failing Styles */ 164 | 165 | #qunit-tests li li.fail { 166 | color: #710909; 167 | background-color: #fff; 168 | border-left: 26px solid #EE5757; 169 | } 170 | 171 | #qunit-tests > li:last-child { 172 | border-radius: 0 0 15px 15px; 173 | -moz-border-radius: 0 0 15px 15px; 174 | -webkit-border-bottom-right-radius: 15px; 175 | -webkit-border-bottom-left-radius: 15px; 176 | } 177 | 178 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 179 | #qunit-tests .fail .test-name, 180 | #qunit-tests .fail .module-name { color: #000000; } 181 | 182 | #qunit-tests .fail .test-actual { color: #EE5757; } 183 | #qunit-tests .fail .test-expected { color: green; } 184 | 185 | #qunit-banner.qunit-fail { background-color: #EE5757; } 186 | 187 | 188 | /** Result */ 189 | 190 | #qunit-testresult { 191 | padding: 0.5em 0.5em 0.5em 2.5em; 192 | 193 | color: #2b81af; 194 | background-color: #D2E0E6; 195 | 196 | border-bottom: 1px solid white; 197 | } 198 | 199 | /** Fixture */ 200 | 201 | #qunit-fixture { 202 | position: absolute; 203 | top: -10000px; 204 | left: -10000px; 205 | } 206 | -------------------------------------------------------------------------------- /test/nested/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QUnit Test Suite 5 | 6 | 7 | 8 | 9 | 10 |

    QUnit Test Suite

    11 |

    12 |
    13 |

    14 |
      15 | 16 | 17 | -------------------------------------------------------------------------------- /test/nested/js/above.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return "I'm an ancestor of the descendant"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/nested/js/level1.js: -------------------------------------------------------------------------------- 1 | var level3 = require('./level2/level3'); 2 | 3 | module.exports = function () { 4 | return "level 1 => " + level3(); 5 | }; 6 | -------------------------------------------------------------------------------- /test/nested/js/level2.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return "level 2"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/nested/js/level2/level3.js: -------------------------------------------------------------------------------- 1 | var level2 = require('../level2'); 2 | 3 | module.exports = function () { 4 | return "level 3 => " + level2(); 5 | }; 6 | -------------------------------------------------------------------------------- /test/nested/js/nested/aboveNested.js: -------------------------------------------------------------------------------- 1 | var above = require('../above'); 2 | module.exports = function () { 3 | return above(); 4 | }; 5 | -------------------------------------------------------------------------------- /test/nested/js/nested/nest.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return "I'm a descendant"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/nested/js/qunit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function(window) { 12 | 13 | var defined = { 14 | setTimeout: typeof window.setTimeout !== "undefined", 15 | sessionStorage: (function() { 16 | try { 17 | return !!sessionStorage.getItem; 18 | } catch(e){ 19 | return false; 20 | } 21 | })() 22 | } 23 | 24 | var testId = 0; 25 | 26 | var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { 27 | this.name = name; 28 | this.testName = testName; 29 | this.expected = expected; 30 | this.testEnvironmentArg = testEnvironmentArg; 31 | this.async = async; 32 | this.callback = callback; 33 | this.assertions = []; 34 | }; 35 | Test.prototype = { 36 | init: function() { 37 | var tests = id("qunit-tests"); 38 | if (tests) { 39 | var b = document.createElement("strong"); 40 | b.innerHTML = "Running " + this.name; 41 | var li = document.createElement("li"); 42 | li.appendChild( b ); 43 | li.id = this.id = "test-output" + testId++; 44 | tests.appendChild( li ); 45 | } 46 | }, 47 | setup: function() { 48 | if (this.module != config.previousModule) { 49 | if ( config.previousModule ) { 50 | QUnit.moduleDone( { 51 | name: config.previousModule, 52 | failed: config.moduleStats.bad, 53 | passed: config.moduleStats.all - config.moduleStats.bad, 54 | total: config.moduleStats.all 55 | } ); 56 | } 57 | config.previousModule = this.module; 58 | config.moduleStats = { all: 0, bad: 0 }; 59 | QUnit.moduleStart( { 60 | name: this.module 61 | } ); 62 | } 63 | 64 | config.current = this; 65 | this.testEnvironment = extend({ 66 | setup: function() {}, 67 | teardown: function() {} 68 | }, this.moduleTestEnvironment); 69 | if (this.testEnvironmentArg) { 70 | extend(this.testEnvironment, this.testEnvironmentArg); 71 | } 72 | 73 | QUnit.testStart( { 74 | name: this.testName 75 | } ); 76 | 77 | // allow utility functions to access the current test environment 78 | // TODO why?? 79 | QUnit.current_testEnvironment = this.testEnvironment; 80 | 81 | try { 82 | if ( !config.pollution ) { 83 | saveGlobal(); 84 | } 85 | 86 | this.testEnvironment.setup.call(this.testEnvironment); 87 | } catch(e) { 88 | QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); 89 | } 90 | }, 91 | run: function() { 92 | if ( this.async ) { 93 | QUnit.stop(); 94 | } 95 | 96 | if ( config.notrycatch ) { 97 | this.callback.call(this.testEnvironment); 98 | return; 99 | } 100 | try { 101 | this.callback.call(this.testEnvironment); 102 | } catch(e) { 103 | fail("Test " + this.testName + " died, exception and test follows", e, this.callback); 104 | QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); 105 | // else next test will carry the responsibility 106 | saveGlobal(); 107 | 108 | // Restart the tests if they're blocking 109 | if ( config.blocking ) { 110 | start(); 111 | } 112 | } 113 | }, 114 | teardown: function() { 115 | try { 116 | checkPollution(); 117 | this.testEnvironment.teardown.call(this.testEnvironment); 118 | } catch(e) { 119 | QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); 120 | } 121 | }, 122 | finish: function() { 123 | if ( this.expected && this.expected != this.assertions.length ) { 124 | QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); 125 | } 126 | 127 | var good = 0, bad = 0, 128 | tests = id("qunit-tests"); 129 | 130 | config.stats.all += this.assertions.length; 131 | config.moduleStats.all += this.assertions.length; 132 | 133 | if ( tests ) { 134 | var ol = document.createElement("ol"); 135 | 136 | for ( var i = 0; i < this.assertions.length; i++ ) { 137 | var assertion = this.assertions[i]; 138 | 139 | var li = document.createElement("li"); 140 | li.className = assertion.result ? "pass" : "fail"; 141 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); 142 | ol.appendChild( li ); 143 | 144 | if ( assertion.result ) { 145 | good++; 146 | } else { 147 | bad++; 148 | config.stats.bad++; 149 | config.moduleStats.bad++; 150 | } 151 | } 152 | 153 | // store result when possible 154 | defined.sessionStorage && sessionStorage.setItem("qunit-" + this.testName, bad); 155 | 156 | if (bad == 0) { 157 | ol.style.display = "none"; 158 | } 159 | 160 | var b = document.createElement("strong"); 161 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 162 | 163 | addEvent(b, "click", function() { 164 | var next = b.nextSibling, display = next.style.display; 165 | next.style.display = display === "none" ? "block" : "none"; 166 | }); 167 | 168 | addEvent(b, "dblclick", function(e) { 169 | var target = e && e.target ? e.target : window.event.srcElement; 170 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 171 | target = target.parentNode; 172 | } 173 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 174 | window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, "")); 175 | } 176 | }); 177 | 178 | var li = id(this.id); 179 | li.className = bad ? "fail" : "pass"; 180 | li.removeChild( li.firstChild ); 181 | li.appendChild( b ); 182 | li.appendChild( ol ); 183 | 184 | } else { 185 | for ( var i = 0; i < this.assertions.length; i++ ) { 186 | if ( !this.assertions[i].result ) { 187 | bad++; 188 | config.stats.bad++; 189 | config.moduleStats.bad++; 190 | } 191 | } 192 | } 193 | 194 | try { 195 | QUnit.reset(); 196 | } catch(e) { 197 | fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); 198 | } 199 | 200 | QUnit.testDone( { 201 | name: this.testName, 202 | failed: bad, 203 | passed: this.assertions.length - bad, 204 | total: this.assertions.length 205 | } ); 206 | }, 207 | 208 | queue: function() { 209 | var test = this; 210 | synchronize(function() { 211 | test.init(); 212 | }); 213 | function run() { 214 | // each of these can by async 215 | synchronize(function() { 216 | test.setup(); 217 | }); 218 | synchronize(function() { 219 | test.run(); 220 | }); 221 | synchronize(function() { 222 | test.teardown(); 223 | }); 224 | synchronize(function() { 225 | test.finish(); 226 | }); 227 | } 228 | // defer when previous test run passed, if storage is available 229 | var bad = defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.testName); 230 | if (bad) { 231 | run(); 232 | } else { 233 | synchronize(run); 234 | }; 235 | } 236 | 237 | } 238 | 239 | var QUnit = { 240 | 241 | // call on start of module test to prepend name to all tests 242 | module: function(name, testEnvironment) { 243 | config.currentModule = name; 244 | config.currentModuleTestEnviroment = testEnvironment; 245 | }, 246 | 247 | asyncTest: function(testName, expected, callback) { 248 | if ( arguments.length === 2 ) { 249 | callback = expected; 250 | expected = 0; 251 | } 252 | 253 | QUnit.test(testName, expected, callback, true); 254 | }, 255 | 256 | test: function(testName, expected, callback, async) { 257 | var name = '' + testName + '', testEnvironmentArg; 258 | 259 | if ( arguments.length === 2 ) { 260 | callback = expected; 261 | expected = null; 262 | } 263 | // is 2nd argument a testEnvironment? 264 | if ( expected && typeof expected === 'object') { 265 | testEnvironmentArg = expected; 266 | expected = null; 267 | } 268 | 269 | if ( config.currentModule ) { 270 | name = '' + config.currentModule + ": " + name; 271 | } 272 | 273 | if ( !validTest(config.currentModule + ": " + testName) ) { 274 | return; 275 | } 276 | 277 | var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); 278 | test.module = config.currentModule; 279 | test.moduleTestEnvironment = config.currentModuleTestEnviroment; 280 | test.queue(); 281 | }, 282 | 283 | /** 284 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 285 | */ 286 | expect: function(asserts) { 287 | config.current.expected = asserts; 288 | }, 289 | 290 | /** 291 | * Asserts true. 292 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 293 | */ 294 | ok: function(a, msg) { 295 | a = !!a; 296 | var details = { 297 | result: a, 298 | message: msg 299 | }; 300 | msg = escapeHtml(msg); 301 | QUnit.log(details); 302 | config.current.assertions.push({ 303 | result: a, 304 | message: msg 305 | }); 306 | }, 307 | 308 | /** 309 | * Checks that the first two arguments are equal, with an optional message. 310 | * Prints out both actual and expected values. 311 | * 312 | * Prefered to ok( actual == expected, message ) 313 | * 314 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 315 | * 316 | * @param Object actual 317 | * @param Object expected 318 | * @param String message (optional) 319 | */ 320 | equal: function(actual, expected, message) { 321 | QUnit.push(expected == actual, actual, expected, message); 322 | }, 323 | 324 | notEqual: function(actual, expected, message) { 325 | QUnit.push(expected != actual, actual, expected, message); 326 | }, 327 | 328 | deepEqual: function(actual, expected, message) { 329 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); 330 | }, 331 | 332 | notDeepEqual: function(actual, expected, message) { 333 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); 334 | }, 335 | 336 | strictEqual: function(actual, expected, message) { 337 | QUnit.push(expected === actual, actual, expected, message); 338 | }, 339 | 340 | notStrictEqual: function(actual, expected, message) { 341 | QUnit.push(expected !== actual, actual, expected, message); 342 | }, 343 | 344 | raises: function(block, expected, message) { 345 | var actual, ok = false; 346 | 347 | if (typeof expected === 'string') { 348 | message = expected; 349 | expected = null; 350 | } 351 | 352 | try { 353 | block(); 354 | } catch (e) { 355 | actual = e; 356 | } 357 | 358 | if (actual) { 359 | // we don't want to validate thrown error 360 | if (!expected) { 361 | ok = true; 362 | // expected is a regexp 363 | } else if (QUnit.objectType(expected) === "regexp") { 364 | ok = expected.test(actual); 365 | // expected is a constructor 366 | } else if (actual instanceof expected) { 367 | ok = true; 368 | // expected is a validation function which returns true is validation passed 369 | } else if (expected.call({}, actual) === true) { 370 | ok = true; 371 | } 372 | } 373 | 374 | QUnit.ok(ok, message); 375 | }, 376 | 377 | start: function() { 378 | config.semaphore--; 379 | if (config.semaphore > 0) { 380 | // don't start until equal number of stop-calls 381 | return; 382 | } 383 | if (config.semaphore < 0) { 384 | // ignore if start is called more often then stop 385 | config.semaphore = 0; 386 | } 387 | // A slight delay, to avoid any current callbacks 388 | if ( defined.setTimeout ) { 389 | window.setTimeout(function() { 390 | if ( config.timeout ) { 391 | clearTimeout(config.timeout); 392 | } 393 | 394 | config.blocking = false; 395 | process(); 396 | }, 13); 397 | } else { 398 | config.blocking = false; 399 | process(); 400 | } 401 | }, 402 | 403 | stop: function(timeout) { 404 | config.semaphore++; 405 | config.blocking = true; 406 | 407 | if ( timeout && defined.setTimeout ) { 408 | clearTimeout(config.timeout); 409 | config.timeout = window.setTimeout(function() { 410 | QUnit.ok( false, "Test timed out" ); 411 | QUnit.start(); 412 | }, timeout); 413 | } 414 | } 415 | 416 | }; 417 | 418 | // Backwards compatibility, deprecated 419 | QUnit.equals = QUnit.equal; 420 | QUnit.same = QUnit.deepEqual; 421 | 422 | // Maintain internal state 423 | var config = { 424 | // The queue of tests to run 425 | queue: [], 426 | 427 | // block until document ready 428 | blocking: true 429 | }; 430 | 431 | // Load paramaters 432 | (function() { 433 | var location = window.location || { search: "", protocol: "file:" }, 434 | GETParams = location.search.slice(1).split('&'); 435 | 436 | for ( var i = 0; i < GETParams.length; i++ ) { 437 | GETParams[i] = decodeURIComponent( GETParams[i] ); 438 | if ( GETParams[i] === "noglobals" ) { 439 | GETParams.splice( i, 1 ); 440 | i--; 441 | config.noglobals = true; 442 | } else if ( GETParams[i] === "notrycatch" ) { 443 | GETParams.splice( i, 1 ); 444 | i--; 445 | config.notrycatch = true; 446 | } else if ( GETParams[i].search('=') > -1 ) { 447 | GETParams.splice( i, 1 ); 448 | i--; 449 | } 450 | } 451 | 452 | // restrict modules/tests by get parameters 453 | config.filters = GETParams; 454 | 455 | // Figure out if we're running the tests from a server or not 456 | QUnit.isLocal = !!(location.protocol === 'file:'); 457 | })(); 458 | 459 | // Expose the API as global variables, unless an 'exports' 460 | // object exists, in that case we assume we're in CommonJS 461 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 462 | extend(window, QUnit); 463 | window.QUnit = QUnit; 464 | } else { 465 | extend(exports, QUnit); 466 | exports.QUnit = QUnit; 467 | } 468 | 469 | // define these after exposing globals to keep them in these QUnit namespace only 470 | extend(QUnit, { 471 | config: config, 472 | 473 | // Initialize the configuration options 474 | init: function() { 475 | extend(config, { 476 | stats: { all: 0, bad: 0 }, 477 | moduleStats: { all: 0, bad: 0 }, 478 | started: +new Date, 479 | updateRate: 1000, 480 | blocking: false, 481 | autostart: true, 482 | autorun: false, 483 | filters: [], 484 | queue: [], 485 | semaphore: 0 486 | }); 487 | 488 | var tests = id( "qunit-tests" ), 489 | banner = id( "qunit-banner" ), 490 | result = id( "qunit-testresult" ); 491 | 492 | if ( tests ) { 493 | tests.innerHTML = ""; 494 | } 495 | 496 | if ( banner ) { 497 | banner.className = ""; 498 | } 499 | 500 | if ( result ) { 501 | result.parentNode.removeChild( result ); 502 | } 503 | 504 | if ( tests ) { 505 | result = document.createElement( "p" ); 506 | result.id = "qunit-testresult"; 507 | result.className = "result"; 508 | tests.parentNode.insertBefore( result, tests ); 509 | result.innerHTML = 'Running...
       '; 510 | } 511 | }, 512 | 513 | /** 514 | * Resets the test setup. Useful for tests that modify the DOM. 515 | * 516 | * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. 517 | */ 518 | reset: function() { 519 | if ( window.jQuery ) { 520 | jQuery( "#main, #qunit-fixture" ).html( config.fixture ); 521 | } else { 522 | var main = id( 'main' ) || id( 'qunit-fixture' ); 523 | if ( main ) { 524 | main.innerHTML = config.fixture; 525 | } 526 | } 527 | }, 528 | 529 | /** 530 | * Trigger an event on an element. 531 | * 532 | * @example triggerEvent( document.body, "click" ); 533 | * 534 | * @param DOMElement elem 535 | * @param String type 536 | */ 537 | triggerEvent: function( elem, type, event ) { 538 | if ( document.createEvent ) { 539 | event = document.createEvent("MouseEvents"); 540 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 541 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 542 | elem.dispatchEvent( event ); 543 | 544 | } else if ( elem.fireEvent ) { 545 | elem.fireEvent("on"+type); 546 | } 547 | }, 548 | 549 | // Safe object type checking 550 | is: function( type, obj ) { 551 | return QUnit.objectType( obj ) == type; 552 | }, 553 | 554 | objectType: function( obj ) { 555 | if (typeof obj === "undefined") { 556 | return "undefined"; 557 | 558 | // consider: typeof null === object 559 | } 560 | if (obj === null) { 561 | return "null"; 562 | } 563 | 564 | var type = Object.prototype.toString.call( obj ) 565 | .match(/^\[object\s(.*)\]$/)[1] || ''; 566 | 567 | switch (type) { 568 | case 'Number': 569 | if (isNaN(obj)) { 570 | return "nan"; 571 | } else { 572 | return "number"; 573 | } 574 | case 'String': 575 | case 'Boolean': 576 | case 'Array': 577 | case 'Date': 578 | case 'RegExp': 579 | case 'Function': 580 | return type.toLowerCase(); 581 | } 582 | if (typeof obj === "object") { 583 | return "object"; 584 | } 585 | return undefined; 586 | }, 587 | 588 | push: function(result, actual, expected, message) { 589 | var details = { 590 | result: result, 591 | message: message, 592 | actual: actual, 593 | expected: expected 594 | }; 595 | 596 | message = escapeHtml(message) || (result ? "okay" : "failed"); 597 | message = '' + message + ""; 598 | expected = escapeHtml(QUnit.jsDump.parse(expected)); 599 | actual = escapeHtml(QUnit.jsDump.parse(actual)); 600 | var output = message + ''; 601 | if (actual != expected) { 602 | output += ''; 603 | output += ''; 604 | } 605 | if (!result) { 606 | var source = sourceFromStacktrace(); 607 | if (source) { 608 | details.source = source; 609 | output += ''; 610 | } 611 | } 612 | output += "
      Expected:
      ' + expected + '
      Result:
      ' + actual + '
      Diff:
      ' + QUnit.diff(expected, actual) +'
      Source:
      ' + source +'
      "; 613 | 614 | QUnit.log(details); 615 | 616 | config.current.assertions.push({ 617 | result: !!result, 618 | message: output 619 | }); 620 | }, 621 | 622 | // Logging callbacks; all receive a single argument with the listed properties 623 | // run test/logs.html for any related changes 624 | begin: function() {}, 625 | // done: { failed, passed, total, runtime } 626 | done: function() {}, 627 | // log: { result, actual, expected, message } 628 | log: function() {}, 629 | // testStart: { name } 630 | testStart: function() {}, 631 | // testDone: { name, failed, passed, total } 632 | testDone: function() {}, 633 | // moduleStart: { name } 634 | moduleStart: function() {}, 635 | // moduleDone: { name, failed, passed, total } 636 | moduleDone: function() {} 637 | }); 638 | 639 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 640 | config.autorun = true; 641 | } 642 | 643 | addEvent(window, "load", function() { 644 | QUnit.begin({}); 645 | 646 | // Initialize the config, saving the execution queue 647 | var oldconfig = extend({}, config); 648 | QUnit.init(); 649 | extend(config, oldconfig); 650 | 651 | config.blocking = false; 652 | 653 | var userAgent = id("qunit-userAgent"); 654 | if ( userAgent ) { 655 | userAgent.innerHTML = navigator.userAgent; 656 | } 657 | var banner = id("qunit-header"); 658 | if ( banner ) { 659 | var paramsIndex = location.href.lastIndexOf(location.search); 660 | if ( paramsIndex > -1 ) { 661 | var mainPageLocation = location.href.slice(0, paramsIndex); 662 | if ( mainPageLocation == location.href ) { 663 | banner.innerHTML = ' ' + banner.innerHTML + ' '; 664 | } else { 665 | var testName = decodeURIComponent(location.search.slice(1)); 666 | banner.innerHTML = '' + banner.innerHTML + '' + testName + ''; 667 | } 668 | } 669 | } 670 | 671 | var toolbar = id("qunit-testrunner-toolbar"); 672 | if ( toolbar ) { 673 | var filter = document.createElement("input"); 674 | filter.type = "checkbox"; 675 | filter.id = "qunit-filter-pass"; 676 | addEvent( filter, "click", function() { 677 | var ol = document.getElementById("qunit-tests"); 678 | if ( filter.checked ) { 679 | ol.className = ol.className + " hidepass"; 680 | } else { 681 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 682 | ol.className = tmp.replace(/ hidepass /, " "); 683 | } 684 | if ( defined.sessionStorage ) { 685 | sessionStorage.setItem("qunit-filter-passed-tests", filter.checked ? "true" : ""); 686 | } 687 | }); 688 | if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { 689 | filter.checked = true; 690 | var ol = document.getElementById("qunit-tests"); 691 | ol.className = ol.className + " hidepass"; 692 | } 693 | toolbar.appendChild( filter ); 694 | 695 | var label = document.createElement("label"); 696 | label.setAttribute("for", "qunit-filter-pass"); 697 | label.innerHTML = "Hide passed tests"; 698 | toolbar.appendChild( label ); 699 | } 700 | 701 | var main = id('main') || id('qunit-fixture'); 702 | if ( main ) { 703 | config.fixture = main.innerHTML; 704 | } 705 | 706 | if (config.autostart) { 707 | QUnit.start(); 708 | } 709 | }); 710 | 711 | function done() { 712 | config.autorun = true; 713 | 714 | // Log the last module results 715 | if ( config.currentModule ) { 716 | QUnit.moduleDone( { 717 | name: config.currentModule, 718 | failed: config.moduleStats.bad, 719 | passed: config.moduleStats.all - config.moduleStats.bad, 720 | total: config.moduleStats.all 721 | } ); 722 | } 723 | 724 | var banner = id("qunit-banner"), 725 | tests = id("qunit-tests"), 726 | runtime = +new Date - config.started, 727 | passed = config.stats.all - config.stats.bad, 728 | html = [ 729 | 'Tests completed in ', 730 | runtime, 731 | ' milliseconds.
      ', 732 | '', 733 | passed, 734 | ' tests of ', 735 | config.stats.all, 736 | ' passed, ', 737 | config.stats.bad, 738 | ' failed.' 739 | ].join(''); 740 | 741 | if ( banner ) { 742 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 743 | } 744 | 745 | if ( tests ) { 746 | id( "qunit-testresult" ).innerHTML = html; 747 | } 748 | 749 | QUnit.done( { 750 | failed: config.stats.bad, 751 | passed: passed, 752 | total: config.stats.all, 753 | runtime: runtime 754 | } ); 755 | } 756 | 757 | function validTest( name ) { 758 | var i = config.filters.length, 759 | run = false; 760 | 761 | if ( !i ) { 762 | return true; 763 | } 764 | 765 | while ( i-- ) { 766 | var filter = config.filters[i], 767 | not = filter.charAt(0) == '!'; 768 | 769 | if ( not ) { 770 | filter = filter.slice(1); 771 | } 772 | 773 | if ( name.indexOf(filter) !== -1 ) { 774 | return !not; 775 | } 776 | 777 | if ( not ) { 778 | run = true; 779 | } 780 | } 781 | 782 | return run; 783 | } 784 | 785 | // so far supports only Firefox, Chrome and Opera (buggy) 786 | // could be extended in the future to use something like https://github.com/csnover/TraceKit 787 | function sourceFromStacktrace() { 788 | try { 789 | throw new Error(); 790 | } catch ( e ) { 791 | if (e.stacktrace) { 792 | // Opera 793 | return e.stacktrace.split("\n")[6]; 794 | } else if (e.stack) { 795 | // Firefox, Chrome 796 | return e.stack.split("\n")[4]; 797 | } 798 | } 799 | } 800 | 801 | function escapeHtml(s) { 802 | if (!s) { 803 | return ""; 804 | } 805 | s = s + ""; 806 | return s.replace(/[\&"<>\\]/g, function(s) { 807 | switch(s) { 808 | case "&": return "&"; 809 | case "\\": return "\\\\"; 810 | case '"': return '\"'; 811 | case "<": return "<"; 812 | case ">": return ">"; 813 | default: return s; 814 | } 815 | }); 816 | } 817 | 818 | function synchronize( callback ) { 819 | config.queue.push( callback ); 820 | 821 | if ( config.autorun && !config.blocking ) { 822 | process(); 823 | } 824 | } 825 | 826 | function process() { 827 | var start = (new Date()).getTime(); 828 | 829 | while ( config.queue.length && !config.blocking ) { 830 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { 831 | config.queue.shift()(); 832 | } else { 833 | window.setTimeout( process, 13 ); 834 | break; 835 | } 836 | } 837 | if (!config.blocking && !config.queue.length) { 838 | done(); 839 | } 840 | } 841 | 842 | function saveGlobal() { 843 | config.pollution = []; 844 | 845 | if ( config.noglobals ) { 846 | for ( var key in window ) { 847 | config.pollution.push( key ); 848 | } 849 | } 850 | } 851 | 852 | function checkPollution( name ) { 853 | var old = config.pollution; 854 | saveGlobal(); 855 | 856 | var newGlobals = diff( old, config.pollution ); 857 | if ( newGlobals.length > 0 ) { 858 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 859 | config.current.expected++; 860 | } 861 | 862 | var deletedGlobals = diff( config.pollution, old ); 863 | if ( deletedGlobals.length > 0 ) { 864 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 865 | config.current.expected++; 866 | } 867 | } 868 | 869 | // returns a new Array with the elements that are in a but not in b 870 | function diff( a, b ) { 871 | var result = a.slice(); 872 | for ( var i = 0; i < result.length; i++ ) { 873 | for ( var j = 0; j < b.length; j++ ) { 874 | if ( result[i] === b[j] ) { 875 | result.splice(i, 1); 876 | i--; 877 | break; 878 | } 879 | } 880 | } 881 | return result; 882 | } 883 | 884 | function fail(message, exception, callback) { 885 | if ( typeof console !== "undefined" && console.error && console.warn ) { 886 | console.error(message); 887 | console.error(exception); 888 | console.warn(callback.toString()); 889 | 890 | } else if ( window.opera && opera.postError ) { 891 | opera.postError(message, exception, callback.toString); 892 | } 893 | } 894 | 895 | function extend(a, b) { 896 | for ( var prop in b ) { 897 | a[prop] = b[prop]; 898 | } 899 | 900 | return a; 901 | } 902 | 903 | function addEvent(elem, type, fn) { 904 | if ( elem.addEventListener ) { 905 | elem.addEventListener( type, fn, false ); 906 | } else if ( elem.attachEvent ) { 907 | elem.attachEvent( "on" + type, fn ); 908 | } else { 909 | fn(); 910 | } 911 | } 912 | 913 | function id(name) { 914 | return !!(typeof document !== "undefined" && document && document.getElementById) && 915 | document.getElementById( name ); 916 | } 917 | 918 | // Test for equality any JavaScript type. 919 | // Discussions and reference: http://philrathe.com/articles/equiv 920 | // Test suites: http://philrathe.com/tests/equiv 921 | // Author: Philippe Rathé 922 | QUnit.equiv = function () { 923 | 924 | var innerEquiv; // the real equiv function 925 | var callers = []; // stack to decide between skip/abort functions 926 | var parents = []; // stack to avoiding loops from circular referencing 927 | 928 | // Call the o related callback with the given arguments. 929 | function bindCallbacks(o, callbacks, args) { 930 | var prop = QUnit.objectType(o); 931 | if (prop) { 932 | if (QUnit.objectType(callbacks[prop]) === "function") { 933 | return callbacks[prop].apply(callbacks, args); 934 | } else { 935 | return callbacks[prop]; // or undefined 936 | } 937 | } 938 | } 939 | 940 | var callbacks = function () { 941 | 942 | // for string, boolean, number and null 943 | function useStrictEquality(b, a) { 944 | if (b instanceof a.constructor || a instanceof b.constructor) { 945 | // to catch short annotaion VS 'new' annotation of a declaration 946 | // e.g. var i = 1; 947 | // var j = new Number(1); 948 | return a == b; 949 | } else { 950 | return a === b; 951 | } 952 | } 953 | 954 | return { 955 | "string": useStrictEquality, 956 | "boolean": useStrictEquality, 957 | "number": useStrictEquality, 958 | "null": useStrictEquality, 959 | "undefined": useStrictEquality, 960 | 961 | "nan": function (b) { 962 | return isNaN(b); 963 | }, 964 | 965 | "date": function (b, a) { 966 | return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); 967 | }, 968 | 969 | "regexp": function (b, a) { 970 | return QUnit.objectType(b) === "regexp" && 971 | a.source === b.source && // the regex itself 972 | a.global === b.global && // and its modifers (gmi) ... 973 | a.ignoreCase === b.ignoreCase && 974 | a.multiline === b.multiline; 975 | }, 976 | 977 | // - skip when the property is a method of an instance (OOP) 978 | // - abort otherwise, 979 | // initial === would have catch identical references anyway 980 | "function": function () { 981 | var caller = callers[callers.length - 1]; 982 | return caller !== Object && 983 | typeof caller !== "undefined"; 984 | }, 985 | 986 | "array": function (b, a) { 987 | var i, j, loop; 988 | var len; 989 | 990 | // b could be an object literal here 991 | if ( ! (QUnit.objectType(b) === "array")) { 992 | return false; 993 | } 994 | 995 | len = a.length; 996 | if (len !== b.length) { // safe and faster 997 | return false; 998 | } 999 | 1000 | //track reference to avoid circular references 1001 | parents.push(a); 1002 | for (i = 0; i < len; i++) { 1003 | loop = false; 1004 | for(j=0;j= 0) { 1149 | type = "array"; 1150 | } else { 1151 | type = typeof obj; 1152 | } 1153 | return type; 1154 | }, 1155 | separator:function() { 1156 | return this.multiline ? this.HTML ? '
      ' : '\n' : this.HTML ? ' ' : ' '; 1157 | }, 1158 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1159 | if ( !this.multiline ) 1160 | return ''; 1161 | var chr = this.indentChar; 1162 | if ( this.HTML ) 1163 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1164 | return Array( this._depth_ + (extra||0) ).join(chr); 1165 | }, 1166 | up:function( a ) { 1167 | this._depth_ += a || 1; 1168 | }, 1169 | down:function( a ) { 1170 | this._depth_ -= a || 1; 1171 | }, 1172 | setParser:function( name, parser ) { 1173 | this.parsers[name] = parser; 1174 | }, 1175 | // The next 3 are exposed so you can use them 1176 | quote:quote, 1177 | literal:literal, 1178 | join:join, 1179 | // 1180 | _depth_: 1, 1181 | // This is the list of parsers, to modify them, use jsDump.setParser 1182 | parsers:{ 1183 | window: '[Window]', 1184 | document: '[Document]', 1185 | error:'[ERROR]', //when no parser is found, shouldn't happen 1186 | unknown: '[Unknown]', 1187 | 'null':'null', 1188 | undefined:'undefined', 1189 | 'function':function( fn ) { 1190 | var ret = 'function', 1191 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1192 | if ( name ) 1193 | ret += ' ' + name; 1194 | ret += '('; 1195 | 1196 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); 1197 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); 1198 | }, 1199 | array: array, 1200 | nodelist: array, 1201 | arguments: array, 1202 | object:function( map ) { 1203 | var ret = [ ]; 1204 | QUnit.jsDump.up(); 1205 | for ( var key in map ) 1206 | ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); 1207 | QUnit.jsDump.down(); 1208 | return join( '{', ret, '}' ); 1209 | }, 1210 | node:function( node ) { 1211 | var open = QUnit.jsDump.HTML ? '<' : '<', 1212 | close = QUnit.jsDump.HTML ? '>' : '>'; 1213 | 1214 | var tag = node.nodeName.toLowerCase(), 1215 | ret = open + tag; 1216 | 1217 | for ( var a in QUnit.jsDump.DOMAttrs ) { 1218 | var val = node[QUnit.jsDump.DOMAttrs[a]]; 1219 | if ( val ) 1220 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); 1221 | } 1222 | return ret + close + open + '/' + tag + close; 1223 | }, 1224 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1225 | var l = fn.length; 1226 | if ( !l ) return ''; 1227 | 1228 | var args = Array(l); 1229 | while ( l-- ) 1230 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1231 | return ' ' + args.join(', ') + ' '; 1232 | }, 1233 | key:quote, //object calls it internally, the key part of an item in a map 1234 | functionCode:'[code]', //function calls it internally, it's the content of the function 1235 | attribute:quote, //node calls it internally, it's an html attribute value 1236 | string:quote, 1237 | date:quote, 1238 | regexp:literal, //regex 1239 | number:literal, 1240 | 'boolean':literal 1241 | }, 1242 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1243 | id:'id', 1244 | name:'name', 1245 | 'class':'className' 1246 | }, 1247 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1248 | indentChar:' ',//indentation unit 1249 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1250 | }; 1251 | 1252 | return jsDump; 1253 | })(); 1254 | 1255 | // from Sizzle.js 1256 | function getText( elems ) { 1257 | var ret = "", elem; 1258 | 1259 | for ( var i = 0; elems[i]; i++ ) { 1260 | elem = elems[i]; 1261 | 1262 | // Get the text from text nodes and CDATA nodes 1263 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1264 | ret += elem.nodeValue; 1265 | 1266 | // Traverse everything else, except comment nodes 1267 | } else if ( elem.nodeType !== 8 ) { 1268 | ret += getText( elem.childNodes ); 1269 | } 1270 | } 1271 | 1272 | return ret; 1273 | }; 1274 | 1275 | /* 1276 | * Javascript Diff Algorithm 1277 | * By John Resig (http://ejohn.org/) 1278 | * Modified by Chu Alan "sprite" 1279 | * 1280 | * Released under the MIT license. 1281 | * 1282 | * More Info: 1283 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1284 | * 1285 | * Usage: QUnit.diff(expected, actual) 1286 | * 1287 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" 1288 | */ 1289 | QUnit.diff = (function() { 1290 | function diff(o, n){ 1291 | var ns = new Object(); 1292 | var os = new Object(); 1293 | 1294 | for (var i = 0; i < n.length; i++) { 1295 | if (ns[n[i]] == null) 1296 | ns[n[i]] = { 1297 | rows: new Array(), 1298 | o: null 1299 | }; 1300 | ns[n[i]].rows.push(i); 1301 | } 1302 | 1303 | for (var i = 0; i < o.length; i++) { 1304 | if (os[o[i]] == null) 1305 | os[o[i]] = { 1306 | rows: new Array(), 1307 | n: null 1308 | }; 1309 | os[o[i]].rows.push(i); 1310 | } 1311 | 1312 | for (var i in ns) { 1313 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1314 | n[ns[i].rows[0]] = { 1315 | text: n[ns[i].rows[0]], 1316 | row: os[i].rows[0] 1317 | }; 1318 | o[os[i].rows[0]] = { 1319 | text: o[os[i].rows[0]], 1320 | row: ns[i].rows[0] 1321 | }; 1322 | } 1323 | } 1324 | 1325 | for (var i = 0; i < n.length - 1; i++) { 1326 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1327 | n[i + 1] == o[n[i].row + 1]) { 1328 | n[i + 1] = { 1329 | text: n[i + 1], 1330 | row: n[i].row + 1 1331 | }; 1332 | o[n[i].row + 1] = { 1333 | text: o[n[i].row + 1], 1334 | row: i + 1 1335 | }; 1336 | } 1337 | } 1338 | 1339 | for (var i = n.length - 1; i > 0; i--) { 1340 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1341 | n[i - 1] == o[n[i].row - 1]) { 1342 | n[i - 1] = { 1343 | text: n[i - 1], 1344 | row: n[i].row - 1 1345 | }; 1346 | o[n[i].row - 1] = { 1347 | text: o[n[i].row - 1], 1348 | row: i - 1 1349 | }; 1350 | } 1351 | } 1352 | 1353 | return { 1354 | o: o, 1355 | n: n 1356 | }; 1357 | } 1358 | 1359 | return function(o, n){ 1360 | o = o.replace(/\s+$/, ''); 1361 | n = n.replace(/\s+$/, ''); 1362 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); 1363 | 1364 | var str = ""; 1365 | 1366 | var oSpace = o.match(/\s+/g); 1367 | if (oSpace == null) { 1368 | oSpace = [" "]; 1369 | } 1370 | else { 1371 | oSpace.push(" "); 1372 | } 1373 | var nSpace = n.match(/\s+/g); 1374 | if (nSpace == null) { 1375 | nSpace = [" "]; 1376 | } 1377 | else { 1378 | nSpace.push(" "); 1379 | } 1380 | 1381 | if (out.n.length == 0) { 1382 | for (var i = 0; i < out.o.length; i++) { 1383 | str += '' + out.o[i] + oSpace[i] + ""; 1384 | } 1385 | } 1386 | else { 1387 | if (out.n[0].text == null) { 1388 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { 1389 | str += '' + out.o[n] + oSpace[n] + ""; 1390 | } 1391 | } 1392 | 1393 | for (var i = 0; i < out.n.length; i++) { 1394 | if (out.n[i].text == null) { 1395 | str += '' + out.n[i] + nSpace[i] + ""; 1396 | } 1397 | else { 1398 | var pre = ""; 1399 | 1400 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1401 | pre += '' + out.o[n] + oSpace[n] + ""; 1402 | } 1403 | str += " " + out.n[i].text + nSpace[i] + pre; 1404 | } 1405 | } 1406 | } 1407 | 1408 | return str; 1409 | }; 1410 | })(); 1411 | 1412 | })(this); 1413 | -------------------------------------------------------------------------------- /test/nested/js/rel.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return "Hello World!"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/nested/js/test.js: -------------------------------------------------------------------------------- 1 | var rel = require('./rel') 2 | , nested = require('./nested/nest') 3 | , aboveNested = require('./nested/aboveNested') 4 | , elevator = require('./level1') 5 | , hell = require('../../hell'); 6 | 7 | window.module('require relatives'); 8 | asyncTest('relative sibling requires', function () { 9 | equals(rel(), 'Hello World!'); 10 | start(); 11 | }); 12 | asyncTest('relative nested requires', function () { 13 | equals(nested(), "I'm a descendant"); 14 | start(); 15 | }); 16 | asyncTest('relative above nested requires', function () { 17 | equals(aboveNested(), "I'm an ancestor of the descendant"); 18 | start(); 19 | }); 20 | asyncTest('elevator requires', function () { 21 | equals(elevator(), "level 1 => level 3 => level 2"); 22 | start(); 23 | }); 24 | asyncTest('level before base dir require', function () { 25 | equals(hell(), "PYRO"); 26 | start(); 27 | }); 28 | 29 | window.module('require npm'); 30 | asyncTest('simple npm modules', function () { 31 | var _ = require('underscore'); 32 | equals(10, _.reduce([1, 2, 3, 4], function (sum, num) { 33 | sum += num; 34 | return sum; 35 | })); 36 | start(); 37 | }); 38 | asyncTest('npm submodules', function () { 39 | var Set = require('data-structures-js/set') 40 | , s = new Set(['look', 'ma', 'no', 'hands']); 41 | s.add('look'); 42 | ok(s.contains('ma')); 43 | start(); 44 | }); 45 | asyncTest('npm modules that use relative modules from a file under the npm lib dir', function () { 46 | // The following file requires ./entities 47 | // See https://github.com/chriso/node-validator/blob/master/lib/xss.js 48 | var clean = require('validator/xss').clean; 49 | equals('function', typeof clean); 50 | start(); 51 | }); 52 | asyncTest('npm modules that use relative modules from the main file', function () { 53 | // The following file requires ./lib/async from the 'main' index.js package file 54 | // See https://github.com/caolan/async/index.js 55 | var noConflict = require('async').noConflict; 56 | equals('function', typeof noConflict); 57 | start(); 58 | }); 59 | -------------------------------------------------------------------------------- /test/nested/server.js: -------------------------------------------------------------------------------- 1 | require.paths.unshift('../../lib'); 2 | var path = require('path'); 3 | require.paths.unshift(path.join(__dirname, '..', '..')); 4 | var connect = require('connect'); 5 | var exposeRequire = require('browser-require'); 6 | var app = connect( 7 | exposeRequire({ 8 | base: __dirname 9 | }), 10 | connect.static(__dirname) 11 | ); 12 | app.listen(1234); 13 | console.log("Server running at http://127.0.0.1:1234"); 14 | process.title = "brtest"; 15 | --------------------------------------------------------------------------------