Created
November 27, 2024 11:02
-
-
Save cptSwing/0b63288955b30efda603f019ccf00c2c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-console */ | |
import { cloneDeep } from 'lodash'; | |
type DevConsoleOptions = { | |
errorHandlerName?: string; | |
printStack?: boolean; | |
clone?: boolean; // clone the argument (to create a snapshot at time of execution) ? | |
devMode?: boolean; // boolean value to use instead of NODE_ENV | |
}; | |
const isOptsObject = (arg: unknown) => | |
Object.keys(arg as keyof DevConsoleOptions).some( | |
(key) => key === 'errorHandlerName' || key === 'printStack' || key === 'clone' || key === 'devMode', | |
); | |
/** First argument can be an options object with keys 'printStack', 'clone', 'devMode' */ | |
const DevConsole = { | |
log: (...args: unknown[]) => consoleFn(args[0] && isOptsObject(args[0]) ? { ...args[0], method: 'log' } : { method: 'log' }, ...args), | |
warn: (...args: unknown[]) => | |
consoleFn(args[0] && isOptsObject(args[0]) ? { ...args[0], method: 'warn' } : { method: 'warn' }, ...args), | |
error: (...args: unknown[]) => | |
consoleFn(args[0] && isOptsObject(args[0]) ? { ...args[0], method: 'error' } : { method: 'error' }, ...args), | |
}; | |
export default DevConsole; | |
function consoleFn( | |
opts: DevConsoleOptions & { | |
method: 'log' | 'warn' | 'error'; | |
}, | |
...args: unknown[] | |
) { | |
const { method, errorHandlerName, printStack, clone, devMode } = opts; | |
/* run only in dev mode */ | |
const isDev = devMode ? devMode : process.env.NODE_ENV === 'production'; | |
if (isDev) { | |
const cloneArg = clone ? clone : true; | |
if (args) { | |
const errorStack = new Error().stack; | |
const callerFunction = getCallerFromErrorStack(errorStack, errorHandlerName); | |
let optsString = ' '; | |
if (args[0] && isOptsObject(args[0])) { | |
/* remove the opts object from args: */ | |
args.splice(0, 1); | |
if (printStack !== undefined) { | |
optsString += `("stack": ${printStack})`; | |
} | |
if (clone !== undefined) { | |
optsString += `("cloned": ${clone})`; | |
} | |
if (devMode !== undefined) { | |
optsString += `("devMode": ${devMode})`; | |
} | |
} | |
const consoleArgs = args.map((arg) => { | |
if (typeof arg === 'string') { | |
return arg; | |
} else if (cloneArg) { | |
return cloneDeep(arg); | |
} else { | |
return arg; | |
} | |
}); | |
consoleArgs.unshift(`DC${optsString}:%c[${callerFunction}]`, `color: ${randomHexColor(callerFunction)};`); | |
if (printStack) { | |
console[method](...consoleArgs, '\n\n', 'DevConsole.printStack: ', errorStack); | |
} else { | |
console[method](...consoleArgs); | |
} | |
} | |
} | |
} | |
function getCallerFromErrorStack(errorStack: Error['stack'], errorHandlerName: string | undefined) { | |
if (!errorStack) { | |
return 'Undefined'; | |
} | |
const splitErrorStack = errorStack.split('\n'); | |
let errorStackStartIndex = 3; // 0 = empty line, 1 = consoleFn, 2 = Object.log, so ... | |
if (errorHandlerName) { | |
if (splitErrorStack[errorStackStartIndex].includes(errorHandlerName)) { | |
errorStackStartIndex += 1; | |
} | |
} | |
const initialAttempt = getCaller(errorStackStartIndex, splitErrorStack); | |
/* Dependant on webpack: `config.devtool = env === 'development' ? 'eval-source-map' : config.devtool` */ | |
if (initialAttempt === 'eval') { | |
return extractFunctionNameFromEvalResult(splitErrorStack[errorStackStartIndex]); | |
} else { | |
return initialAttempt; | |
} | |
} | |
/** | |
* | |
* Helper functions: | |
* | |
*/ | |
/** Returns a random `#FFFFFF` hex color code if no arguments, or generates a hex code from passed in number/string */ | |
function randomHexColor(num?: string | number) { | |
if (num) { | |
let sum = 0; | |
let numString: string; | |
if (typeof num === 'number') { | |
numString = num.toString(); | |
} else { | |
numString = num; | |
} | |
for (let i = 0; i < numString.length; i++) { | |
sum += numString.charCodeAt(i); | |
} | |
/* This is kinda arbitrary */ | |
sum = sum / (numString.length * 1000); | |
/* The number 16,777,215 is the total possible combinations of RGB(255,255,255) which is 32 bit colour. */ | |
const hexSum = `#${Math.floor(sum * 16777215).toString(16)}`; | |
return hexSum; | |
} else { | |
return `#${Math.floor(Math.random() * 16777215).toString(16)}`; | |
} | |
} | |
function getCaller(errorStackStartIndex: number, splitErrorStack: string[]) { | |
const a = splitErrorStack[errorStackStartIndex]; | |
const b = a && a.trim(); // trim whitespaces beginning/end | |
const bSplit = b.split(' '); // split at empty spaces | |
let c: string; | |
if (b.includes('async')) { | |
c = b && bSplit[2]; | |
} else { | |
c = b && bSplit[1]; | |
} | |
const d = c && c.match(/\w+/)![0]; | |
return d; | |
} | |
/* Function to strip function name (fx, 'useUpdateMesh') for an explicit string in this format: " at eval (webpack-internal:///./src/lib/hooks/useUpdateMesh.tsx:233:78)" */ | |
function extractFunctionNameFromEvalResult(str: string) { | |
// Extract the part of the string between the last slash and the colon | |
const start = str.lastIndexOf('/') + 1; | |
const end = str.lastIndexOf(':'); | |
const substring = str.substring(start, end); | |
// Split the substring by '.' and get the second last part | |
const parts = substring.split('.'); | |
const functionName = parts[parts.length - 2]; | |
return functionName; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment