UNPKG

@oat-sa/tao-core-sdk

Version:
270 lines (254 loc) 10.3 kB
define(['require', 'lodash', 'core/promise'], function (require, _, Promise) { 'use strict'; function _interopNamespace(e) { if (e && e.__esModule) { return e; } else { var n = {}; if (e) { Object.keys(e).forEach(function (k) { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); }); } n['default'] = e; return n; } } _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _; Promise = Promise && Object.prototype.hasOwnProperty.call(Promise, 'default') ? Promise['default'] : Promise; /** * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; under version 2 * of the License (non-upgradable). * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Copyright (c) 2017-2019 (original work) Open Assessment Technologies SA ; */ /** * The data required by the modules loader * * @typedef {Object} moduleDefinition * @property {String} module - AMD module name * @property {String} bundle - AMD module name of the bundle that should contain the module * @property {String} category - the module category * @property {String} name - the module name * @property {String|Number} [position = 'append'] - append, prepend or arbitrary position within the category */ /** * Creates a loader with the list of required modules * @param {Object} requiredModules - A collection of mandatory modules, where the key is the category and the value are an array of loaded modules * @param {Function} [validate] - A validator function, by default the module should be an object * @param {Object} [specs] - Some extra methods to assign to the loader instance * @returns {loader} the provider loader * @throws TypeError if something is not well formatted */ function moduleLoaderFactory(requiredModules, validate, specs) { /** * The list of loaded modules */ const loaded = {}; /** * Retains the AMD modules to load */ const modules = {}; /** * The modules to exclude */ const excludes = []; /** * Bundles to require */ const bundles = []; /** * The module loader * @typedef {loader} */ const loader = { /** * Adds a list of dynamic modules to load * @param {moduleDefinition[]} moduleList - the modules to add * @returns {loader} chains * @throws {TypeError} misuse */ addList(moduleList) { _.forEach(moduleList, this.add, this); return this; }, /** * Adds a dynamic module to load * @param {moduleDefinition} def - the module to add * @returns {loader} chains * @throws {TypeError} misuse */ add(def) { if (!_.isPlainObject(def)) { throw new TypeError('The module definition module must be an object'); } if (_.isEmpty(def.module) || !_.isString(def.module)) { throw new TypeError('An AMD module must be defined'); } if (_.isEmpty(def.category) || !_.isString(def.category)) { const identifyProvider = def.id || def.name || def.module; throw new TypeError(`The provider '${identifyProvider}' must belong to a category`); } modules[def.category] = modules[def.category] || []; if (_.isNumber(def.position)) { modules[def.category][def.position] = def.module; } else if (def.position === 'prepend' || def.position === 'before') { modules[def.category].unshift(def.module); } else { modules[def.category].push(def.module); } if (def.bundle && !_.includes(bundles, def.bundle)) { bundles.push(def.bundle); } return this; }, /** * Appends a dynamic module * @param {moduleDefinition} def - the module to add * @returns {loader} chains * @throws {TypeError} misuse */ append(def) { return this.add(_.merge({ position: 'append' }, def)); }, /** * Prepends a dynamic module to a category * @param {moduleDefinition} def - the module to add * @returns {loader} chains * @throws {TypeError} misuse */ prepend(def) { return this.add(_.merge({ position: 'prepend' }, def)); }, /** * Removes a module from the loading stack * @param {String} module - the module's module * @returns {loader} chains * @throws {TypeError} misuse */ remove(module) { excludes.push(module); return this; }, /** * Loads the dynamic modules : trigger the dependency resolution * @param {Boolean} [loadBundles=false] - does load the bundles * @returns {Promise} */ load(loadBundles) { //compute the providers dependencies const dependencies = _(modules).values().flatten().uniq().difference(excludes).value(); /** * Loads AMD modules and wrap then into a Promise * @param {String[]} amdModules - the list of modules to require * @returns {Promise} resolves with the loaded modules */ const loadModules = function () { let amdModules = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; if (_.isArray(amdModules) && amdModules.length) { if (typeof window.define === 'function' && window.define.amd) { return new Promise((resolve, reject) => { window.require(amdModules, function () { for (var _len = arguments.length, loadedModules = new Array(_len), _key = 0; _key < _len; _key++) { loadedModules[_key] = arguments[_key]; } return resolve(loadedModules); }, err => { reject(err); }); }); } else { return Promise.all(amdModules.map(module => new Promise(function (resolve, reject) { require([ /* webpackIgnore: true */`${module}`], function (m) { resolve(_interopNamespace(m)); }, reject) }))).then(loadedModules => loadedModules.map(module => module.default)); } } return Promise.resolve(); }; // 1. load bundles // 2. load dependencies // 3. add them to the modules list return loadModules(loadBundles ? bundles : []).then(() => loadModules(dependencies)).then(loadedModules => { _.forEach(dependencies, (dependency, index) => { const module = loadedModules[index]; const category = _.findKey(modules, val => _.includes(val, dependency)); if (typeof validate === 'function' && !validate(module)) { throw new TypeError(`The module '${dependency}' is not valid`); } if (_.isString(category)) { loaded[category] = loaded[category] || []; loaded[category].push(module); } }); return this.getModules(); }); }, /** * Get the resolved list of modules. * Load needs to be called before to have the dynamic modules. * @param {String} [category] - to get the modules for a given category, if not set, we get everything * @returns {Object[]} the modules */ getModules(category) { if (_.isString(category)) { return loaded[category] || []; } return _(loaded).values().flatten().uniq().value(); }, /** * Get the module categories * @returns {String[]} the categories */ getCategories() { return _.keys(loaded); } }; validate = _.isFunction(validate) ? validate : _.isPlainObject; //verify and add the required modules _.forEach(requiredModules, function (moduleList, category) { if (_.isEmpty(category) || !_.isString(category)) { throw new TypeError('Modules must belong to a category'); } if (!_.isArray(moduleList)) { throw new TypeError('A list of modules must be an array'); } if (!_.every(moduleList, validate)) { throw new TypeError('The list does not contain valid modules'); } if (loaded[category]) { loaded[category] = loaded[category].concat(moduleList); } else { loaded[category] = moduleList; } }); // let's extend the instance with extra methods if (specs) { _(specs).functions().forEach(function (method) { loader[method] = function () { for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } return specs[method].apply(loader, args); }; }); } return loader; } return moduleLoaderFactory; });