Path: blob/trunk/third_party/closure/goog/module/loader.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15*16* @fileoverview This class supports the dynamic loading of compiled17* javascript modules at runtime, as described in the designdoc.18*19* <http://go/js_modules_design>20*21*/2223goog.provide('goog.module.Loader');2425goog.require('goog.Timer');26goog.require('goog.array');27goog.require('goog.asserts');28goog.require('goog.dom');29goog.require('goog.dom.TagName');30/** @suppress {extraRequire} */31goog.require('goog.module');32goog.require('goog.object');33343536/**37* The dynamic loading functionality is defined as a class. The class38* will be used as singleton. There is, however, a two step39* initialization procedure because parameters need to be passed to40* the goog.module.Loader instance.41*42* @constructor43* @final44*/45goog.module.Loader = function() {46/**47* Map of module name/array of {symbol name, callback} pairs that are pending48* to be loaded.49* @type {Object}50* @private51*/52this.pending_ = {};5354/**55* Provides associative access to each module and the symbols of each module56* that have already been loaded (one lookup for the module, another lookup57* on the module for the symbol).58* @type {Object}59* @private60*/61this.modules_ = {};6263/**64* Map of module name to module url. Used to avoid fetching the same URL65* twice by keeping track of in-flight URLs.66* Note: this allows two modules to be bundled into the same file.67* @type {Object}68* @private69*/70this.pendingModuleUrls_ = {};7172/**73* The base url to load modules from. This property will be set in init().74* @type {?string}75* @private76*/77this.urlBase_ = null;7879/**80* Array of modules that have been requested before init() was called.81* If require() is called before init() was called, the required82* modules can obviously not yet be loaded, because their URL is83* unknown. The modules that are requested before init() are84* therefore stored in this array, and they are loaded at init()85* time.86* @type {Array<string>}87* @private88*/89this.pendingBeforeInit_ = [];90};91goog.addSingletonGetter(goog.module.Loader);929394/**95* Wrapper of goog.module.Loader.require() for use in modules.96* See method goog.module.Loader.require() for97* explanation of params.98*99* @param {string} module The name of the module. Usually, the value100* is defined as a constant whose name starts with MOD_.101* @param {number|string} symbol The ID of the symbol. Usually, the value is102* defined as a constant whose name starts with SYM_.103* @param {Function} callback This function will be called with the104* resolved symbol as the argument once the module is loaded.105*/106goog.module.Loader.require = function(module, symbol, callback) {107goog.module.Loader.getInstance().require(module, symbol, callback);108};109110111/**112* Wrapper of goog.module.Loader.provide() for use in modules113* See method goog.module.Loader.provide() for explanation of params.114*115* @param {string} module The name of the module. Cf. parameter module116* of method require().117* @param {number|string=} opt_symbol The symbol being defined, or nothing118* when all symbols of the module are defined. Cf. parameter symbol of119* method require().120* @param {Object=} opt_object The object bound to the symbol, or nothing when121* all symbols of the module are defined.122*/123goog.module.Loader.provide = function(module, opt_symbol, opt_object) {124goog.module.Loader.getInstance().provide(module, opt_symbol, opt_object);125};126127128/**129* Wrapper of init() so that we only need to export this single130* identifier instead of three. See method goog.module.Loader.init() for131* explanation of param.132*133* @param {string} urlBase The URL of the base library.134* @param {Function=} opt_urlFunction Function that creates the URL for the135* module file. It will be passed the base URL for module files and the136* module name and should return the fully-formed URL to the module file to137* load.138*/139goog.module.Loader.init = function(urlBase, opt_urlFunction) {140goog.module.Loader.getInstance().init(urlBase, opt_urlFunction);141};142143144/**145* Produces a function that delegates all its arguments to a146* dynamically loaded function. This is used to export dynamically147* loaded functions.148*149* @param {string} module The module to load from.150* @param {number|string} symbol The ID of the symbol to load from the module.151* This symbol must resolve to a function.152* @return {!Function} A function that forwards all its arguments to153* the dynamically loaded function specified by module and symbol.154*/155goog.module.Loader.loaderCall = function(module, symbol) {156return function() {157var args = arguments;158goog.module.Loader.require(159module, symbol, function(f) { f.apply(null, args); });160};161};162163164/**165* Creates a full URL to the compiled module code given a base URL and a166* module name. By default it's urlBase + '_' + module + '.js'.167* @param {string} urlBase URL to the module files.168* @param {string} module Module name.169* @return {string} The full url to the module binary.170* @private171*/172goog.module.Loader.prototype.getModuleUrl_ = function(urlBase, module) {173return urlBase + '_' + module + '.js';174};175176177/**178* The globally exported name of the load callback. Matches the179* definition in the js_module_binary() BUILD rule.180* @type {string}181*/182goog.module.Loader.LOAD_CALLBACK = '__gjsload__';183184185/**186* Loads the module by evaluating the javascript text in the current187* scope. Uncompiled, base identifiers are visible in the global scope;188* when compiled they are visible in the closure of the anonymous189* namespace. Notice that this cannot be replaced by the global eval,190* because the global eval isn't in the scope of the anonymous191* namespace function that the jscompiled code lives in.192*193* @param {string} t_ The javascript text to evaluate. IMPORTANT: The194* name of the identifier is chosen so that it isn't compiled and195* hence cannot shadow compiled identifiers in the surrounding scope.196* @private197*/198goog.module.Loader.loaderEval_ = function(t_) {199eval(t_);200};201202203/**204* Initializes the Loader to be fully functional. Also executes load205* requests that were received before initialization. Must be called206* exactly once, with the URL of the base library. Module URLs are207* derived from the URL of the base library by inserting the module208* name, preceded by a period, before the .js prefix of the base URL.209*210* @param {string} baseUrl The URL of the base library.211* @param {Function=} opt_urlFunction Function that creates the URL for the212* module file. It will be passed the base URL for module files and the213* module name and should return the fully-formed URL to the module file to214* load.215*/216goog.module.Loader.prototype.init = function(baseUrl, opt_urlFunction) {217// For the use by the module wrappers, loaderEval_ is exported to218// the page. Note that, despite the name, this is not part of the219// API, so it is here and not in api_app.js. Cf. BUILD. Note this is220// done before the first load requests are sent.221goog.exportSymbol(222goog.module.Loader.LOAD_CALLBACK, goog.module.Loader.loaderEval_);223224this.urlBase_ = baseUrl.replace(/\.js$/, '');225if (opt_urlFunction) {226this.getModuleUrl_ = opt_urlFunction;227}228229goog.array.forEach(230this.pendingBeforeInit_, function(module) { this.load_(module); }, this);231goog.array.clear(this.pendingBeforeInit_);232};233234235/**236* Requests the loading of a symbol from a module. When the module is237* loaded, the requested symbol will be passed as argument to the238* function callback.239*240* @param {string} module The name of the module. Usually, the value241* is defined as a constant whose name starts with MOD_.242* @param {number|string} symbol The ID of the symbol. Usually, the value is243* defined as a constant whose name starts with SYM_.244* @param {Function} callback This function will be called with the245* resolved symbol as the argument once the module is loaded.246*/247goog.module.Loader.prototype.require = function(module, symbol, callback) {248var pending = this.pending_;249var modules = this.modules_;250if (modules[module]) {251// already loaded252callback(modules[module][symbol]);253} else if (pending[module]) {254// loading is pending from another require of the same module255pending[module].push([symbol, callback]);256} else {257// not loaded, and not requested258pending[module] = [[symbol, callback]]; // Yes, really [[ ]].259// Defer loading to initialization if Loader is not yet260// initialized, otherwise load the module.261if (goog.isString(this.urlBase_)) {262this.load_(module);263} else {264this.pendingBeforeInit_.push(module);265}266}267};268269270/**271* Registers a symbol in a loaded module. When called without symbol,272* registers the module to be fully loaded and executes all callbacks273* from pending require() callbacks for this module.274*275* @param {string} module The name of the module. Cf. parameter module276* of method require().277* @param {number|string=} opt_symbol The symbol being defined, or nothing when278* all symbols of the module are defined. Cf. parameter symbol of method279* require().280* @param {Object=} opt_object The object bound to the symbol, or nothing when281* all symbols of the module are defined.282*/283goog.module.Loader.prototype.provide = function(284module, opt_symbol, opt_object) {285var modules = this.modules_;286var pending = this.pending_;287if (!modules[module]) {288modules[module] = {};289}290if (opt_object) {291// When an object is provided, just register it.292modules[module][opt_symbol] = opt_object;293} else if (pending[module]) {294// When no object is provided, and there are pending require()295// callbacks for this module, execute them.296for (var i = 0; i < pending[module].length; ++i) {297var symbol = pending[module][i][0];298var callback = pending[module][i][1];299callback(modules[module][symbol]);300}301delete pending[module];302delete this.pendingModuleUrls_[module];303}304};305306307/**308* Starts to load a module. Assumes that init() was called.309*310* @param {string} module The name of the module.311* @private312*/313goog.module.Loader.prototype.load_ = function(module) {314// NOTE(user): If the module request happens inside a click handler315// (presumably inside any user event handler, but the onload event316// handler is fine), IE will load the script but not execute317// it. Thus we break out of the current flow of control before we do318// the load. For the record, for IE it would have been enough to319// just defer the assignment to src. Safari doesn't execute the320// script if the assignment to src happens *after* the script321// element is inserted into the DOM.322goog.Timer.callOnce(function() {323// The module might have been registered in the interim (if fetched as part324// of another module fetch because they share the same url)325if (this.modules_[module]) {326return;327}328329goog.asserts.assertString(this.urlBase_);330var url = this.getModuleUrl_(this.urlBase_, module);331332// Check if specified URL is already in flight333var urlInFlight = goog.object.containsValue(this.pendingModuleUrls_, url);334this.pendingModuleUrls_[module] = url;335if (urlInFlight) {336return;337}338339var s = goog.dom.createDom(340goog.dom.TagName.SCRIPT, {'type': 'text/javascript', 'src': url});341document.body.appendChild(s);342}, 0, this);343};344345346