lfr-amd-loader
Version:
AMD Loader with support for combo URL and conditional loading
233 lines (188 loc) • 8.05 kB
JavaScript
(function (global, factory) {
'use strict';
var built = factory(global);
/* istanbul ignore else */
if (typeof module === 'object' && module) {
module.exports = built;
}
/* istanbul ignore next */
if (typeof define === 'function' && define.amd) {
define(factory);
}
global.DependencyBuilder = built;
}(typeof global !== 'undefined' ? global : /* istanbul ignore next */ this, function (global) {
'use strict';
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Creates an instance of DependencyBuilder class.
*
* @constructor
* @param {object} - instance of {@link ConfigParser} object.
*/
function DependencyBuilder(configParser) {
this._configParser = configParser;
this._pathResolver = new global.PathResolver();
this._result = [];
}
DependencyBuilder.prototype = {
constructor: DependencyBuilder,
/**
* Resolves modules dependencies.
*
* @param {array} modules List of modules which dependencies should be resolved.
* @return {array} List of module names, representing module dependencies. Module name itself is being returned too.
*/
resolveDependencies: function (modules) {
// Copy the passed modules to a resolving modules queue.
// Modules may be added there during the process of resolving.
this._queue = modules.slice(0);
var result;
try {
this._resolveDependencies();
// Reorder the modules list so the modules without dependencies will
// be moved upfront
result = this._result.reverse().slice(0);
}
finally {
this._cleanup();
}
return result;
},
/**
* Clears the used resources during the process of resolving dependencies.
*
* @protected
*/
_cleanup: function () {
var modules = this._configParser.getModules();
// Set to false all temporary markers which were set during the process of
// dependencies resolving.
for (var key in modules) { /* istanbul ignore else */
if (hasOwnProperty.call(modules, key)) {
var module = modules[key];
module.conditionalMark = false;
module.mark = false;
module.tmpMark = false;
}
}
this._queue.length = 0;
this._result.length = 0;
},
/**
* Processes conditional modules. If a module has conditional module as dependency, this module will be added to
* the list of modules, which dependencies should be resolved.
*
* @protected
* @param {object} module Module, which will be checked for conditional modules as dependencies.
*/
_processConditionalModules: function (module) {
var conditionalModules = this._configParser.getConditionalModules()[module.name];
// If the current module has conditional modules as dependencies,
// add them to the list (queue) of modules, which have to be resolved.
if (conditionalModules && !module.conditionalMark) {
var modules = this._configParser.getModules();
for (var i = 0; i < conditionalModules.length; i++) {
var conditionalModule = modules[conditionalModules[i]];
if (this._queue.indexOf(conditionalModule.name) === -1 && this._testConditionalModule(conditionalModule.condition.test)) {
this._queue.push(conditionalModule.name);
}
}
module.conditionalMark = true;
}
},
/**
* Processes all modules in the {@link DependencyBuilder#_queue} and resolves their dependencies.
* If the module is not registered to the configuration, it will be automatically added there with no
* dependencies. The function implements a standard
* [topological sorting based on depth-first search]{@link http://en.wikipedia.org/wiki/Topological_sorting}.
*
* @protected
*/
_resolveDependencies: function () {
// Process all modules in the queue.
// Note: modules may be added to the queue during the process of evaluating.
var modules = this._configParser.getModules();
for (var i = 0; i < this._queue.length; i++) {
var module = modules[this._queue[i]];
// If not registered, add the module on the fly with no dependencies.
// Note: the module name (this._queue[i]) is expected to be already mapped.
if (!module) {
module = this._configParser.addModule({
name: this._queue[i],
dependencies: []
});
}
if (!module.mark) {
this._visit(module);
}
}
},
/**
* Executes the test function of an conditional module and adds it to the list of module dependencies if the
* function returns true.
*
* @param {function|string} testFunction The function which have to be executed. May be Function object or string.
* @return {boolean} The result of the execution of the test function.
*/
_testConditionalModule: function (testFunction) {
if (typeof testFunction === 'function') {
return testFunction();
} else {
return eval('false || ' + testFunction)();
}
},
/**
* Visits a module during the process of resolving dependencies. The function will throw exception in case of
* circular dependencies among modules. If a dependency is not registered, it will be added to the configuration
* as a module without dependencies.
*
* @protected
* @param {object} module The module which have to be visited.
*/
_visit: function(module) {
// Directed Acyclic Graph is supported only, throw exception if there are circular dependencies.
if (module.tmpMark) {
throw new Error('Error processing module: ' + module.name + '. ' + 'The provided configuration is not Directed Acyclic Graph.');
}
// Check if this module has conditional modules and add them to the queue if so.
this._processConditionalModules(module);
if (!module.mark) {
module.tmpMark = true;
var modules = this._configParser.getModules();
for (var i = 0; i < module.dependencies.length; i++) {
var dependencyName = module.dependencies[i];
if (dependencyName === 'exports' || dependencyName === 'module') {
continue;
}
// Resolve relative path and map the dependency to its alias
dependencyName = this._pathResolver.resolvePath(module.name, dependencyName);
// A module may have many dependencies so we should map them.
var mappedDependencyName = this._configParser.mapModule(dependencyName);
var moduleDependency = modules[mappedDependencyName];
// Register on the fly all unregistered in the configuration dependencies as modules without dependencies.
if (!moduleDependency) {
moduleDependency = this._configParser.addModule({
name: mappedDependencyName,
dependencies: []
});
}
this._visit(moduleDependency);
}
module.mark = true;
module.tmpMark = false;
this._result.unshift(module.name);
}
},
/**
* @property {array} _queue List of modules, which dependencies should be resolved. Initially, it is copy of
* the array of modules, passed for resolving; during the process more modules may be added to the queue. For
* example, these might be conditional modules.
*
* @protected
* @memberof! DependencyBuilder#
* @default []
*/
_queue: []
};
return DependencyBuilder;
}));