DEV Community

Cover image for Comparison and Benchmarks of Node.js libraries to colorize text in terminal
webdiscus
webdiscus

Posted on • Edited on

Comparison and Benchmarks of Node.js libraries to colorize text in terminal

In the Node.js ecosystem, there are a lot of libraries for coloring text in the terminal. Every library claims things like "I'm the fastest" or "I'm the smallest".

Today, the most popular and widely used library is chΠ°lk. It's feature rich and fast - but it's not perfect.

Let's dive into this colorful world and see how libraries differ.

Unpacked size of actual libraries

The package size in node_modules directory:

  • picocolors: 6.37 kB (not minimized) - A micro library with basic features.
  • Π°nsis: 5.89 kB (minimized) - A powerful library with a rich set of features.
  • chalk: 44.2 kB (not minimized) - Provides similar functionality to Ansis.

Compare the features of most popular libraries

Library
______________
Colors support Features
- ESM | CJS
- named import
16|256|16m|🌐
____________
Fallback Chained
syntax
Nested
template
strings
`${}`
LF
\n
Supports
ENV vars
CLI flags
ansis
ESM CJS
βœ… named import
βœ… βœ… βœ… βœ… β†’256
β†’16
β†’b&w
βœ… βœ… βœ… NO_COLOR
FORCE_COLOR
COLORTERM
--no-color
--color
chalk v5
ESM
❌ named import
βœ… βœ… βœ… βœ… β†’256
β†’16
β†’b&w
βœ… ❌ βœ… NO_COLOR
FORCE_COLOR
--no-color
--color
kolorist
ESM CJS
βœ… named import
βœ… βœ… βœ… ❌ β†’256
β†’b&w
❌ ❌ ❌ NO_COLOR
FORCE_COLOR
cli-color
CJS
❌ named import
βœ… βœ… ❌ ❌ β†’16
β†’b&w
βœ… ❌ ❌ NO_COLOR
colors-cli
CJS
❌ named import
βœ… βœ… ❌ ❌ β†’b&w βœ… ❌ βœ… --no-color
--color
colors.js
CJS
❌ named import
βœ… ❌ ❌ ❌ β†’b&w βœ… ❌ βœ… FORCE_COLOR
--no-color
--color
ansi-colors
CJS
❌ named import
βœ… ❌ ❌ ❌ ❌ βœ… ❌ βœ… FORCE_COLOR
colorette
ESM CJS
βœ… named import
βœ… ❌ ❌ ❌ β†’b&w ❌ ❌ ❌ NO_COLOR
FORCE_COLOR
--no-color
--color
picocolors
CJS
❌ named import
βœ… ❌ ❌ ❌ β†’b&w ❌ ❌ ❌ NO_COLOR
FORCE_COLOR
--no-color
--color
tinyrainbow
ESM
❌ named import
βœ… ❌ ❌ βœ… β†’b&w ❌ ❌ ❌ NO_COLOR
FORCE_COLOR
FORCE_TTY
--no-color
--color
kleur
ESM CJS
βœ… named import
❌ ❌ ❌ βœ…
8 colors
β†’b&w βœ… ❌ ❌ NO_COLOR
FORCE_COLOR

Colors support

Only chalk and ansis support truecolor. Other libraries are limited to 16 colors, while kleur supports only the 8 basic colors.

  • 16 - ANSI 16 colors like red, redBright, bgRed, bgRedBright

  • 256 - ANSI 256 colors methods, e.g.:

  • 16m - Truecolor methods, e.g.: hex(), bgHex(), rgb(), bgRgb()

  • 🌐 - Colored output in Chromium-based browser console:

    • βœ… - colored output
    • ❌ - b&w output, or fatal error by compilation or in runtime
  • Fallback - Truecolor β†’ 256 colors β†’ 16 colors β†’ no colors

The ANSI 256 colors:

ANSI 256 colors

If a terminal supports only 16 colors then ANSI 256 colors will be interpolated into base 16 colors.

Fallback

Using the chalk or ansis you can colorize the text with truecolor.

import { hex, rgb } from 'ansis';

// ruby color with bold style
hex('#E0115F').bold`Ruby`;
rgb(224, 17, 95).bold`Ruby`;
Enter fullscreen mode Exit fullscreen mode
import chalk from 'chalk';

// ruby color with bold style
chalk.hex('#E0115F').bold`Ruby`;
chalk.rgb(224, 17, 95).bold`Ruby`;
Enter fullscreen mode Exit fullscreen mode

