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