loader.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. *
  16. * @fileoverview This class supports the dynamic loading of compiled
  17. * javascript modules at runtime, as described in the designdoc.
  18. *
  19. * <http://go/js_modules_design>
  20. *
  21. */
  22. goog.provide('goog.module.Loader');
  23. goog.require('goog.Timer');
  24. goog.require('goog.array');
  25. goog.require('goog.asserts');
  26. goog.require('goog.dom');
  27. goog.require('goog.dom.TagName');
  28. /** @suppress {extraRequire} */
  29. goog.require('goog.module');
  30. goog.require('goog.object');
  31. /**
  32. * The dynamic loading functionality is defined as a class. The class
  33. * will be used as singleton. There is, however, a two step
  34. * initialization procedure because parameters need to be passed to
  35. * the goog.module.Loader instance.
  36. *
  37. * @constructor
  38. * @final
  39. */
  40. goog.module.Loader = function() {
  41. /**
  42. * Map of module name/array of {symbol name, callback} pairs that are pending
  43. * to be loaded.
  44. * @type {Object}
  45. * @private
  46. */
  47. this.pending_ = {};
  48. /**
  49. * Provides associative access to each module and the symbols of each module
  50. * that have already been loaded (one lookup for the module, another lookup
  51. * on the module for the symbol).
  52. * @type {Object}
  53. * @private
  54. */
  55. this.modules_ = {};
  56. /**
  57. * Map of module name to module url. Used to avoid fetching the same URL
  58. * twice by keeping track of in-flight URLs.
  59. * Note: this allows two modules to be bundled into the same file.
  60. * @type {Object}
  61. * @private
  62. */
  63. this.pendingModuleUrls_ = {};
  64. /**
  65. * The base url to load modules from. This property will be set in init().
  66. * @type {?string}
  67. * @private
  68. */
  69. this.urlBase_ = null;
  70. /**
  71. * Array of modules that have been requested before init() was called.
  72. * If require() is called before init() was called, the required
  73. * modules can obviously not yet be loaded, because their URL is
  74. * unknown. The modules that are requested before init() are
  75. * therefore stored in this array, and they are loaded at init()
  76. * time.
  77. * @type {Array<string>}
  78. * @private
  79. */
  80. this.pendingBeforeInit_ = [];
  81. };
  82. goog.addSingletonGetter(goog.module.Loader);
  83. /**
  84. * Wrapper of goog.module.Loader.require() for use in modules.
  85. * See method goog.module.Loader.require() for
  86. * explanation of params.
  87. *
  88. * @param {string} module The name of the module. Usually, the value
  89. * is defined as a constant whose name starts with MOD_.
  90. * @param {number|string} symbol The ID of the symbol. Usually, the value is
  91. * defined as a constant whose name starts with SYM_.
  92. * @param {Function} callback This function will be called with the
  93. * resolved symbol as the argument once the module is loaded.
  94. */
  95. goog.module.Loader.require = function(module, symbol, callback) {
  96. goog.module.Loader.getInstance().require(module, symbol, callback);
  97. };
  98. /**
  99. * Wrapper of goog.module.Loader.provide() for use in modules
  100. * See method goog.module.Loader.provide() for explanation of params.
  101. *
  102. * @param {string} module The name of the module. Cf. parameter module
  103. * of method require().
  104. * @param {number|string=} opt_symbol The symbol being defined, or nothing
  105. * when all symbols of the module are defined. Cf. parameter symbol of
  106. * method require().
  107. * @param {Object=} opt_object The object bound to the symbol, or nothing when
  108. * all symbols of the module are defined.
  109. */
  110. goog.module.Loader.provide = function(module, opt_symbol, opt_object) {
  111. goog.module.Loader.getInstance().provide(module, opt_symbol, opt_object);
  112. };
  113. /**
  114. * Wrapper of init() so that we only need to export this single
  115. * identifier instead of three. See method goog.module.Loader.init() for
  116. * explanation of param.
  117. *
  118. * @param {string} urlBase The URL of the base library.
  119. * @param {Function=} opt_urlFunction Function that creates the URL for the
  120. * module file. It will be passed the base URL for module files and the
  121. * module name and should return the fully-formed URL to the module file to
  122. * load.
  123. */
  124. goog.module.Loader.init = function(urlBase, opt_urlFunction) {
  125. goog.module.Loader.getInstance().init(urlBase, opt_urlFunction);
  126. };
  127. /**
  128. * Produces a function that delegates all its arguments to a
  129. * dynamically loaded function. This is used to export dynamically
  130. * loaded functions.
  131. *
  132. * @param {string} module The module to load from.
  133. * @param {number|string} symbol The ID of the symbol to load from the module.
  134. * This symbol must resolve to a function.
  135. * @return {!Function} A function that forwards all its arguments to
  136. * the dynamically loaded function specified by module and symbol.
  137. */
  138. goog.module.Loader.loaderCall = function(module, symbol) {
  139. return function() {
  140. var args = arguments;
  141. goog.module.Loader.require(
  142. module, symbol, function(f) { f.apply(null, args); });
  143. };
  144. };
  145. /**
  146. * Creates a full URL to the compiled module code given a base URL and a
  147. * module name. By default it's urlBase + '_' + module + '.js'.
  148. * @param {string} urlBase URL to the module files.
  149. * @param {string} module Module name.
  150. * @return {string} The full url to the module binary.
  151. * @private
  152. */
  153. goog.module.Loader.prototype.getModuleUrl_ = function(urlBase, module) {
  154. return urlBase + '_' + module + '.js';
  155. };
  156. /**
  157. * The globally exported name of the load callback. Matches the
  158. * definition in the js_module_binary() BUILD rule.
  159. * @type {string}
  160. */
  161. goog.module.Loader.LOAD_CALLBACK = '__gjsload__';
  162. /**
  163. * Loads the module by evaluating the javascript text in the current
  164. * scope. Uncompiled, base identifiers are visible in the global scope;
  165. * when compiled they are visible in the closure of the anonymous
  166. * namespace. Notice that this cannot be replaced by the global eval,
  167. * because the global eval isn't in the scope of the anonymous
  168. * namespace function that the jscompiled code lives in.
  169. *
  170. * @param {string} t_ The javascript text to evaluate. IMPORTANT: The
  171. * name of the identifier is chosen so that it isn't compiled and
  172. * hence cannot shadow compiled identifiers in the surrounding scope.
  173. * @private
  174. */
  175. goog.module.Loader.loaderEval_ = function(t_) {
  176. eval(t_);
  177. };
  178. /**
  179. * Initializes the Loader to be fully functional. Also executes load
  180. * requests that were received before initialization. Must be called
  181. * exactly once, with the URL of the base library. Module URLs are
  182. * derived from the URL of the base library by inserting the module
  183. * name, preceded by a period, before the .js prefix of the base URL.
  184. *
  185. * @param {string} baseUrl The URL of the base library.
  186. * @param {Function=} opt_urlFunction Function that creates the URL for the
  187. * module file. It will be passed the base URL for module files and the
  188. * module name and should return the fully-formed URL to the module file to
  189. * load.
  190. */
  191. goog.module.Loader.prototype.init = function(baseUrl, opt_urlFunction) {
  192. // For the use by the module wrappers, loaderEval_ is exported to
  193. // the page. Note that, despite the name, this is not part of the
  194. // API, so it is here and not in api_app.js. Cf. BUILD. Note this is
  195. // done before the first load requests are sent.
  196. goog.exportSymbol(
  197. goog.module.Loader.LOAD_CALLBACK, goog.module.Loader.loaderEval_);
  198. this.urlBase_ = baseUrl.replace(/\.js$/, '');
  199. if (opt_urlFunction) {
  200. this.getModuleUrl_ = opt_urlFunction;
  201. }
  202. goog.array.forEach(
  203. this.pendingBeforeInit_, function(module) { this.load_(module); }, this);
  204. goog.array.clear(this.pendingBeforeInit_);
  205. };
  206. /**
  207. * Requests the loading of a symbol from a module. When the module is
  208. * loaded, the requested symbol will be passed as argument to the
  209. * function callback.
  210. *
  211. * @param {string} module The name of the module. Usually, the value
  212. * is defined as a constant whose name starts with MOD_.
  213. * @param {number|string} symbol The ID of the symbol. Usually, the value is
  214. * defined as a constant whose name starts with SYM_.
  215. * @param {Function} callback This function will be called with the
  216. * resolved symbol as the argument once the module is loaded.
  217. */
  218. goog.module.Loader.prototype.require = function(module, symbol, callback) {
  219. var pending = this.pending_;
  220. var modules = this.modules_;
  221. if (modules[module]) {
  222. // already loaded
  223. callback(modules[module][symbol]);
  224. } else if (pending[module]) {
  225. // loading is pending from another require of the same module
  226. pending[module].push([symbol, callback]);
  227. } else {
  228. // not loaded, and not requested
  229. pending[module] = [[symbol, callback]]; // Yes, really [[ ]].
  230. // Defer loading to initialization if Loader is not yet
  231. // initialized, otherwise load the module.
  232. if (goog.isString(this.urlBase_)) {
  233. this.load_(module);
  234. } else {
  235. this.pendingBeforeInit_.push(module);
  236. }
  237. }
  238. };
  239. /**
  240. * Registers a symbol in a loaded module. When called without symbol,
  241. * registers the module to be fully loaded and executes all callbacks
  242. * from pending require() callbacks for this module.
  243. *
  244. * @param {string} module The name of the module. Cf. parameter module
  245. * of method require().
  246. * @param {number|string=} opt_symbol The symbol being defined, or nothing when
  247. * all symbols of the module are defined. Cf. parameter symbol of method
  248. * require().
  249. * @param {Object=} opt_object The object bound to the symbol, or nothing when
  250. * all symbols of the module are defined.
  251. */
  252. goog.module.Loader.prototype.provide = function(
  253. module, opt_symbol, opt_object) {
  254. var modules = this.modules_;
  255. var pending = this.pending_;
  256. if (!modules[module]) {
  257. modules[module] = {};
  258. }
  259. if (opt_object) {
  260. // When an object is provided, just register it.
  261. modules[module][opt_symbol] = opt_object;
  262. } else if (pending[module]) {
  263. // When no object is provided, and there are pending require()
  264. // callbacks for this module, execute them.
  265. for (var i = 0; i < pending[module].length; ++i) {
  266. var symbol = pending[module][i][0];
  267. var callback = pending[module][i][1];
  268. callback(modules[module][symbol]);
  269. }
  270. delete pending[module];
  271. delete this.pendingModuleUrls_[module];
  272. }
  273. };
  274. /**
  275. * Starts to load a module. Assumes that init() was called.
  276. *
  277. * @param {string} module The name of the module.
  278. * @private
  279. */
  280. goog.module.Loader.prototype.load_ = function(module) {
  281. // NOTE(user): If the module request happens inside a click handler
  282. // (presumably inside any user event handler, but the onload event
  283. // handler is fine), IE will load the script but not execute
  284. // it. Thus we break out of the current flow of control before we do
  285. // the load. For the record, for IE it would have been enough to
  286. // just defer the assignment to src. Safari doesn't execute the
  287. // script if the assignment to src happens *after* the script
  288. // element is inserted into the DOM.
  289. goog.Timer.callOnce(function() {
  290. // The module might have been registered in the interim (if fetched as part
  291. // of another module fetch because they share the same url)
  292. if (this.modules_[module]) {
  293. return;
  294. }
  295. goog.asserts.assertString(this.urlBase_);
  296. var url = this.getModuleUrl_(this.urlBase_, module);
  297. // Check if specified URL is already in flight
  298. var urlInFlight = goog.object.containsValue(this.pendingModuleUrls_, url);
  299. this.pendingModuleUrls_[module] = url;
  300. if (urlInFlight) {
  301. return;
  302. }
  303. var s = goog.dom.createDom(
  304. goog.dom.TagName.SCRIPT, {'type': 'text/javascript', 'src': url});
  305. document.body.appendChild(s);
  306. }, 0, this);
  307. };