If you use the hex(), rgb(), fg()(Ansis) or ansis256()(Chalk) functions in a terminal not supported Truecolor or 256 colors, then colors will be interpolated.

Fallback

Named import

Without a named import, you use the library name to access the colors, e.g.:

import color from 'some-lib.js';

let out = color.blue('file.js') + red(' not found!');
Enter fullscreen mode Exit fullscreen mode

Using the named import, you directly import only the colors you use, e.g.:

import {red, blue} from 'some-lib.js';

let out = blue('file.js') + red(' not found!');
// OR using template literals
let out = `${blue('file.js')} ${red('not found!')}`
Enter fullscreen mode Exit fullscreen mode

The colorette support only named import, kleur and ansis supports both named import and normal import, others does not have a named import.

Chained syntax

Any color and style can be combined in any order.
Almost all libraries support chained syntax, except for smallest picocolors and colorette.

import {blue, bold} from 'some-lib.js';

let out = blue.underline('file.js') + bold.red(' not found!');
Enter fullscreen mode Exit fullscreen mode

Nested template strings

In some complex cases you can use the nested template strings.
This feature is supported by ansis only.

import { red, green, blue } from 'ansis';

let out = red`R ${green`G ${blue.bold`B`} G`} R`;
Enter fullscreen mode Exit fullscreen mode

Using the chunk it would look like:

import chalk from 'chalk';

let out = chalk.red`R` + 
            chalk.green`G` + 
              chalk.blue.bold`B` + 
            chalk.green`G` +
          chalk.red`R`;
Enter fullscreen mode Exit fullscreen mode

New lines

Not all libraries support correct style break at the end of line, see the table above. For example, the smallest picocolors, colorette and kleur cannot do this.

import { bgGreen } from 'ansis';

bgGreen`\nAnsis\nNew Line\nNext New Line\n`;
Enter fullscreen mode Exit fullscreen mode

screenshot

Benchmark

Setup and run

git clone https://github.com/webdiscus/ansis.git
cd ./ansis/bench
npm i
npm run bench
Enter fullscreen mode Exit fullscreen mode

Tested on

MacBook Pro 16" M1 Max 64GB
macOS Monterey 12.1
Node.js v16.13.1
Terminal iTerm2

Colorette bench

c.red(`${c.bold(`${c.cyan(`${c.yellow('yellow')}cyan`)}`)}red`);
Enter fullscreen mode Exit fullscreen mode
+ colorette           4,572,582 ops/sec   very fast
  picocolors          3,841,124 ops/sec   very fast
  ansis               2,669,734 ops/sec   fast
  kleur/colors        2,281,415 ops/sec   fast
  chalk               2,287,146 ops/sec   fast
  kleur               2,228,639 ops/sec   fast
  ansi-colors         1,265,615 ops/sec   slow
  colors.js           1,158,572 ops/sec   slow
  cli-color             470,320 ops/sec   too slow
  colors-cli            109,811 ops/sec   too slow
Enter fullscreen mode Exit fullscreen mode

Base colors

const colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'];
colors.forEach((color) => c[color]('foo'));
Enter fullscreen mode Exit fullscreen mode
+ picocolors          8,265,628 ops/sec  very fast
  ansis               6,197,754 ops/sec  fast
  kleur               5,455,121 ops/sec  fast
  chalk               4,428,884 ops/sec  fast
  kleur/colors        2,074,111 ops/sec  slow
  colorette           1,874,506 ops/sec  slow
  ansi-colors         1,010,628 ops/sec  slow
  colors.js             640,101 ops/sec  too slow
  cli-color             305,690 ops/sec  too slow
  colors-cli            104,962 ops/sec  too slow
Enter fullscreen mode Exit fullscreen mode

Chained styles

colors.forEach((color) => c[color].bold.underline.italic('foo'));
Enter fullscreen mode Exit fullscreen mode
+ ansis               5,515,868 ops/sec  very fast
  chalk               1,234,573 ops/sec  fast
  kleur                 514,035 ops/sec  slow
  ansi-colors           158,921 ops/sec  too slow
  cli-color             144,837 ops/sec  too slow
  colors.js             138,219 ops/sec  too slow
  colors-cli             52,732 ops/sec  too slow
  kleur/colors  (not supported)
  colorette     (not supported)
  picocolors    (not supported)
Enter fullscreen mode Exit fullscreen mode

Nested calls

