UNPKG

muttley

Version:
95 lines (89 loc) 3.84 kB
import fs from 'fs'; import path from 'path'; import { logger } from './logger'; export interface DependencyTree { getFlat(filepath: string): string[]; } /** * Find file dependancies bases on commonJS require statements. * It does not load the modules - it regexes the files - hence not guarenteed to be 100% correct */ class CommonJsRegexDependencyTree implements DependencyTree { private cache: Map<string, string[]>; public constructor() { this.cache = new Map(); } /** * * @param filepath can be absolute or relative path * @returns a flattened list of all the dependants */ public getFlat(filepath: string): string[] { let list; if ((list = this.cache.get(filepath))) return list; list = this.getImportsCommonJS(filepath); list && this.cache.set(filepath, list); return list; } /** * * @param filepath absolute or relative file path * @param depth recursion depth - just for logging indentation * @returns the full list of dependants for the file. * Only relative dependants are considered - not NODE_MODULES. */ private getImportsCommonJS(filepath: string, depth: number = 0): string[] { const indent: string = ' '.repeat(depth); const list: string[] = []; const folder = path.dirname(filepath); fs.readFileSync(filepath) .toString() .split('\n') .forEach((line): void => { const imports = line.match(/.*require\((.*)\).*/); if (imports) { logger.debug('Found require on line', line); if (imports[1].indexOf("'") >= 0 || imports[1].indexOf('"') >= 0) { const targetModule = imports[1].replace(/['"]/g, ''); let resolvedPath; if (targetModule.startsWith('.')) { resolvedPath = path.resolve(folder, targetModule); if (resolvedPath.endsWith('.js') || resolvedPath.endsWith('.json')) { } else if (fs.existsSync(resolvedPath)) { resolvedPath += '/index.js'; } else { resolvedPath += '.js'; } } else { // for now ignore node modules resolvedPath = targetModule; // resolvedPath = require.resolve(targetModule); } // for native modules resolve just returns the module name if (resolvedPath !== targetModule) { list.push(resolvedPath); // l(filepath, 'imports', targetModule, resolvedPath); } } // else it is dynamically computing target string } }); const nextLevel: Set<string> = new Set(); logger.info(indent, `File ${filepath} depends on ${list}`); list.forEach((file): void => { const imports = this.getImportsCommonJS(file, depth + 1); // recurse next level down if (imports.length) { logger.info(indent, `File ${file} depends on ${imports}`); imports.forEach(importedFile => { nextLevel.add(importedFile); }); } }); const allDependants: string[] = [...list, ...Array.from(nextLevel.keys())]; logger.info(indent, `Full dependants for ${filepath} are ${allDependants}`); return allDependants; } } export const tree = (() => { return new CommonJsRegexDependencyTree(); })();