/* * Asynchronous Module Definition (AMD) API implementation for JLS. * The following code creates a 'require' function to load modules. * See https://github.com/amdjs/amdjs-api */ (function() { /* Helper functions */ function extension(path) { var index = path.lastIndexOf('.'); if (index !== -1) { var slashIndex = path.lastIndexOf('/'); if (index > slashIndex) { return path.substring(index + 1); } } return ''; } function basename(path) { var index = path.lastIndexOf('/'); return index === -1 ? path : path.substring(index + 1); } function dirname(path) { var index = path.lastIndexOf('/'); return index === -1 ? '' : path.substring(0, index); } function endsWith(value, search) { return value.substring(value.length - search.length) === search; } function startsWith(value, search) { return value.substring(0, search.length) === search; } function concatPath(a, b) { var pathItems = b.split('/'); if ((a !== '.') && ((pathItems[0] === '.') || (pathItems[0] === '..'))) { pathItems = a.split('/').concat(pathItems); } for (var i = 0; i < pathItems.length; i++) { var pathItem = pathItems[i]; if ((pathItem === '.') || (pathItem === '')) { pathItems.splice(i, 1); i--; } else if ((i > 0) && (pathItem === '..')) { pathItems.splice(i - 1, 2); i -= 2; } } var path = pathItems.join('/'); if (startsWith(b, '/')) { path = '/' + path; } if (endsWith(b, '/')) { path = path + '/'; } return path; } function basemodule(path) { var index = path.indexOf('$'); return index === -1 ? path : path.substring(0, index); } function submodule(module, path) { if (typeof path !== 'string') { return module; } var index = path.indexOf('$'); if (index === -1) { return module; } var subpath = path.substring(index + 1); // TODO handle sub sub... return module[subpath]; } /* Miscellaneous functions */ var warn, log, debug, loadModule; log = debug = warn = function(msg) {}; var previousDefine = undefined; loadModule = function(path, callback, sync, prefix, suffix) {}; var fullyQualifiedRequire, createRequire, fullyQualifiedDefine; /* The cache is a map containing the defined module by absolute path (module identifier) */ var cache = {}; /* The AMD specific define entry */ var amd = { multiversion: true }; /* */ var CacheEntry = function(path, sync, module) { this.path = path || null; this.module = module || undefined; this.exports = null; this.sync = sync || false; this.paths = null; this.callback = null; this.defined = false; this.loading = false; this.dependencies = []; this.count = 0; this.waiters = []; if (path) { cache[path] = this; } log('CacheEntry("' + path + '", ' + sync + ', ' + module + ')'); }; CacheEntry.prototype.declare = function(paths, callback) { log('declare([' + paths + '], ...) "' + this.path + '"'); this.paths = paths || null; this.callback = callback || null; }; CacheEntry.prototype.onDefined = function(module) { log('onDefined(...) "' + this.path + '"'); this.module = module || null; if ((this.exports !== null) && (typeof this.exports !== 'undefined')) { var singleKey = null; for (var k in this.exports) { if (singleKey === null) { singleKey = k; } else { singleKey = null; break; } } if (singleKey === null) { this.module = this.exports; } else { this.module = this.exports[singleKey]; } } // Play dependencies for (var index = 0; index < this.waiters.length; index++) { this.waiters[index].notify(this); } // TODO Cleanup dependencies... this.waiters = []; }; CacheEntry.prototype.onLoaded = function(fnOrContent, err) { var t = typeof fnOrContent; debug('onLoaded(' + t + ', ' + err + ') "' + this.path + '"'); var m = null; if (err) { warn('Exception raised while loading "' + this.path + '": ' + err); } else { var self = this; if (t === 'function') { try { /* Creates a define function suitable for this module */ var defined = false; var defineFn = function(id, dependencies, factory) { if (typeof id === 'function') { factory = id; dependencies = null; id = undefined; } else if (typeof dependencies === 'function') { factory = dependencies; dependencies = id; id = undefined; } fullyQualifiedRequire(self.path, dependencies, factory, self.sync); defined = true; }; m = fnOrContent(defineFn); if (defined) { return; } } catch (e) { warn('Exception raised while evaluating "' + this.path + '": ' + e); } } else if ((t === 'string') || (t === 'object')) { m = fnOrContent; } else { warn('Invalid argument while loading "' + this.path + '" (' + t + ')'); } } if (typeof m === 'object' && typeof m.then === 'function') { m.then(function(n) { self.onDefined(n); }, function(e) { warn('Exception raised while loading "' + self.path + '": ' + e); self.onDefined(null); }); } else { this.onDefined(m); } }; CacheEntry.prototype.load = function() { debug('load() "' + this.path + '"'); var self = this; var path = this.path; var ext = extension(path); if (ext === '' && !endsWith(path, '/')) { path += '.js'; } this.loading = true; loadModule(path, function(fnOrContent, err) { self.onLoaded.call(self, fnOrContent, err); }, this.sync, '(function() { return function(define) { ', ' }; })();'); }; CacheEntry.prototype.notify = function(entry) { debug('notify("' + entry.path + '") "' + this.path + '"'); for (var index = 0; index < this.paths.length; index++) { var path = basemodule(this.paths[index]); if (path === entry.path) { this.setDependency(index, entry.module); break; } } }; CacheEntry.prototype.isDependent = function(entry) { debug('isDependent("' + entry.path + '") "' + this.path + '"'); if (entry.path === null) { return false; } for (var index = 0; index < this.waiters.length; index++) { var waiter = this.waiters[index]; if ((waiter.path !== null) && ((waiter.path === entry.path) || (waiter.isDependent(entry)))) { return true; } } return false; }; CacheEntry.prototype.wait = function(entry) { debug('wait("' + entry.path + '") "' + this.path + '"'); if (this.isDependent(entry)) { throw 'Cyclic dependency detected between ' + this.path + ' and ' + entry.path; } entry.waiters.push(this); }; CacheEntry.prototype.isLoading = function() { return this.loading; }; CacheEntry.prototype.isLoaded = function() { return typeof this.module !== 'undefined'; }; CacheEntry.prototype.isModule = function() { return this.path !== null; }; CacheEntry.prototype.isDeclared = function() { return (this.callback !== null) || this.isLoaded(); }; CacheEntry.prototype.setDependency = function(index, value) { var path = this.paths[index]; this.dependencies[index] = submodule(value, path); this.count++; debug('setDependency(' + index + ', ...) "' + this.path + '" ' + this.count + '/' + this.paths.length); this.wakeup(); }; CacheEntry.prototype.wakeup = function() { if ((this.callback) && ((this.paths === null) || (this.count === this.paths.length))) { debug('wakeup() "' + this.path + '"'); var fn = this.callback; var dep = this.dependencies; this.paths = null; this.callback = null; this.dependencies = null; var module = fn.apply(null, dep); this.onDefined(this.isModule() ? module : null); } }; /* Base require/define function that use a fully qualified module id */ fullyQualifiedRequire = function(moduleId, ids, callback, sync, dl) { if (typeof callback !== 'function') { throw 'Invalid arguments'; } sync = (typeof sync === 'boolean') ? sync : false; dl = (typeof dl === 'boolean') ? dl : false; var basepath = dirname(moduleId || ''); var name = basename(moduleId || ''); var paths = []; if ((ids !== null) && ('length' in ids)) { for (var index = 0; index < ids.length; index++) { var id = ids[index]; if (id === '.') { id = './main'; } paths.push(concatPath(basepath, id)); } } if (name === '?') { moduleId = null; } var entry; if (moduleId) { if (moduleId in cache) { // Check sync ? entry = cache[moduleId]; } else { entry = new CacheEntry(moduleId, sync, undefined); } entry.defined = true; } else { entry = new CacheEntry(null, sync, undefined); } entry.declare(paths, callback); for (var index = 0; index < paths.length; index++) { var path = basemodule(paths[index]); var depEntry; try { if (path === 'require') { entry.setDependency(index, createRequire(basepath)); } else if (path === 'requirePath') { entry.setDependency(index, basepath); } else if (path === 'exports') { entry.setDependency(index, entry.exports = {}); } else if (path in cache) { depEntry = cache[path]; if (depEntry.isLoaded()) { entry.setDependency(index, depEntry.module); } else { // TODO Check sync entry.wait(depEntry); if (! (dl || depEntry.isLoading())) { depEntry.load(); } } } else { depEntry = new CacheEntry(path, sync); entry.wait(depEntry); if (! dl) { depEntry.load(); } } } catch (e) { warn('Exception raised: ' + e); //debug(e.name + ': ' + e.message); entry.setDependency(index, null); } } entry.wakeup(); }; /* Returns a require function suitable for a specific path */ createRequire = function(path) { return function(ids, callback) { var async = true; var rmod = null; if (typeof ids === 'string') { ids = [ids]; } if (typeof callback === 'undefined') { async = false; callback = function(module) { if ((typeof module === 'undefined') || (module === null)) { warn('Fail to require "' + ids[0] + '"'); } rmod = module; }; } fullyQualifiedRequire(path + '/?', ids, callback, !async); return async ? undefined : rmod; }; }; /* Define function that use a fully qualified module id */ fullyQualifiedDefine = function(id, dependencies, factory) { log('define(' + id + ')'); fullyQualifiedRequire(id, dependencies, factory, true, true); }; // Populate default cache values new CacheEntry('_AMD', true, { cache : cache, setLogFunction: function(fn, level) { switch(level) { default: case 'warn': warn = fn; break; case 'log': log = fn; break; case 'debug': debug = fn; break; case 'all': log = debug = warn = fn; break; } }, enableDefine: function() { log('enableDefine()'); previousDefine = (typeof define !== 'undefined') ? define : undefined; // global define function define = fullyQualifiedDefine; }, disableDefine: function() { log('disableDefine()'); define = previousDefine; }, setLoadModuleFunction: function(fn) { loadModule = fn; }, setEvalScriptFunction: function(fn) { loadModule = fn; }, getModuleId: function(obj) { for (var id in cache) { if (cache[id].module === obj.constructor) { return id; } } return null; }, status: function() { log('AMD Cache status:'); var lm = [], wm = []; for (var path in cache) { (cache[path].module ? lm : wm).push(path); } lm.sort(); wm.sort(); for (var i = 0; i < lm.length; i++) { log(' "' + lm[i] + '" loaded'); } for (var i = 0; i < wm.length; i++) { var path = wm[i]; var ce = cache[path]; var paths = ''; if (ce.paths !== null) { for (var j = 0; j < ce.paths.length; j++) { paths += (paths === '' ? '' : ', ') + (ce.dependencies[j] !== null ? '!' : '') + ce.paths[j]; } } log(' "' + path + '"(' + paths + ') ' + ce.waiters.length + ' waiting callback(s)'); } }, lazyRequire : function(ids, callback) { if (arguments.length < 2) { return; } fullyQualifiedRequire('/?', ids, callback, true, true); }, dirname : dirname, basename : basename, concatPath : concatPath, createRequire : createRequire, fullyQualifiedDefine : fullyQualifiedDefine, fullyQualifiedRequire : fullyQualifiedRequire }); // global require function require = createRequire(''); })();