A NodeJS “Preprocessor” Can Add Any Functionality Including Easy Logging
Ever forget one of these: A console.log statement that needs to be removed before submitting from the LeetCode test runner. Yes, I know we should all be good enough to not need to ever use console.log statements, or to even iterate for that matter but sometimes I find myself tired and working on a project and just want to double check my data structure is in the correct state.
The issue however, is that I find it very annoying to:
- Have to remove these console.log statements all the time when submitting to the LeetCode test runner
- Sometimes I want to keep them around in case I want to come back and iterate on this data structure or algorithm later
- It isn't always clear right away if your submission passes but is incrediblys slow if you just forgot to remove a console.log or if your code is sub par
Hence the creation of nodelog
! A NodeJS preprocessor that will not only allow you to console.log anything that has a String representation but that uses comments to dictate what to log so that whenever the code is ran without the preprocessor, the log statements are ignored.
Behold:
And all this requires is a single Javascript file which is only ran whenever you use whichever alias you use to include this file. In this example, I use nodelog
as my alias which usesa a NodeJS argument to require a Javascript file before running the actual code.
So most languages have what are called preprocessors. This is a compilation step where your source code is taken and modified before it is actually compiled.
For exmaple, a C preprocessor is a tool that processes source code before the actual compilation begins. It handles directives that begin with #, such as #include to include files, #define to create macros, and #ifdef for conditional compilation. The preprocessor replaces macros (all those #define lines), includes the contents of header files, and conditionally compiles code, producing a modified source file that is then passed to the compiler.
In other languages, macros are usually great ways to re use code without needing a function and as a result inlinnig the code defined by the macro.
NodeJS doesn't have any sort of preprocessor step. IT does however have an option to run a file before execution. And with this you can do some things like maybe alias console.log to a function cl:
global.cl = console.log
But I was having issues where I would write some code for LeetCode and maybe leave in a few console.log calls which severely slows down execution time.
I also wanted a way that would amke it easier to log out different values without having to write so much code.
To do so I created my own NodejS "PreProcessor" which will add the ability for me to easily log out anything I want with a comment starting with:
//log -
followed by whatever statements I want logged.
This way, when I run locally, I can see the output of the logs, but when I submit to LeetCode or anywhere else for that matter, the log lines are just ignored.
I also created a shell/zsh alias so that this preprocessor is only used when I run nodelog
instead of node
. Just to be safe and not mess with my actual node setup.
I created this file in ~/.config/nodelog.js
and added the following code:
const fs = require("fs");
const vm = require("vm");
const BOLD = `\\x1b[1m`;
// Basic Text Color Codes
const BLACK = `\\x1b[30m`;
const RED = `\\x1b[31m`;
const GREEN = `\\x1b[32m`;
const YELLOW = `\\x1b[33m`;
const BLUE = `\\x1b[34m`;
const MAGENTA = `\\x1b[35m`;
const CYAN = `\\x1b[36m`;
const WHITE = `\\x1b[37m`;
// Bright Text Color Codes
const BRIGHT_BLACK = `\\x1b[90m`;
const BRIGHT_RED = `\\x1b[91m`;
const BRIGHT_GREEN = `\\x1b[92m`;
const BRIGHT_YELLOW = `\\x1b[93m`;
const BRIGHT_BLUE = `\\x1b[94m`;
const BRIGHT_MAGENTA = `\\x1b[95m`;
const BRIGHT_CYAN = `\\x1b[96m`;
const BRIGHT_WHITE = `\\x1b[97m`;
// Reset Code
const RESET = `\\x1b[0m`;
function preprocessAndRunJavaScript(inputFile) {
// Read the content of the input file
const fileContent = fs.readFileSync(inputFile, "utf-8");
// Regular expression to match lines with the `// log -` pattern
const logRegex = /\/\/\s*log\s*-\s*(.+)/g;
// Replace matched lines with the corresponding console.log statement
const processedContent = fileContent.replace(logRegex, (match, p1) => {
const variables = p1.split(",");
// Determine the maximum length of variable names for proper alignment
const maxLength = Math.max(...variables.map(v => v.trim().length));
const logStatement = variables
.map((v) => {
// Pad variable names to align the columns
const paddedVar = v.trim().padEnd(maxLength);
return `"${BRIGHT_WHITE}${BOLD}${paddedVar}${RESET}: ", ${v}, "\\t"`;
})
.join(", ");
return `console.log(${logStatement});`;
});
// Create a new script from the processed content
const script = new vm.Script(processedContent);
// Run the script in the current context
script.runInThisContext();
process.exit(0);
}
// Get the file path from the command line arguments
const inputFile = process.argv[1];
if (!inputFile) {
console.error("Please provide an input file");
process.exit(1);
}
preprocessAndRunJavaScript(inputFile);
Then I edited my ~/.zshrc
file to include:
function nodelog() {
NODE_OPTIONS="$NODE_OPTIONS --require $HOME/.config/nodelog.js" node $@
}
Now I can write code like (filename is dailyTemperatures.js
):
var dailyTemperatures = function (temps) {
const memo = {};
const answer = [];
for (let i = 1; i < temps.length; i++) {
// log - temps[i], temps[i - 1], i
etc...
I use my nodelog
alias:
nodelog dailyTemperatures.js
And in the console I see:
temps[i]: 80 temps[i - 1]: 34 i: 1
And best of all, I no longer need to worry about removing any console.log statements if I am just hacking through a LeetCode problem anymore.