MagicMirror/js/loader.js
Kristjan ESPERANTO 0b70274a1a
Replace prettier by stylistic to lint JavaScript (#3303)
In the latest versions of ESLint, more and more formatting rules were
removed or declared deprecated. These rules have been integrated into
the new Stylistic package (https://eslint.style/guide/why) and expanded.

Stylistic acts as a better formatter  for JavaScript as Prettier.

With this PR there are many changes that make the code more uniform, but
it may be difficult to review due to the large amount. Even if I have no
worries about the changes, perhaps this would be something for the
release after next.

Let me know what you think.
2023-12-25 08:17:11 +01:00

262 lines
7.2 KiB
JavaScript

/* global defaultModules, vendor */
/* MagicMirror²
* Module and File loaders.
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
const Loader = (function () {
/* Create helper variables */
const loadedModuleFiles = [];
const loadedFiles = [];
const moduleObjects = [];
/* Private Methods */
/**
* Loops through all modules and requests start for every module.
*/
const startModules = async function () {
const modulePromises = [];
for (const module of moduleObjects) {
try {
modulePromises.push(module.start());
} catch (error) {
Log.error(`Error when starting node_helper for module ${module.name}:`);
Log.error(error);
}
}
const results = await Promise.allSettled(modulePromises);
// Log errors that happened during async node_helper startup
results.forEach((result) => {
if (result.status === "rejected") {
Log.error(result.reason);
}
});
// Notify core of loaded modules.
MM.modulesStarted(moduleObjects);
// Starting modules also hides any modules that have requested to be initially hidden
for (const thisModule of moduleObjects) {
if (thisModule.data.hiddenOnStartup) {
Log.info(`Initially hiding ${thisModule.name}`);
thisModule.hide();
}
}
};
/**
* Retrieve list of all modules.
* @returns {object[]} module data as configured in config
*/
const getAllModules = function () {
return config.modules;
};
/**
* Generate array with module information including module paths.
* @returns {object[]} Module information.
*/
const getModuleData = function () {
const modules = getAllModules();
const moduleFiles = [];
modules.forEach(function (moduleData, index) {
const module = moduleData.module;
const elements = module.split("/");
const moduleName = elements[elements.length - 1];
let moduleFolder = `${config.paths.modules}/${module}`;
if (defaultModules.indexOf(moduleName) !== -1) {
moduleFolder = `${config.paths.modules}/default/${module}`;
}
if (moduleData.disabled === true) {
return;
}
moduleFiles.push({
index: index,
identifier: `module_${index}_${module}`,
name: moduleName,
path: `${moduleFolder}/`,
file: `${moduleName}.js`,
position: moduleData.position,
animateIn: moduleData.animateIn,
animateOut: moduleData.animateOut,
hiddenOnStartup: moduleData.hiddenOnStartup,
header: moduleData.header,
configDeepMerge: typeof moduleData.configDeepMerge === "boolean" ? moduleData.configDeepMerge : false,
config: moduleData.config,
classes: typeof moduleData.classes !== "undefined" ? `${moduleData.classes} ${module}` : module
});
});
return moduleFiles;
};
/**
* Load modules via ajax request and create module objects.
* @param {object} module Information about the module we want to load.
* @returns {Promise<void>} resolved when module is loaded
*/
const loadModule = async function (module) {
const url = module.path + module.file;
/**
* @returns {Promise<void>}
*/
const afterLoad = async function () {
const moduleObject = Module.create(module.name);
if (moduleObject) {
await bootstrapModule(module, moduleObject);
}
};
if (loadedModuleFiles.indexOf(url) !== -1) {
await afterLoad();
} else {
await loadFile(url);
loadedModuleFiles.push(url);
await afterLoad();
}
};
/**
* Bootstrap modules by setting the module data and loading the scripts & styles.
* @param {object} module Information about the module we want to load.
* @param {Module} mObj Modules instance.
*/
const bootstrapModule = async function (module, mObj) {
Log.info(`Bootstrapping module: ${module.name}`);
mObj.setData(module);
await mObj.loadScripts();
Log.log(`Scripts loaded for: ${module.name}`);
await mObj.loadStyles();
Log.log(`Styles loaded for: ${module.name}`);
await mObj.loadTranslations();
Log.log(`Translations loaded for: ${module.name}`);
moduleObjects.push(mObj);
};
/**
* Load a script or stylesheet by adding it to the dom.
* @param {string} fileName Path of the file we want to load.
* @returns {Promise} resolved when the file is loaded
*/
const loadFile = async function (fileName) {
const extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
let script, stylesheet;
switch (extension.toLowerCase()) {
case "js":
return new Promise((resolve) => {
Log.log(`Load script: ${fileName}`);
script = document.createElement("script");
script.type = "text/javascript";
script.src = fileName;
script.onload = function () {
resolve();
};
script.onerror = function () {
Log.error("Error on loading script:", fileName);
resolve();
};
document.getElementsByTagName("body")[0].appendChild(script);
});
case "css":
return new Promise((resolve) => {
Log.log(`Load stylesheet: ${fileName}`);
stylesheet = document.createElement("link");
stylesheet.rel = "stylesheet";
stylesheet.type = "text/css";
stylesheet.href = fileName;
stylesheet.onload = function () {
resolve();
};
stylesheet.onerror = function () {
Log.error("Error on loading stylesheet:", fileName);
resolve();
};
document.getElementsByTagName("head")[0].appendChild(stylesheet);
});
}
};
/* Public Methods */
return {
/**
* Load all modules as defined in the config.
*/
async loadModules () {
let moduleData = getModuleData();
/**
* @returns {Promise<void>} when all modules are loaded
*/
const loadNextModule = async function () {
if (moduleData.length > 0) {
const nextModule = moduleData[0];
await loadModule(nextModule);
moduleData = moduleData.slice(1);
await loadNextModule();
} else {
// All modules loaded. Load custom.css
// This is done after all the modules so we can
// overwrite all the defined styles.
await loadFile(config.customCss);
// custom.css loaded. Start all modules.
await startModules();
}
};
await loadNextModule();
},
/**
* Load a file (script or stylesheet).
* Prevent double loading and search for files in the vendor folder.
* @param {string} fileName Path of the file we want to load.
* @param {Module} module The module that calls the loadFile function.
* @returns {Promise} resolved when the file is loaded
*/
async loadFileForModule (fileName, module) {
if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) {
Log.log(`File already loaded: ${fileName}`);
return;
}
if (fileName.indexOf("http://") === 0 || fileName.indexOf("https://") === 0 || fileName.indexOf("/") !== -1) {
// This is an absolute or relative path.
// Load it and then return.
loadedFiles.push(fileName.toLowerCase());
return loadFile(fileName);
}
if (vendor[fileName] !== undefined) {
// This file is available in the vendor folder.
// Load it from this vendor folder.
loadedFiles.push(fileName.toLowerCase());
return loadFile(`${config.paths.vendor}/${vendor[fileName]}`);
}
// File not loaded yet.
// Load it based on the module path.
loadedFiles.push(fileName.toLowerCase());
return loadFile(module.file(fileName));
}
};
}());