Chalk Styles Can Be Passed Around As Naked Function References In Node.js
If you use Node.js, you're probably familiar with the npm module, Chalk. It's a super awesome module that allows you to control the color of text that gets printed to the console. Chalk provides a fluent API that allows you to chain methods as a means of mixing and matching colors and modifiers. And, one thing that I just learned, is that portions of this fluent API can be passed around a naked Function objects without breaking. This allows you to pass colorization methods out of scope.
To be clear, this feature is documented right in the Chalk readme, where it's referred to as "theming." But, since I didn't know about it (or at least I forgot about it), I thought this feature might be worth sharing. Here's the example from the Chalk readme:
const chalk = require('chalk');
const error = chalk.bold.red;
console.log(error('Error!'));
As you can see, the method "chalk.bold.red" is stored in a variable where it can subsequently be invoked as a naked Function object.
To explore this feature of Chalk, I wanted to create a Class that would be able to recursively print a nested Error object to the console. And, as part of that recursive action, I wanted to prefix multiline values with an indentation on each line. In order to encapsulate that complexity, I'm going to pass a naked Chalk style Function around to be consumed as part of the multiline algorithm:
// Require the core node modules.
var chalk = require( "chalk" );
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
class ErrorPrinter {
// I pretty-print an error that may contain nested errors (via rootCause).
printErrorToConsole( error ) {
console.log( chalk.red.bold( "An Error Occurred" ) );
console.log( chalk.red.bold( "=================" ) );
this._printError( error );
}
// ---
// PRIVATE METHODS.
// ---
// I generate the prefix to be used at the given depth of printing.
_getPrefix( depth ) {
var tabSize = 4;
if ( depth >= 2 ) {
return( " ".repeat( depth * tabSize ) + " ".repeat( depth ) + "|" );
} else if ( depth ) {
return( " ".repeat( depth * tabSize ) + "|" );
} else {
return( "" );
}
}
// I recursively print the given error at the given depth.
_printError( error, depth = 0 ) {
var prefix = this._getPrefix( depth );
// Print the nested error header.
if ( depth ) {
this._printLines( prefix, "Root Cause:", chalk.red.bold );
this._printLines( prefix, "- - - - - -", chalk.red.bold );
}
this._printLines( prefix, error.message, chalk.red );
// Print the error meta-data (if it exists).
if ( error.extendedInfo ) {
this._printLines( prefix, this._serialize( error.extendedInfo ), chalk.grey );
}
this._printLines( prefix, error.stack, chalk.dim.italic );
// If this error had a root cause, recursively walk the down to the next level
// of the nested errors.
if ( error.rootCause ) {
this._printError( error.rootCause, ( depth + 1 ) );
}
}
// I print the given, potentially multi-line value, with the prefix at the start of
// each line using the given colorization method (ie, Chalk chain).
_printLines( prefix, value, colorizeLine ) {
var lines = value
.split( /[\r\n]+/g ) // Split on the new lines.
.slice( 0, 3 ) // Truncate the number of lines.
.forEach(
( line ) => {
prefix
? console.log( chalk.dim( prefix ), colorizeLine( line ) )
: console.log( colorizeLine( line ) )
;
}
)
;
}
// I serialize the given value in a way that can be pretty-printed.
_serialize( value ) {
return( JSON.stringify( value, null, 3 ) );
}
}
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
// Let's setup a mock error that contains chained / nested Russian Doll style errors.
var mockError = {
message: "Resource Not Found!",
// Mock nested error.
rootCause: {
message: "Repository Error: Entity Not Found!",
extendedInfo: {
id: 4
},
// Mock nested error.
rootCause: {
message: "Object Marked as Deleted",
extendedInfo: {
_id: 4
}
}
}
};
// Inject mock stack traces.
Error.captureStackTrace( mockError );
Error.captureStackTrace( mockError.rootCause );
Error.captureStackTrace( mockError.rootCause.rootCause );
// Print the complex error object.
new ErrorPrinter().printErrorToConsole( mockError );
As you can see, the _printError() method turns around and calls the _printLines() method several times, each time passing along a naked Chalk style Function object:
this._printLines( prefix, "Root Cause:", chalk.red.bold );
The _printLines() method can then invoke this Function reference and applies all of the embedded styling and formatting:
As you can see, the style Function references, invoked by the _printLines() method, successfully applied the colorization to the output of the console.
This is a minor feature, but it's pretty cool. And, it's a good reminder that there are many ways to build an API, each of which may have benefits and tradeoffs. In this case, the Chalk API uses fluent methods that can be passed around as naked Function references that retain all of their composite styling.
Want to use code from this post? Check out the license.
Reader Comments