@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.
157 lines (135 loc) • 6.45 kB
JavaScript
// @ts-check
import path from 'path';
import fs from 'fs';
/** @typedef {{ id: string, imports: GraphNode[], importedBy: GraphNode[] }} GraphNode */
/**
* @param {"build" | "serve"} _command
* @param {unknown} _config
* @param {import('../types').userSettings} userSettings
* @returns {import('vite').Plugin | null}
*/
export function needleImportsLogger(_command, _config, userSettings) {
if (!userSettings.debugImportChains) return null;
/** @type {{allNodes: Map<string, GraphNode>, graph: Record<string, unknown>}} */
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',
/** @param {string} id @param {string | undefined} importer */
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 (importer && !graph.allNodes.has(importer)) {
graph.allNodes.set(importer, {
id: importer,
imports: [],
importedBy: [],
});
}
const node = graph.allNodes.get(id);
const importerNode = importer ? graph.allNodes.get(importer) : undefined;
if (node && importerNode) {
if (!node.importedBy.includes(importerNode))
node.importedBy.push(importerNode);
if (!importerNode.imports.includes(node))
importerNode.imports.push(node);
}
return;
},
/** @param {Error | undefined} [_error] */
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");
/** @param {string} str */
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 = /** @type {Set<string>} */ (new Set());
/** @param {GraphNode} node @param {number} depth @param {0|1|2} type */
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);
}
}
}
}