In this blog post, we explore how we can style text that we log to the console in Node.js.
Some of the examples use a Unix shell but most of the code should also work on Windows.
If we want to style text we print to a terminal, we use ANSI escape sequences. Quoting Wikipedia:
ANSI escape sequences are a standard for in-band signaling to control cursor location, color, font styling, and other options on video text terminals and terminal emulators. Certain sequences of bytes, most starting with an ASCII escape character and a bracket character, are embedded into text. The terminal interprets these sequences as commands, rather than text to display verbatim.
ANSI sequences were introduced in the 1970s to replace vendor-specific sequences and became widespread in the computer equipment market by the early 1980s. Although hardware text terminals have become increasingly rare in the 21st century, the relevance of the ANSI standard persists because a great majority of terminal emulators and command consoles interpret at least a portion of the ANSI standard.
SGR is an ANSI escape sequence that sets display attributes:
SGR = "\x1B[" ATTRIBUTES? "m" ;
ATTRIBUTES = ATTRIBUTE (";" ATTRIBUTE)* ;
ATTRIBUTE = DECIMAL_DIGIT+
0x1B is the escape control character.
These are two examples of attributes:
Therefore, the following statement switches to bold, displays “TEXT” and then switches back to a normal font weight:
console.log('\x1B[1mTEXT\x1B[22m')
util.styleText()
Node.js has a built-in function for creating strings with SGR control sequences:
util.styleText(format, text, options?)
format
is a single string with a “modifier” (a name for one of the SGR attributes) or an Array of modifiers. These are a few examples of modifiers (complete list of modifiers):
reset
bold
italic
red
(foreground)bgGreen
(background)The following code demonstrates util.styleText()
:
> util.styleText('bold', 'TEXT')
'\x1B[1mTEXT\x1B[22m'
https://nodejs.org/api/util.html#utilstyletextformat-text-options
The following TypeScript code demonstrates how we can define a template tag for bold text:
const b: TagFunction<string> = (templateStrings, ...substitutions) => (
styleText('bold', String.raw(templateStrings, ...substitutions))
);
Alas, there is no built-in type TagFunction
– we have to define it ourselves:
type TagFunction<R> = (
templateStrings: TemplateStringsArray,
...substitutions: unknown[]
) => R;
The template tag is used like this:
console.log(`This is ${b`bold`} text`);
We can use the property process.stdout.isTTY
to detect if stdout is a terminal. The following shell commands demonstrate how that works:
% node -p 'process.stdout.isTTY'
true
% node -p 'process.stdout.isTTY' | cat
undefined
% echo | node -p 'process.stdout.isTTY'
true
“TTY” stands for teletypewriter (source).
util.styleText()
omits display attributes if stdout is not a terminal Demonstration:
% node -p "JSON.stringify(util.styleText('bold', 'TEXT'))"
"\u001b[1mTEXT\u001b[22m"
% node -p "JSON.stringify(util.styleText('bold', 'TEXT'))" | cat
"TEXT"
Option validateStream
lets us change that behavior:
% node -p "JSON.stringify(util.styleText('bold', 'TEXT', {validateStream: false}))" | cat
"\u001b[1mTEXT\u001b[22m"
We can also switch it off by setting the environment variable FORCE_COLOR
to a value greater than 0:
% FORCE_COLOR=3 node -p "JSON.stringify(util.styleText('bold', 'TEXT'))" | cat
"\u001b[1mTEXT\u001b[22m"
We can even change that environment variable in code and it has the same effect:
process.env['FORCE_COLOR'] = '3';
The Node.js documentation has more information on FORCE_COLOR
.
less
By default, less
does not interpret ANSI escape sequences and displays escaped characters:
% node -p "'\x1B[1mtext\x1B[22m'" | less
We can change that via the option -R
:
% node -p "'\x1B[1mtext\x1B[22m'" | less -R