src/ModuleResolver.js
const path = require('path');
const fs = require('fs');
/**
* The module resolver will resolve modules and converts their inject information into
* a set of information, the objectManager needs in order to handle injection properly
*
* @author Wolfgang Felbermeier (@f3lang)
*/
class ModuleResolver {
/**
* @param moduleSrc An array with absolute paths to folders including
* modules to use for injection
* @param cacheFile A path to a json file, that should be used as a cache.
* @param moduleAnalyzer
*/
constructor(moduleSrc, cacheFile, moduleAnalyzer) {
this.cache = {
moduleMap: {},
fileCache: {}
};
if (cacheFile) {
this.cacheFile = cacheFile;
if (fs.existsSync(cacheFile)) {
this.cache = require(cacheFile);
}
}
this.moduleAnalyzer = moduleAnalyzer;
this.nameCollisionMap = [];
let i = moduleSrc.length;
while (i--) {
this.addModuleSrcDir(moduleSrc[i]);
}
if (cacheFile) {
this.writeCache();
}
}
/**
* Add an additional source directory to the Module Resolver
* @param {string} dir The directory to parse
*/
addModuleSrcDir(dir) {
let modulePaths = fs.readdirSync(dir);
modulePaths.forEach(src => {
let stat = fs.statSync(path.join(dir, src));
if (stat.isDirectory()) {
this.addModuleSrcDir(path.join(dir, src));
} else {
if (path.extname(src).toLowerCase() === '.js') {
this.processModule(path.join(dir, src), stat);
}
}
});
if (this.cacheFile) {
this.writeCache();
}
}
/**
* Fetches the module and handles caching.
* @param modulePath The absolute path to the module source file
* @param stat The stat of the module file
*/
processModule(modulePath, stat) {
if (this.cacheFile) {
if (this.cache.fileCache[path.basename(modulePath)]) {
let stat = fs.statSync(modulePath);
let modificationTime = stat.mtimeMs || stat.mtime * 1;
if (this.cache.fileCache[path.basename(modulePath)].mtimeMs < modificationTime) {
this.cache.fileCache[path.basename(modulePath)].mtimeMs = modificationTime;
this.fetchModule(modulePath);
}
} else {
let stat = fs.statSync(modulePath);
this.cache.fileCache[path.basename(modulePath)] = {
mtimeMs: stat.mtimeMs || stat.mtime * 1
};
this.fetchModule(modulePath);
}
} else {
this.fetchModule(modulePath);
}
}
/**
* Fetches the module information and puts them into the cache.
* @param modulePath The path to the module. Has to be resolvable by require()
* @return {Promise<any>}
*/
fetchModule(modulePath) {
let module = require(modulePath);
let moduleConfig = this.moduleAnalyzer.analyze(module);
let moduleNames = Object.getOwnPropertyNames(module).indexOf('alias') > -1 ? module.alias : [];
moduleNames.push(module.name);
let i = moduleNames.length;
while (i--) {
if (this.nameCollisionMap.indexOf(moduleNames[i]) > -1) {
throw new Error("module name " + moduleNames[i] + " is already in use");
}
}
moduleNames.forEach(name => {
this.nameCollisionMap.push(name);
this.cache.moduleMap[name] = {
path: modulePath,
config: moduleConfig,
name: name,
originModuleName: module.name
}
});
}
/**
* Get the complete set of resolved modules
* @return {{moduleMap: {}, fileCache: {}}|*}
*/
getResolvedModules() {
return this.cache;
}
/**
* Write the cache file.
*/
writeCache() {
fs.writeFileSync(this.cacheFile, JSON.stringify(this.cache, null, '\t'));
}
/**
* Retrieves a list of module names which match a regular expression
* @param {RegExp} regex
* @return {Array} An array of matched module names
*/
getModuleNames(regex) {
return Object.keys(this.cache.moduleMap).filter(name => regex.test(name));
}
}
module.exports = ModuleResolver;
module.exports.inject = ['config:cdi:moduleSrc', 'config:cdi:resolver.cacheFile', 'ModuleAnalyzer'];