@liferay/amd-loader
Version:
AMD Loader with support for combo URL and conditional loading
377 lines (319 loc) • 9.41 kB
JavaScript
/**
* SPDX-FileCopyrightText: © 2014 Liferay, Inc. <https://liferay.com>
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
import Module from './module';
/**
*
*/
export default class Config {
/**
* Creates an instance of Configuration class
* @constructor
* @param {object=} cfg configuration properties
*/
constructor(cfg = {}) {
this._modules = {};
this._maps = {};
this._paths = {};
this._config = {maps: {}, paths: {}};
this._parse(cfg, 'defaultURLParams', {});
this._parse(cfg, 'explainResolutions', false);
this._parse(cfg, 'showWarnings', false);
this._parse(cfg, 'waitTimeout', 7000);
this._parse(cfg, 'basePath', '/');
this._parse(cfg, 'resolvePath', '/o/js_resolve_modules');
this._parse(cfg, 'combine', false);
this._parse(cfg, 'nonce', '');
this._parse(cfg, 'url', '');
this._parse(cfg, 'urlMaxLength', 2000);
this._parse(cfg, 'logLevel', 'error');
this._parse(cfg, 'moduleType', 'module');
}
/**
* Whether to explain how require() calls are resolved
*/
get explainResolutions() {
return this._config.explainResolutions;
}
/**
* Whether to show development warnings
*/
get showWarnings() {
return this._config.showWarnings;
}
/**
* Time to wait for module script requests to load (in milliseconds)
*/
get waitTimeout() {
return this._config.waitTimeout;
}
/**
* The base path from where modules must be retrieved
*/
get basePath() {
return this._config.basePath;
}
/**
* The path to use when calling the server to resolve module dependencies
*/
get resolvePath() {
return this._config.resolvePath;
}
/**
* Whether to combine module requests into combo URLs
*/
get combine() {
return this._config.combine;
}
/**
* The URL of the server
*/
get url() {
return this._config.url;
}
/**
* The maximum length of a combo URL. If URL is larger than that it is split
* in as many requests as needed.
*/
get urlMaxLength() {
return this._config.urlMaxLength;
}
get logLevel() {
return this._config.logLevel;
}
/**
* The type to use for ESM <script> nodes.
*/
get moduleType() {
return this._config.moduleType;
}
/**
* Default parameters to add to the module request URLs
*/
get defaultURLParams() {
return this._config.defaultURLParams;
}
/**
* An object with registered module paths
*/
get paths() {
return this._paths;
}
get nonce() {
return this._config.nonce;
}
/**
* Adds a module to the configuration with default field values if it
* doesn't exist. Otherwise, throws an exception.
* @param {string} moduleName
* @param {object} moduleProperties initial properties to set on module in
* addition to its name
* @return {Object} the module
*/
addModule(moduleName, moduleProperties = {}) {
if (this._modules[moduleName]) {
throw new Error(`Module is already registered: ${moduleName}`);
}
const module = new Module(moduleName);
Object.entries(moduleProperties).forEach(([key, value]) => {
module[key] = value;
});
this._modules[moduleName] = module;
return module;
}
/**
* Add mappings to the current configuration
* @param {object} mappings an object with one or more mappings
*/
addMappings(mappings) {
Object.assign(this._maps, mappings);
}
/**
* Add path mappings to the current configuration
* @param {object} paths an object with one or more path mappings
*/
addPaths(paths) {
Object.assign(this._paths, paths);
}
/**
* Returns array with all registered modules or the requested subset of
* them.
* @param {?Array} moduleNames optional list of module names to retrieve
* @return {Array}
*/
getModules(moduleNames = undefined) {
if (moduleNames === undefined) {
return Object.values(this._modules);
}
return moduleNames.map((moduleName) => this.getModule(moduleName));
}
/**
* Returns the registered module for the moduleName.
* @param {string} moduleName the module name
* @return {Object} the registed module object
*/
getModule(moduleName) {
let module = this._modules[moduleName];
if (!module) {
const mappedName = this._mapModule(moduleName);
module = this._modules[mappedName];
}
return module;
}
/**
* Returns the registered module for the dependency of moduleName.
* @param {string} moduleName the module name
* @param {string} dependencyName the dependencyName name
* @return {Object} the registed module object
*/
getDependency(moduleName, dependencyName) {
const module = this.getModule(moduleName);
let dependencyModule = this._modules[dependencyName];
if (!dependencyModule) {
const mappedName = this._mapModule(dependencyName, module.map);
dependencyModule = this._modules[mappedName];
}
return dependencyModule;
}
/**
* Update the resolve path (usually as a consequence of a redirect being
* received from the server when a module resolution is attempted).
* @param {string} resolvePath the updated resolve path
* @return {void}
*/
updateResolvePath(resolvePath) {
const prefix = `${window.location.protocol}//${window.location.host}`;
if (resolvePath.startsWith(prefix)) {
resolvePath = resolvePath.substr(prefix.length);
}
this._config.resolvePath = resolvePath;
}
/**
* Parse a configuration property to store it in _config.
* @param {object} cfg
* @param {string} property
* @param {*} defaultValue
*/
_parse(cfg, property, defaultValue) {
this._config[property] = Object.prototype.hasOwnProperty.call(
cfg,
property
)
? cfg[property]
: defaultValue;
}
/**
* Maps module names to their aliases. Example:
* __CONFIG__.maps = {
* liferay: 'liferay@1.0.0'
* }
*
* When someone does require('liferay/html/js/ac.es',...),
* if the module 'liferay/html/js/ac.es' is not defined,
* then a corresponding alias will be searched. If found, the name will be
* replaced, so it will look like user did
* require('liferay@1.0.0/html/js/ac.es',...).
*
* Additionally, modules can define a custom map to alias module names just
* in the context of that module loading operation. When present, the
* contextual module mapping will take precedence over the general one.
* @param {string} moduleName The module which have to be mapped
* @param {?object} contextMap Contextual module mapping information
* relevant to the current load operation
* @return {array} The mapped module
*/
_mapModule(moduleName, contextMap) {
if (contextMap) {
moduleName = this._mapMatches(moduleName, contextMap);
}
if (Object.keys(this._maps).length) {
moduleName = this._mapMatches(moduleName, this._maps);
}
return moduleName;
}
/**
* Creates a function that transforms module names based on a provided
* set of mappings.
* @param {string} moduleName module name
* @param {object} maps Mapping information.
* @return {function} The generated mapper function
*/
_mapMatches(moduleName, maps) {
let match = maps[moduleName];
if (match) {
if (typeof match === 'object') {
return match.value;
}
return match;
}
match = this._mapExactMatch(moduleName, maps);
// Apply partial mapping only if exactMatch hasn't been
// already applied for this mapping
if (!match) {
match = this._mapPartialMatch(moduleName, maps);
}
// Apply * mapping only if neither exactMatch nor
// partialMatch have been already applied for this mapping
if (!match) {
match = this._mapWildcardMatch(moduleName, maps);
}
return match || moduleName;
}
/**
* Transforms a module name using the exactMatch mappings
* in a provided mapping object.
* @param {string} module The module which have to be mapped.
* @param {object} maps Mapping information.
* @return {object} An object with a boolean `matched` field and a string
* `result` field containing the mapped module name
*/
_mapExactMatch(module, maps) {
for (const alias in maps) {
if (Object.prototype.hasOwnProperty.call(maps, alias)) {
const aliasValue = maps[alias];
if (aliasValue.value && aliasValue.exactMatch) {
if (module === alias) {
return aliasValue.value;
}
}
}
}
}
/**
* Transforms a module name using the partial mappings
* in a provided mapping object.
* @param {string} module The module which have to be mapped.
* @param {object} maps Mapping information.
* @return {object} An object with a boolean `matched` field and a string
* `result` field containing the mapped module name
*/
_mapPartialMatch(module, maps) {
for (const alias in maps) {
if (Object.prototype.hasOwnProperty.call(maps, alias)) {
let aliasValue = maps[alias];
if (!aliasValue.exactMatch) {
if (aliasValue.value) {
aliasValue = aliasValue.value;
}
if (module === alias || module.indexOf(alias + '/') === 0) {
return aliasValue + module.substring(alias.length);
}
}
}
}
}
/**
* Transforms a module name using the wildcard mapping in a provided mapping
* object.
* @param {string} module The module which have to be mapped.
* @param {object} maps Mapping information.
* @return {object} An object with a boolean `matched` field and a string
* `result` field containing the mapped module name
*/
_mapWildcardMatch(module, maps) {
if (typeof maps['*'] === 'function') {
return maps['*'](module);
}
}
}