@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
144 lines (123 loc) • 5.9 kB
JavaScript
import path from 'path';
import fs from 'fs';
/**
* @param {import('../types').userSettings} userSettings
*/
export const needleImportsLogger = (command, config, userSettings) => {
if (!userSettings.debugImportChains) return;
const graph = {
allNodes: new Map(),
graph: {},
};
const showOneLevelOfImportsThatWereAlreadyLogged = false;
const showOneLevelOfImportedBy = true;
const logToConsole = false;
const logToImportsLogFile = true;
return {
name: 'needle:imports-logger',
enforce: 'pre',
resolveId(id, importer) {
// we want to make a graph of all the imports
// so when we get this call,
// we can store the importer and id in a graph
// to make this work properly we need to resolve the paths all to absolute paths
// assuming that id is relative to importer
if (importer)
id = path.resolve(path.dirname(importer), id);
if (!graph.allNodes.has(id)) {
graph.allNodes.set(id, {
id,
imports: [],
importedBy: [],
});
}
if (!graph.allNodes.has(importer)) {
graph.allNodes.set(importer, {
id: importer,
imports: [],
importedBy: [],
});
}
const node = graph.allNodes.get(id);
const importerNode = graph.allNodes.get(importer);
if (!node.importedBy.includes(importerNode))
node.importedBy.push(importerNode);
if (!importerNode.imports.includes(node))
importerNode.imports.push(node);
return;
},
buildEnd(error) {
try {
// create log file and append lines to it
const logFile = path.resolve(process.cwd(), "imports.log");
// append a single line efficiently; replace if necessary
const fd = fs.openSync(logFile, "w");
const write = (str) => {
if (logToConsole)
console.log(str);
if (logToImportsLogFile) {
// replace coloring characters
str = str.replace(/\x1b\[\d+m/g, "");
fs.writeSync(fd, str + "\n");
}
}
// log graph
const loggedNodes = new Set();
const logNode = (node, depth, type) => {
if (!showOneLevelOfImportsThatWereAlreadyLogged && loggedNodes.has(node.id))
return;
const char = ["├", "├", "└"][type];
const depthStringWithExactlyThreePlaces = depth.toString().padStart(3, " ");
let spacing = ("│ ").repeat(depth);
// format console.log with dim grey
let id = node.id;
const alreadyLogged = loggedNodes.has(node.id);
if (alreadyLogged)
id = "\x1b[2m" + id + "\x1b[0m";
const suffix = node.imports.length > 0 ? " \x1b[2m[+" + node.imports.length + "]\x1b[0m" : "";
const lineString = "[" + depthStringWithExactlyThreePlaces + "]" + spacing;
write(lineString + char + "─ " + id + (!showOneLevelOfImportsThatWereAlreadyLogged ? suffix : ""));
if (showOneLevelOfImportedBy && node.importedBy.length > 0) {
for (let i = 0; i < node.importedBy.length; i++) {
const _importer = node.importedBy[i];
write(lineString + "│ " + "\x1b[2m^ " + _importer.id + "\x1b[0m");
}
}
if (alreadyLogged)
return;
loggedNodes.add(node.id);
// all utf8 characters for showing a tree in a console:
// ─ │ ├ └ ┌ ┐ ┘ ┴ ┬ ─ │ ├ └ ┌ ┐ ┘ ┴ ┬
// get list of nodes that haven't been displayed before
// const childNodes = node.imports.filter(n => !loggedNodes.has(n.id));
const childNodes = node.imports;
const omittedChildNodes = node.imports.length - childNodes.length;
for( let i = 0; i < childNodes.length; i++) {
const _import = childNodes[i];
const isFirst = i === 0;
const isLast = i === childNodes.length - 1;
logNode(_import, depth + 1, isLast ? 2 : isFirst ? 0 : 1);
};
if (omittedChildNodes > 0) {
const spacing = ("│ ").repeat(depth + 1);
write("[" + depthStringWithExactlyThreePlaces + "]" + spacing + "└" + "─" + " " + omittedChildNodes + " more");
}
}
graph.allNodes.forEach(node => {
try {
logNode(node, 1, 0);
}
catch (e) {
console.error(e);
return;
}
});
fs.closeSync(fd);
console.log(logFile + "(1,0) : [needle-imports-logger] Wrote Module Imports log file.");
}
catch (e) {
console.error("Error in plugin", e);
}
}
}
}