colors.forEach((color) => c[color](c.bold(c.underline(c.italic('foo')))));
Enter fullscreen mode Exit fullscreen mode
+ picocolors            942,592 ops/sec  very fast
  colorette             695,350 ops/sec  fast
  kleur                 648,195 ops/sec  fast
  kleur/colors          561,111 ops/sec  fast
  ansis                 558,575 ops/sec  fast
  chalk                 497,292 ops/sec  fast
  ansi-colors           260,316 ops/sec  slow
  colors.js             166,425 ops/sec  slow
  cli-color              65,561 ops/sec  too slow
  colors-cli             13,800 ops/sec  too slow
Enter fullscreen mode Exit fullscreen mode

Nested styles

c.red(
  `a red ${c.white('white')} red ${c.red('red')} red ${c.cyan('cyan')} red ${c.black('black')} red ${c.red(
    'red'
  )} red ${c.green('green')} red ${c.red('red')} red ${c.yellow('yellow')} red ${c.blue('blue')} red ${c.red(
    'red'
  )} red ${c.magenta('magenta')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red(
    'red'
  )} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red(
    'red'
  )} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.green(
    'green'
  )} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red(
    'red'
  )} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.magenta(
    'magenta'
  )} red ${c.red('red')} red ${c.red('red')} red ${c.cyan('cyan')} red ${c.red('red')} red ${c.red(
    'red'
  )} red ${c.yellow('yellow')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red(
    'red'
  )} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} message`
);
Enter fullscreen mode Exit fullscreen mode
+ picocolors            243,975 ops/sec  very fast
  colorette             243,139 ops/sec  very fast
  kleur/colors          234,132 ops/sec  very fast
  kleur                 221,446 ops/sec  very fast
  ansis                 211,868 ops/sec  very fast
  chalk                 189,960 ops/sec  fast
  ansi-colors           121,451 ops/sec  slow
  colors.js              89,633 ops/sec  too slow
  cli-color              41,657 ops/sec  too slow
  colors-cli             14,264 ops/sec  too slow
Enter fullscreen mode Exit fullscreen mode

Deep nested styles

c.green(
  `green ${c.cyan(
    `cyan ${c.red(
      `red ${c.yellow(
        `yellow ${c.blue(
          `blue ${c.magenta(`magenta ${c.underline(`underline ${c.italic(`italic`)} underline`)} magenta`)} blue`
        )} yellow`
      )} red`
    )} cyan`
  )} green`
);
Enter fullscreen mode Exit fullscreen mode
+ colorette           1,131,757 ops/sec  very fast
  picocolors          1,002,649 ops/sec  very fast
  ansis                 882,220 ops/sec  fast
  chalk                 565,965 ops/sec  fast
  kleur/colors          478,547 ops/sec  fast
  kleur                 464,004 ops/sec  fast
  colors.js             451,592 ops/sec  fast
  ansi-colors           362,733 ops/sec  slow
  cli-color             213,441 ops/sec  slow
  colors-cli             40,340 ops/sec  too slow

Enter fullscreen mode Exit fullscreen mode

HEX colors

Only two libraries supported truecolor methods: ansis and chalk

c.hex('#FBA')('foo');
Enter fullscreen mode Exit fullscreen mode
+ ansis               4,944,572 ops/sec  very fast
  chalk               2,891,684 ops/sec  fast
  colors.js             (not supported)
  colorette             (not supported)
  picocolors            (not supported)
  cli-color             (not supported)
  colors-cli            (not supported)
  ansi-colors           (not supported)
  kleur/colors          (not supported)
  kleur                 (not supported)
Enter fullscreen mode Exit fullscreen mode

Conclusion

There is definitely no fastest, smallest and most functional library.
But we can identify 3 main leaders:

  • picocolors smallest and fastest for simple use cases
  • chalk most popular and fast for commonly use cases
  • ansis small, very fast (faster than chalk) and powerful for simple and complex use cases

Don't be afraid to be modern and experiment with new things.

P.S.

The ansis is not as popular (yet) as chalk, but give a chance to a new, modern, powerful library. Just try this in your new project.

import { black, cyan, green, inverse, red } from 'ansis';

let out = green`Hello ${inverse`ANSI`} World!
${black.bgYellow`Warning:`} ${cyan`file.js`} ${red`not found!`}`;

console.log(out);
Enter fullscreen mode Exit fullscreen mode

The output:

screenshot

View and edit this example in browser:

Open in StackBlitz

Top comments (1)

Collapse
 
thestoicdeveloper profile image
The Stoic Developer

That is a very comprehensive comparison! Shout out to Sindre's other color library that competes with picocolors, github.com/sindresorhus/yoctocolors