Comparison and Benchmarks of Node.js libraries to colorize text in terminal
Tags: webdev, javascript, node, programming
Published: 2023-09-20 19:18:05

In the Node.js world are huge number of different libraries for coloring text in the terminal. Every library write that "I am the best", "I am fastest", "I'm smallest", etc.

Today the most popular library and de facto standard is the [chаlk](https://github.com/chalk/chalk). The `chalk` has rich functionality, is fast but not ideal. It lacks some useful features.

Let's explore this zoo and compare the availability of the most useful features.

| Library<br>______________<br> - name<br> - code size<br> - named import   |               Naming colors                | ANSI 256<br>colors | True-<br>color | Chained<br>syntax | Nested<br>template strings | New<br>Line | Supports<br>CLI params                                   |
|:---------------------------------------------------------------------------|:------------------------------------------:|:------------------:|:--------------:|:-----------------:|:--------------------------:|:-----------:|:---------------------------------------------------------|
| [`colors.js`][colors.js]<br>**18.1KB**<br><nobr>`❌ named import`</nobr>    | <nobr>_non-standard_</nobr><br>`16` colors |         ❌          |       ❌        |         ✅         |             ❌              |      ✅      | only<br>`FORCE_COLOR`<br>`--no-color`<br>`--color`       |
| [`colors-cli`][colors-cli]<br>**8.6KB**<br><nobr>`❌ named import`</nobr>   | <nobr>_non-standard_</nobr><br>`16` colors |         ✅          |       ❌        |         ✅         |             ❌              |      ✅      | only<br>`--no-color`<br>`--color`                        |
| [`cli-color`][cli-color]<br><nobr>`❌ named import`</nobr>                  |        **standard**<br>`16` colors         |         ✅          |       ❌        |         ✅         |             ❌              |      ❌      | only<br>`NO_COLOR`                                       |
| [`ansi-colors`][ansi-colors]<br>**5.8KB**<br><nobr>`❌ named import`</nobr> |        **standard**<br>`16` colors         |         ❌          |       ❌        |         ✅         |             ❌              |      ✅      | only<br>`FORCE_COLOR`                                    |
| [`colorette`][colorette]<br>**3.3KB**<br><nobr>`✅ named import`</nobr>     |        **standard**<br>`16` colors         |         ❌          |       ❌        |         ❌         |             ❌              |      ❌      | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |
| [`picocolors`][picocolors]<br>**2.6KB**<br><nobr>`❌ named import`</nobr>   |         **standard**<br>`8` colors         |         ❌          |       ❌        |         ❌         |             ❌              |      ❌      | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |
| [`kleur`][kleur]<br>**2.7KB**<br><nobr>`✅ named import`</nobr>             |         **standard**<br>`8` colors         |         ❌          |       ❌        |         ✅         |             ❌              |      ❌      | only<br>`NO_COLOR`<br>`FORCE_COLOR`                      |
| [`chalk`][chalk]<br>**15KB**<br><nobr>`❌ named import`</nobr>              |        **standard**<br>`16` colors         |         ✅          |       ✅        |         ✅         |             ❌              |      ✅      | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |
| [`ansis`][ansis]<br>**3.2KB**<br><nobr>`✅ named import`</nobr>             |        **standard**<br>`16` colors         |         ✅          |       ✅        |         ✅         |             ✅              |      ✅      | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |


## Smallest code size

This is the size of pure JS code from the node module, that is runned. This does not include tests, documentation or other files. The smaller is the imported code, the faster it will be loaded and executed, in theory.

Here are the smallest libs: [picocolors][picocolors] (2.6KB), [kleur][kleur] (2.7KB), [ansis][ansis] (3.2KB) and [colorette][colorette] (3.3KB), the rest are at least double larger.

But size doesn't always matter. For example, the smallest `picocolors` has a _minimum_ of features, while the equivalent by size `ansis` has a _maximum_ of useful features.


## Naming and number of supported colors

There are libraries with [standard color names](https://github.com/webdiscus/ansis#base-colors-and-styles), similar to chalk (`red`, `redBright`), and non-standard ones, such as `colors.js` (`brightRed`) or `colors-cli` (`red_bbt`).

Only `chalk` and `ansis` support [truecolor](https://github.com/webdiscus/ansis#truecolor), others support for 16 or 256 colors, and smallest `picocolors` and `kleur` only support 8 base colors.

The ANSI 256 colors:

[![ANSI 256 colors](https://raw.githubusercontent.com/webdiscus/ansis/master/docs/img/ansi256.png)](https://github.com/webdiscus/ansis#ansi-256-colors)

| Code range | Description    |
|-----------:|----------------|
|      0 - 7 | base   colors  |
|     8 - 15 | bright colors  |
|   16 - 231 | 6 × 6 × 6 cube |
|  232 - 255 | grayscale      |


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

```js
import {hex, rgb} from 'ansis/colors';

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

```js
import chalk from 'chalk';

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

## Named import

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

```js
import color from 'some-lib.js';

let out = color.blue('file.js') + red(' not found!');
```

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

```js
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!')}`
```

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`.

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

let out = blue.underline('file.js') + bold.red(' not found!');
```

## Nested template strings

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

```js
import { red, green, blue } from 'ansis/colors';

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

Using the `chunk` it would look like:

```js
import chalk from 'chalk';

let out = chalk.red`R` + 
            chalk.green`G` + 
              chalk.blue.bold`B` + 
            chalk.green`G` +
          chalk.red`R`;
```

## 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.

```js
import { bgGreen } from 'ansis/colors';

bgGreen`\nAnsis\nNew Line\nNext New Line\n`;
```

![screenshot "New lines"](https://github.com/webdiscus/ansis/blob/master/docs/img/break-style-nl.png?raw=true)

## Benchmark

Setup and run

```bash
git clone https://github.com/webdiscus/ansis.git
cd ./ansis/bench
npm i
npm run bench
```

> ### Tested on
>
> MacBook Pro 16" M1 Max 64GB
> macOS Monterey 12.1
> Node.js v16.13.1
> Terminal `iTerm2`

### Colorette bench

```js
c.red(`${c.bold(`${c.cyan(`${c.yellow('yellow')}cyan`)}`)}red`);
```

```diff
+ 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
```

### Base colors

```js
const colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'];
colors.forEach((color) => c[color]('foo'));
```

```diff
+ 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
```

### Chained styles

```js
colors.forEach((color) => c[color].bold.underline.italic('foo'));
```

```diff
+ 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)
```

### Nested calls

```js
colors.forEach((color) => c[color](c.bold(c.underline(c.italic('foo')))));
```

```diff
+ 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
```

### Nested styles

```js
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`
);
```

```diff
+ 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
```

### Deep nested styles

```js
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`
);
```

```diff
+ 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

```

### HEX colors

Only two libraries supported truecolor methods: `ansis` and `chalk`

```js
c.hex('#FBA')('foo');
```

```diff
+ 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)
```

## Conclusion

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

- [picocolors][picocolors] smallest and fastest for simple use cases
- [chalk][chalk] most popular and fast for commonly use cases
- [ansis][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][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.

```js
import { black, cyan, green, inverse, red } from 'ansis/colors';

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

console.log(out);
```

The output:

![screenshot "Hello ANSI World!"](https://raw.githubusercontent.com/webdiscus/ansis/master/docs/img/quik-start-output.png?raw=true)

View and edit this example in browser:

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/edit/stackblitz-starters-sx74bl?file=index.js)


[colors.js]: https://github.com/Marak/colors.js
[colorette]: https://github.com/jorgebucaran/colorette
[picocolors]: https://github.com/alexeyraspopov/picocolors
[cli-color]: https://github.com/medikoo/cli-color
[colors-cli]: https://github.com/jaywcjlove/colors-cli
[ansi-colors]: https://github.com/doowb/ansi-colors
[kleur]: https://github.com/lukeed/kleur
[chalk]: https://github.com/chalk/chalk
[ansis-github]: https://github.com/webdiscus/ansis
[ansis]: https://github.com/webdiscus/ansis