goog.module.moduleloader.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of google-closure-library
Show all versions of google-closure-library
The Google Closure Library is a collection of JavaScript code
designed for use with the Google Closure JavaScript Compiler.
This non-official distribution was prepared by the ClojureScript
team at http://clojure.org/
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The module loader for loading modules across the network.
*
* Browsers do not guarantee that scripts appended to the document
* are executed in the order they are added. For production mode, we use
* XHRs to load scripts, because they do not have this problem and they
* have superior mechanisms for handling failure. However, XHR-evaled
* scripts are harder to debug.
*
* In debugging mode, we use normal script tags. In order to make this work,
* we load the scripts in serial: we do not execute script B to the document
* until we are certain that script A is finished loading.
*
*/
goog.provide('goog.module.ModuleLoader');
goog.require('goog.Timer');
goog.require('goog.array');
goog.require('goog.events');
goog.require('goog.events.Event');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventId');
goog.require('goog.events.EventTarget');
goog.require('goog.labs.userAgent.browser');
goog.require('goog.log');
goog.require('goog.module.AbstractModuleLoader');
goog.require('goog.net.BulkLoader');
goog.require('goog.net.EventType');
goog.require('goog.net.jsloader');
goog.require('goog.userAgent');
goog.require('goog.userAgent.product');
/**
* A class that loads Javascript modules.
* @constructor
* @extends {goog.events.EventTarget}
* @implements {goog.module.AbstractModuleLoader}
*/
goog.module.ModuleLoader = function() {
goog.module.ModuleLoader.base(this, 'constructor');
/**
* Event handler for managing handling events.
* @type {goog.events.EventHandler}
* @private
*/
this.eventHandler_ = new goog.events.EventHandler(this);
/**
* A map from module IDs to goog.module.ModuleLoader.LoadStatus.
* @type {!Object, goog.module.ModuleLoader.LoadStatus>}
* @private
*/
this.loadingModulesStatus_ = {};
};
goog.inherits(goog.module.ModuleLoader, goog.events.EventTarget);
/**
* A logger.
* @type {goog.log.Logger}
* @protected
*/
goog.module.ModuleLoader.prototype.logger =
goog.log.getLogger('goog.module.ModuleLoader');
/**
* Whether debug mode is enabled.
* @type {boolean}
* @private
*/
goog.module.ModuleLoader.prototype.debugMode_ = false;
/**
* Whether source url injection is enabled.
* @type {boolean}
* @private
*/
goog.module.ModuleLoader.prototype.sourceUrlInjection_ = false;
/**
* @return {boolean} Whether sourceURL affects stack traces.
*/
goog.module.ModuleLoader.supportsSourceUrlStackTraces = function() {
return goog.userAgent.product.CHROME ||
(goog.labs.userAgent.browser.isFirefox() &&
goog.labs.userAgent.browser.isVersionOrHigher('36'));
};
/**
* @return {boolean} Whether sourceURL affects the debugger.
*/
goog.module.ModuleLoader.supportsSourceUrlDebugger = function() {
return goog.userAgent.product.CHROME || goog.userAgent.GECKO;
};
/**
* Gets the debug mode for the loader.
* @return {boolean} Whether the debug mode is enabled.
*/
goog.module.ModuleLoader.prototype.getDebugMode = function() {
return this.debugMode_;
};
/**
* Sets the debug mode for the loader.
* @param {boolean} debugMode Whether the debug mode is enabled.
*/
goog.module.ModuleLoader.prototype.setDebugMode = function(debugMode) {
this.debugMode_ = debugMode;
};
/**
* When enabled, we will add a sourceURL comment to the end of all scripts
* to mark their origin.
*
* On WebKit, stack traces will reflect the sourceURL comment, so this is
* useful for debugging webkit stack traces in production.
*
* Notice that in debug mode, we will use source url injection + eval rather
* then appending script nodes to the DOM, because the scripts will load far
* faster. (Appending script nodes is very slow, because we can't parallelize
* the downloading and evaling of the script).
*
* The cost of appending sourceURL information is negligible when compared to
* the cost of evaling the script. Almost all clients will want this on.
*
* TODO(nicksantos): Turn this on by default. We may want to turn this off
* for clients that inject their own sourceURL.
*
* @param {boolean} enabled Whether source url injection is enabled.
*/
goog.module.ModuleLoader.prototype.setSourceUrlInjection = function(enabled) {
this.sourceUrlInjection_ = enabled;
};
/**
* @return {boolean} Whether we're using source url injection.
* @private
*/
goog.module.ModuleLoader.prototype.usingSourceUrlInjection_ = function() {
return this.sourceUrlInjection_ ||
(this.getDebugMode() &&
goog.module.ModuleLoader.supportsSourceUrlStackTraces());
};
/** @override */
goog.module.ModuleLoader.prototype.loadModules = function(
ids, moduleInfoMap, opt_successFn, opt_errorFn, opt_timeoutFn,
opt_forceReload) {
var loadStatus = this.loadingModulesStatus_[ids] ||
new goog.module.ModuleLoader.LoadStatus();
loadStatus.loadRequested = true;
loadStatus.successFn = opt_successFn || null;
loadStatus.errorFn = opt_errorFn || null;
if (!this.loadingModulesStatus_[ids]) {
// Modules were not prefetched.
this.loadingModulesStatus_[ids] = loadStatus;
this.downloadModules_(ids, moduleInfoMap);
// TODO(user): Need to handle timeouts in the module loading code.
} else if (goog.isDefAndNotNull(loadStatus.responseTexts)) {
// Modules prefetch is complete.
this.evaluateCode_(ids);
}
// Otherwise modules prefetch is in progress, and these modules will be
// executed after the prefetch is complete.
};
/**
* Evaluate the JS code.
* @param {Array} moduleIds The module ids.
* @private
*/
goog.module.ModuleLoader.prototype.evaluateCode_ = function(moduleIds) {
this.dispatchEvent(
new goog.module.ModuleLoader.RequestSuccessEvent(moduleIds));
goog.log.info(this.logger, 'evaluateCode ids:' + moduleIds);
var loadStatus = this.loadingModulesStatus_[moduleIds];
var uris = loadStatus.requestUris;
var texts = loadStatus.responseTexts;
var error = null;
try {
if (this.usingSourceUrlInjection_()) {
for (var i = 0; i < uris.length; i++) {
var uri = uris[i];
goog.globalEval(texts[i] + ' //# sourceURL=' + uri);
}
} else {
goog.globalEval(texts.join('\n'));
}
} catch (e) {
error = e;
// TODO(user): Consider throwing an exception here.
goog.log.warning(
this.logger, 'Loaded incomplete code for module(s): ' + moduleIds, e);
}
this.dispatchEvent(new goog.module.ModuleLoader.EvaluateCodeEvent(moduleIds));
if (error) {
this.handleErrorHelper_(
moduleIds, loadStatus.errorFn, null /* status */, error);
} else if (loadStatus.successFn) {
loadStatus.successFn();
}
delete this.loadingModulesStatus_[moduleIds];
};
/**
* Handles a successful response to a request for prefetch or load one or more
* modules.
*
* @param {goog.net.BulkLoader} bulkLoader The bulk loader.
* @param {Array} moduleIds The ids of the modules requested.
* @private
*/
goog.module.ModuleLoader.prototype.handleSuccess_ = function(
bulkLoader, moduleIds) {
goog.log.info(this.logger, 'Code loaded for module(s): ' + moduleIds);
var loadStatus = this.loadingModulesStatus_[moduleIds];
loadStatus.responseTexts = bulkLoader.getResponseTexts();
if (loadStatus.loadRequested) {
this.evaluateCode_(moduleIds);
}
// NOTE: A bulk loader instance is used for loading a set of module ids.
// Once these modules have been loaded successfully or in error the bulk
// loader should be disposed as it is not needed anymore. A new bulk loader
// is instantiated for any new modules to be loaded. The dispose is called
// on a timer so that the bulkloader has a chance to release its
// objects.
goog.Timer.callOnce(bulkLoader.dispose, 5, bulkLoader);
};
/** @override */
goog.module.ModuleLoader.prototype.prefetchModule = function(id, moduleInfo) {
// Do not prefetch in debug mode.
if (this.getDebugMode()) {
return;
}
var loadStatus = this.loadingModulesStatus_[[id]];
if (loadStatus) {
return;
}
var moduleInfoMap = {};
moduleInfoMap[id] = moduleInfo;
this.loadingModulesStatus_[[id]] = new goog.module.ModuleLoader.LoadStatus();
this.downloadModules_([id], moduleInfoMap);
};
/**
* Downloads a list of JavaScript modules.
*
* @param {Array} ids The module ids in dependency order.
* @param {Object} moduleInfoMap A mapping from module id to ModuleInfo object.
* @private
*/
goog.module.ModuleLoader.prototype.downloadModules_ = function(
ids, moduleInfoMap) {
var uris = [];
for (var i = 0; i < ids.length; i++) {
goog.array.extend(uris, moduleInfoMap[ids[i]].getUris());
}
goog.log.info(this.logger, 'downloadModules ids:' + ids + ' uris:' + uris);
if (this.getDebugMode() && !this.usingSourceUrlInjection_()) {
// In debug mode use