@liferay/amd-loader
Version:
AMD Loader with support for combo URL and conditional loading
135 lines (108 loc) • 3.23 kB
JavaScript
/**
* SPDX-FileCopyrightText: © 2014 Liferay, Inc. <https://liferay.com>
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
import URLBuilder from './url-builder';
/**
* A class responsible for loading the script resources that contain modules
* from the server.
*/
export default class ScriptLoader {
/**
* @param {object} document DOM document object to use
* @param {Config} config
* @param {Logger} log
*/
constructor(document, config, log) {
this._document = document;
this._config = config;
this._log = log;
this._urlBuilder = new URLBuilder(config);
this._injectedScripts = {};
}
/**
* Loads list of modules
* @param {Array} moduleNames List of modules to be loaded.
* @return {Promise} Promise which will be resolved as soon as all modules
* have been loaded.
*/
loadModules(moduleNames) {
const urlBuilder = this._urlBuilder;
if (!moduleNames.length) {
return Promise.resolve();
}
const modulesURLs = urlBuilder.build(moduleNames);
const promises = modulesURLs.map((modulesURL) =>
this._loadScript(modulesURL)
);
return Promise.all(promises);
}
/**
* Places a script element on the page and waits for it to load.
* @param {object} modulesURL an object with two properties:
* - modules: list of the modules which should be loaded
* - url: the URL from which the modules should be loaded
* - useESM: whether to use ESM semantics in the <script>
* @return {Promise} a Promise which will be resolved as soon as the script
* is loaded
*/
_loadScript(modulesURL) {
const config = this._config;
const modules = config.getModules(modulesURL.modules);
let script = this._injectedScripts[modulesURL.url];
if (!script) {
script = this._document.createElement('script');
script.src = modulesURL.url;
script.async = false;
if (config.nonce) {
script.setAttribute('nonce', config.nonce);
}
if (modulesURL.useESM) {
script.type = config.moduleType;
}
script.onload = script.onreadystatechange = () => {
if (
this.readyState &&
this.readyState !== 'complete' &&
this.readyState !== 'load'
) {
return;
}
script.onload = script.onreadystatechange = null;
script.onerror = null;
modules.forEach((module) => {
if (module.fetch.fulfilled) {
this._log.warn(
`Module '${module.name}' is being fetched from\n`,
script.src,
'but was already fetched from\n',
module.fetch.resolved
? module.fetch.resolution.src
: module.fetch.rejection.script.src
);
return;
}
module.fetch.resolve(script);
});
};
script.onerror = () => {
script.onload = script.onreadystatechange = null;
script.onerror = null;
const error = Object.assign(
new Error(
`Unable to load script from URL ${modulesURL.url}`
),
{
modules: modulesURL.modules,
script,
url: modulesURL.url,
}
);
modules.forEach((module) => module.fetch.reject(error));
};
this._injectedScripts[modulesURL.url] = script;
this._document.head.appendChild(script);
}
return Promise.all(modules.map((module) => module.fetch));
}
}