1 /* See license.txt for terms of usage */ 2 3 // ********************************************************************************************* // 4 // Module Loader Implementation 5 6 var EXPORTED_SYMBOLS = ["require", "define"]; 7 8 var require, define; 9 10 (function() { 11 12 // ********************************************************************************************* // 13 // Constants 14 15 var Cu = Components.utils; 16 var Cc = Components.classes; 17 var Ci = Components.interfaces; 18 19 Cu.import("resource://gre/modules/Services.jsm"); 20 Cu.import("resource://firebug/fbtrace.js"); 21 22 // ********************************************************************************************* // 23 // Module Loader implementation 24 25 var Loader = 26 { 27 config: {}, 28 modules: {}, 29 currentModule: [], 30 payloads: {}, 31 32 require: function(config, modules, callback) 33 { 34 if (typeof config == "string" && !modules && !callback) 35 return this.modules[config].exports; 36 37 this.config = config ? config : this.config; 38 this.currentModule = []; 39 40 var main = this.modules["main"] = { 41 scope: {} 42 }; 43 44 this.currentModule.push(main); 45 this.lookup(modules, callback); 46 }, 47 48 define: function(moduleId, deps, payload) 49 { 50 payload.deps = deps; 51 this.payloads[moduleId] = payload; 52 }, 53 54 lookup: function(moduleId, deps, callback) 55 { 56 // Module name doesn't have to be specified. 57 if (arguments.length == 2) 58 { 59 callback = deps; 60 deps = moduleId; 61 moduleId = undefined; 62 } 63 64 var self = this; 65 var args = deps.map(function(dep) 66 { 67 var result = self.loadModule(dep); 68 if (!result) 69 { 70 FBTrace.sysout("mini-require; ERROR Could be a cycle dependency or undefined " + 71 "return value from a module: " + dep, self.getDeps()); 72 } 73 return result; 74 }); 75 76 try 77 { 78 var module = this.currentModule[this.currentModule.length - 1]; 79 module.deps = deps; 80 module.args = args; 81 module.exports = callback.apply(module.scope, args); 82 } 83 catch (err) 84 { 85 Cu.reportError(err); 86 87 if (FBTrace.DBG_ERRORS) 88 FBTrace.sysout("mini-require; lookup " + err, err); 89 } 90 }, 91 92 loadModule: function(moduleId) 93 { 94 var module = this.modules[moduleId]; 95 if (module) 96 return module.exports; 97 98 module = this.modules[moduleId] = {}; 99 module.scope = { 100 define: this.lookup.bind(this) 101 } 102 103 this.currentModule.push(module); 104 105 // If the module is already registered, load all deps and execute. 106 // Otherwise we need to load the script from URL. 107 var payload = this.payloads[moduleId]; 108 if (payload) 109 { 110 this.lookup(moduleId, payload.deps, payload); 111 } 112 else 113 { 114 var moduleUrl = this.getModuleUrl(moduleId) + ".js"; 115 require.load(module.scope, moduleId, moduleUrl); 116 } 117 118 this.currentModule.pop(); 119 120 // Exports (the module return value in case of AMD) is set in define function. 121 return module.exports; 122 }, 123 124 load: function(context, fullName, url) 125 { 126 try 127 { 128 Services.scriptloader.loadSubScript(url, context); 129 130 if (FBTrace.DBG_MODULES) 131 FBTrace.sysout("mini-require; Module loaded " + fullName, url); 132 } 133 catch (err) 134 { 135 Cu.reportError(fullName + " -> " + url); 136 Cu.reportError(err); 137 } 138 }, 139 140 getModuleUrl: function(moduleId) 141 { 142 var baseUrl = this.config.baseUrl; 143 if (baseUrl.substr(-1) != "/") 144 baseUrl += "/"; 145 146 // If there are no aliases just use baseUrl. 147 if (!this.config.paths) 148 return baseUrl + moduleId; 149 150 // Get module id path parts (excluding the module name). 151 var parts = moduleId.split("/"); 152 var moduleName = parts.pop(); 153 154 var self = this; 155 var resolved = parts.map(function(part) 156 { 157 var alias = self.config.paths[part]; 158 return alias ? alias : part; 159 }); 160 161 var moduleUrl = resolved.join("/"); 162 if (moduleUrl.substr(-1) != "/") 163 moduleUrl += "/"; 164 165 moduleUrl += moduleName; 166 167 var reProtocol = /^[^:]+(?=:\/\/)/; 168 if (moduleUrl.match(reProtocol)) 169 return moduleUrl; 170 171 // If there is no protocol, use baseUrl. 172 return baseUrl + moduleUrl; 173 }, 174 175 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 176 // Debugging Dependencies 177 178 getDeps: function() 179 { 180 var result = {}; 181 for (var p in this.modules) 182 this.calculateDeps(p, result); 183 return result; 184 }, 185 186 calculateDeps: function(moduleId, result) 187 { 188 var deps = result[moduleId]; 189 if (deps) 190 return deps; 191 192 deps = result[moduleId] = {}; 193 194 var module = this.modules[moduleId]; 195 if (!module.deps) 196 return deps; 197 198 for (var i=0; i<module.deps.length; i++) 199 { 200 var id = module.deps[i]; 201 deps[id] = this.calculateDeps(id, result); 202 } 203 204 return deps; 205 }, 206 207 getDepDesc: function() 208 { 209 var desc = ""; 210 var deps = this.getDeps(); 211 for (var p in deps) 212 desc += p + "\n"; 213 return desc; 214 }, 215 } 216 217 // ********************************************************************************************* // 218 // Public API 219 220 require = Loader.require.bind(Loader); 221 define = Loader.define.bind(Loader); 222 require.load = Loader.load.bind(Loader); 223 require.Loader = Loader; 224 225 // ********************************************************************************************* // 226 })(); 227