diff --git a/.prettierrc b/.prettierrc index 9ea329e..2ab8d23 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,5 @@ { - "printWidth": 120, + "printWidth": 180, "tabWidth": 2, "useTabs": false, "singleQuote": true, diff --git a/CHANGELOG.md b/CHANGELOG.md index 39bf450..05394bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change log +## 3.0.1 (2024-04-01) + +- refactor: improve code +- chore: reduce code bundle size from 3.8 KB to 3.4 KB +- chore: update benchmark +- chore: update compare tests +- test: add more tests +- docs: improve readme + ## 3.0.0 (2024-03-29) - feat: add detection of color spaces support: TrueColor, 256 colors, 16 colors, no color diff --git a/README.md b/README.md index 580fb5e..d8062c9 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,21 @@ black.bgYellow`Warning!` hex('#E0115F').bold.underline('Hello World!') ``` -**Why yet one lib?**\ -See the features [comparison](https://github.com/webdiscus/ansis#compare) and [benchmarks](https://github.com/webdiscus/ansis#benchmark) of most popular Node.js libraries:\ -[`chalk`][chalk], [`colors.js`][colors.js], [`colorette`][colorette], [`picocolors`][picocolors], [`kleur`][kleur], [`ansi-colors`][ansi-colors], [`cli-color`][cli-color], [`colors-cli`][colors-cli]. +🚀 [Install and Quick Start](#install) + + + +## 👀 Why yet one lib? + +- Quality is first, test coverage 100%. +- Ansis has a lot of useful [features](#features), compare with [other libraries](#compare). +- Ansis is one of the smallest, 3.4 KB only. +- Ansis is one of the [fastest](#benchmark). +- Ansis is stable, continuously developing and improving. +- Ansis is open for your [feature requests](https://github.com/webdiscus/ansis/issues). + +See the features [comparison](#compare) and [benchmarks](#benchmark) of most popular Node.js libraries:\ +[chalk][chalk], [colors.js][colors.js], [colorette][colorette], [picocolors][picocolors], [kleur][kleur], [kolorist][kolorist], [ansi-colors][ansi-colors], [cli-color][cli-color], [colors-cli][colors-cli].
@@ -37,7 +49,7 @@ See the features [comparison](https://github.com/webdiscus/ansis#compare) and [b [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/edit/stackblitz-starters-gs2gve?file=index.js) - + ## 💡 Highlights @@ -49,7 +61,7 @@ See the features [comparison](https://github.com/webdiscus/ansis#compare) and [b - import chalk from 'chalk'; + import chalk, { red } from 'ansis'; - chalk.red.bold('Error!'); // <- the Chalk like syntax works fine with Ansis + chalk.red.bold('Error!'); // <- Chalk like syntax works fine with Ansis red.bold('Error!'); // <- the same result with Ansis red.bold`Error!`; // <- the same result with Ansis ``` @@ -61,15 +73,18 @@ See the features [comparison](https://github.com/webdiscus/ansis#compare) and [b - CJS: `const { red, green, bold, underline } = require('ansis')` - [Chained syntax](#chained-syntax) `red.bold.underline('text')` - [Nested **template strings**](#nested-syntax) ``` green`GREEN text ${red`RED text`} GREEN text` ``` -- [ANSI 256 colors](#256-colors) and [TrueColor](#truecolor) (**RGB**, **HEX**) ``` rgb(224, 17, 95)`Ruby` ```, ``` hex('#96C')`Amethyst` ``` +- [Base ANSI styles](#base-colors) `dim` **`bold`** _`italic`_ `underline` `strikethrough` +- [Base ANSI 16 colors](#base-colors) ``` red`Error!` ``` ``` redBright`Error!` ``` ``` bgRed`Error!` ``` ``` bgRedBright`Error!` ``` +- [ANSI 256 colors](#256-colors) ``` fg(56)`violet` ``` ``` bg(208)`orange` ``` +- [TrueColor](#truecolor) (**RGB**, **HEX**) ``` rgb(224, 17, 95)`Ruby` ```, ``` hex('#96C')`Amethyst` ``` +- [Fallback](#fallback) to supported [color space](#color-support): TrueColor —> 256 colors —> 16 colors —> no colors - [Extending of base colors](#extend-colors) with named **True Colors** - [ANSI codes](#escape-codes) as `open` and `close` properties ``` `Hello ${red.open}World${red.close}!` ``` - [Strip ANSI codes](#strip) method `ansis.strip()` - [Correct style break](#new-line) at the `end of line` when used `\n` in string - Supports [environment variables](#cli-vars) `NO_COLOR` `FORCE_COLOR` and flags `--no-color` `--color` -- **Auto detects** [color spaces support](#color-support): TrueColor, 256 colors, 16 colors, no color (black & white) -- [Fallback](#fallback) to supported color space: TrueColor —> 256 colors —> 16 colors —> no colors -- Up to **x3 faster** than **Chalk**, [see benchmarks](#benchmark) and code bundle size is only **3.8 KB** +- Up to **x3 faster** than **Chalk**, [see benchmarks](#benchmark) +- Code bundle is only **3.4 KB** - Doesn't extend `String.prototype` - Zero dependencies @@ -90,14 +105,12 @@ an [issue](https://github.com/webdiscus/ansis/issues) on GitHub. -## Install +## Install and Quick Start ```bash npm install ansis ``` -## Usage - You can import default module or named colors with ESM or CommonJS syntax. ```js @@ -116,6 +129,8 @@ const ansis = require('ansis'); const { red, green, blue } = require('ansis'); ``` +See the list of the [ANSI colors and styles](#base-colors). + ```js console.log(ansis.green('Success!')); console.log(green('Success!')); @@ -284,30 +299,29 @@ Output:\ -## Base colors and styles +## Base ANSI 16 colors and styles Colors and styles have standard names used by many popular libraries, such as [chalk][chalk], [colorette][colorette], [kleur][kleur]. -| Foreground colors | Background colors | Styles | -|:----------------------|:--------------------------|-----------------------------------------| -| `black` | `bgBlack` | `dim` | -| `red` | `bgRed` | **`bold`** | -| `green` | `bgGreen` | _`italic`_ | -| `yellow` | `bgYellow` | `underline` | -| `blue` | `bgBlue` | `strikethrough` (alias `strike`) | -| `magenta` | `bgMagenta` | `inverse` | -| `cyan` | `bgCyan` | `visible` | -| `white` | `bgWhite` | `hidden` | -| `gray` (alias `grey`) | `bgGray` (alias `bgGrey`) | `reset` | -| `blackBright` | `bgBlackBright` | | -| `redBright` | `bgRedBright` | | -| `greenBright` | `bgGreenBright` | | -| `yellowBright` | `bgYellowBright` | | -| `blueBright` | `bgBlueBright` | | -| `magentaBright` | `bgMagentaBright` | | -| `cyanBright` | `bgCyanBright` | | -| `whiteBright` | `bgWhiteBright` | | +| Foreground colors | Background colors | Styles | +|:----------------------------------------------------------|:----------------------------------------------------------------|-----------------------------------------| +| `black` | `bgBlack` | `dim` | +| `red` | `bgRed` | **`bold`** | +| `green` | `bgGreen` | _`italic`_ | +| `yellow` | `bgYellow` | `underline` | +| `blue` | `bgBlue` | `strikethrough` (alias `strike`) | +| `magenta` | `bgMagenta` | `inverse` | +| `cyan` | `bgCyan` | `visible` | +| `white` | `bgWhite` | `hidden` | +| `blackBright`
aliases:
`grey`
`gray` US spelling | `bgBlackBright`
aliases:
`bgGrey`
`bgGray` US spelling | `reset` | +| `redBright` | `bgRedBright` | | +| `greenBright` | `bgGreenBright` | | +| `yellowBright` | `bgYellowBright` | | +| `blueBright` | `bgBlueBright` | | +| `magentaBright` | `bgMagentaBright` | | +| `cyanBright` | `bgCyanBright` | | +| `whiteBright` | `bgWhiteBright` | | @@ -589,26 +603,33 @@ $ ./app.js --color=true > log.txt # output in file with ANSI codes ## Color support +Ansis automatically detects the supported color space: + +- TrueColor +- ANSI 256 colors +- ANSI 16 colors +- black & white (no color) + There is no standard way to detect which color space is supported. The most common way to detect color support is to check the `TERM` and `COLORTERM` environment variables. CI systems can be detected by checking for the existence of the `CI` and other specifically environment variables. Combine that with the knowledge about which operating system the program is running on, and we have a decent enough way to detect colors. -| Name | ANSI 16
colors | ANSI 256
colors | True
Color | $TERM | $COLORTERM | specifically ENV variable | -|:---------------------------------|-------------------|:-------------------|:--------------|:---------------|:----------:|:----------------------------------------| -| Azure CI | ✅ | ❌ | ❌ | dumb | | $TF_BUILD
$AGENT_NAME | -| GitHub CI | ✅ | ✅ | ✅ | dumb | | $CI
$GITHUB_ACTIONS | -| GitTea CI | ✅ | ✅ | ✅ | dumb | | $CI
$GITEA_ACTIONS | -| GitLab CI | ✅ | ❌ | ❌ | dumb | | $CI
$GITLAB_CI | -| Travis CI | ✅ | ❌ | ❌ | dumb | | $TRAVIS | -| JetBrains TeamCity
>=2020.1.1 | ✅ | ✅ | ❌ | | | $TEAMCITY_VERSION | -| JetBrains IDEA | ✅ | ✅ | ✅ | xterm-256color | | $TERMINAL_EMULATOR='JetBrains-JediTerm' | -| VS Code | ✅ | ✅ | ✅ | xterm-256color | truecolor | | -| Windows
Terminal | ✅ | ✅ | ✅* | | | | -| Windows
PowerShell | ✅ | ✅ | ✅* | | | | -| macOS Terminal | ✅ | ✅ | ❌ | xterm-256color | | | -| iTerm | ✅ | ✅ | ✅ | xterm-256color | truecolor | | -| Terminal emulator Kitty | ✅ | ✅ | ✅ | xterm-kitty | | | +| Terminal | ANSI 16
colors | ANSI 256
colors | True
Color | env.
TERM | env.
COLORTERM | Specifically ENV variables | +|:---------------------------------|-------------------|:-------------------|:--------------|:--------------:|:-----------------:|:---------------------------------------| +| Azure CI | ✅ | ❌ | ❌ | dumb | | TF_BUILD
AGENT_NAME | +| GitHub CI | ✅ | ✅ | ✅ | dumb | | CI
GITHUB_ACTIONS | +| GitTea CI | ✅ | ✅ | ✅ | dumb | | CI
GITEA_ACTIONS | +| GitLab CI | ✅ | ❌ | ❌ | dumb | | CI
GITLAB_CI | +| Travis CI | ✅ | ❌ | ❌ | dumb | | TRAVIS | +| JetBrains TeamCity
>=2020.1.1 | ✅ | ✅ | ❌ | | | TEAMCITY_VERSION | +| JetBrains IDEA | ✅ | ✅ | ✅ | xterm-256color | | TERMINAL_EMULATOR='JetBrains-JediTerm' | +| VS Code | ✅ | ✅ | ✅ | xterm-256color | truecolor | | +| Windows
Terminal | ✅ | ✅ | ✅* | | | | +| Windows
PowerShell | ✅ | ✅ | ✅* | | | | +| macOS Terminal | ✅ | ✅ | ❌ | xterm-256color | | | +| iTerm | ✅ | ✅ | ✅ | xterm-256color | truecolor | | +| Terminal emulator Kitty | ✅ | ✅ | ✅ | xterm-kitty | | | *The Windows terminal supports true color since Windows 10 revision 14931 (2016-09-21). @@ -628,17 +649,18 @@ npm run compare [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/edit/compare-colorize-libraries?file=index.js) -| Library
______________
- name
- code size
- named import | Naming
base colors | ANSI 256
colors | True
Color | Chained
syntax | Nested
template strings | New
Line | Supports
CLI params | -|:---------------------------------------------------------------------------|:------------------------------------------:|:------------------:|:-------------:|:-----------------:|:--------------------------:|:-----------:|:---------------------------------------------------------| -| [`colors.js`][colors.js]
**18.1KB**
`❌ named import` | _non-standard_
`16` colors | ❌ | ❌ | ✅ | ❌ | ✅ | only
`FORCE_COLOR`
`--no-color`
`--color` | -| [`colors-cli`][colors-cli]
**8.6KB**
`❌ named import` | _non-standard_
`16` colors | ✅ | ❌ | ✅ | ❌ | ✅ | only
`--no-color`
`--color` | -| [`cli-color`][cli-color]
`❌ named import` | **standard**
`16` colors | ✅ | ❌ | ✅ | ❌ | ❌ | only
`NO_COLOR` | -| [`ansi-colors`][ansi-colors]
**5.8KB**
`❌ named import` | **standard**
`16` colors | ❌ | ❌ | ✅ | ❌ | ✅ | only
`FORCE_COLOR` | -| [`colorette`][colorette]
**3.3KB**
`✅ named import` | **standard**
`16` colors | ❌ | ❌ | ❌ | ❌ | ❌ | `NO_COLOR`
`FORCE_COLOR`
`--no-color`
`--color` | -| [`picocolors`][picocolors]
**2.6KB**
`❌ named import` | **standard**
`8` colors | ❌ | ❌ | ❌ | ❌ | ❌ | `NO_COLOR`
`FORCE_COLOR`
`--no-color`
`--color` | -| [`kleur`][kleur]
**2.7KB**
`✅ named import` | **standard**
`8` colors | ❌ | ❌ | ✅ | ❌ | ❌ | only
`NO_COLOR`
`FORCE_COLOR` | -| [`chalk`][chalk]
**15KB**
`❌ named import` | **standard**
`16` colors | ✅ | ✅ | ✅ | ❌ | ✅ | `NO_COLOR`
`FORCE_COLOR`
`--no-color`
`--color` | -| [`ansis`][ansis]
**3.8KB**
`✅ named import` | **standard**
`16` colors | ✅ | ✅ | ✅ | ✅ | ✅ | `NO_COLOR`
`FORCE_COLOR`
`--no-color`
`--color` | +| Library
________________
- name
- bundle size
- named import
- naming colors | ANSI base colors | ANSI 256
colors | True
Color | Chained
syntax | Nested
template strings | New
Line | Fallbacks | Supports
ENV vars
CLI flags | +|:------------------------------------------------------------------------------------------------------------|:----------------:|:------------------:|:-------------:|:-----------------:|:--------------------------:|:-----------:|------------------------------------|:---------------------------------------------------------| +| [`colors.js`][colors.js]
**18.1KB**
`❌ named import`
`❌ standard` | `16` colors | ❌ | ❌ | ✅ | ❌ | ✅ | no color | `FORCE_COLOR`
`--no-color`
`--color` | +| [`colors-cli`][colors-cli]
**8.6KB**
`❌ named import`
`❌ standard` | `16` colors | ✅ | ❌ | ✅ | ❌ | ✅ | no color | `--no-color`
`--color` | +| [`cli-color`][cli-color]
`❌ named import`
`✅ standard` | `16` colors | ✅ | ❌ | ✅ | ❌ | ❌ | 16 colors
no color | `NO_COLOR` | +| [`ansi-colors`][ansi-colors]
**5.8KB**
`❌ named import`
`✅ standard` | `16` colors | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | `FORCE_COLOR` | +| [`colorette`][colorette]
**3.3KB**
`✅ named import`
`✅ standard` | `16` colors | ❌ | ❌ | ❌ | ❌ | ❌ | no color | `NO_COLOR`
`FORCE_COLOR`
`--no-color`
`--color` | +| [`picocolors`][picocolors]
**2.6KB**
`❌ named import`
`✅ standard` | `8` colors | ❌ | ❌ | ❌ | ❌ | ❌ | no color | `NO_COLOR`
`FORCE_COLOR`
`--no-color`
`--color` | +| [`kleur`][kleur]
**2.7KB**
`✅ named import`
`✅ standard` | `8` colors | ❌ | ❌ | ✅ | ❌ | ❌ | no color | `NO_COLOR`
`FORCE_COLOR` | +| [`kolorist`][kolorist]
**6.8KB**
`✅ named import`
`❌ standard` | `16` colors | ✅ | ✅ | ❌ | ❌ | ❌ | 256 color

no color | `NO_COLOR`
`FORCE_COLOR` | +| [`chalk`][chalk]
**15KB**
`❌ named import`
`✅ standard` | `16` colors | ✅ | ✅ | ✅ | ❌ | ✅ | 256 color
16 colors
no color | `NO_COLOR`
`FORCE_COLOR`
`--no-color`
`--color` | +| [`ansis`][ansis]
**3.4KB**
`✅ named import`
`✅ standard` | `16` colors | ✅ | ✅ | ✅ | ✅ | ✅ | 256 color
16 colors
no color | `NO_COLOR`
`FORCE_COLOR`
`--no-color`
`--color` | > **Note** > @@ -694,7 +716,24 @@ npm run demo -## Run benchmark +## Benchmark + +To measure performance is used [benchmark.js](https://github.com/bestiejs/benchmark.js). + +> ‼️ **Warning** +> +> **Don't trust** other test results using [vitest benchmark](https://vitest.dev/config/#benchmark). +> +> The `vitest benchmark` generate FALSE/**unreal** results.\ +> For example, the results of the simple bench: +> ``` +> chalk.red('foo') - 7.000.000 ops/sec +> ansis.red('foo') - 23.000.000 ops/sec (x3 faster is WRONG result) +> ``` +> +> The real performance results of `chalk` and `ansis` in this test are very close. + +### Run benchmark ```bash git clone https://github.com/webdiscus/ansis.git @@ -909,6 +948,8 @@ Most popular ANSI libraries for `Node.js`: [kleur]: https://github.com/lukeed/kleur +[kolorist]: https://github.com/marvinhagemeister/kolorist + [chalk]: https://github.com/chalk/chalk [ansis]: https://github.com/webdiscus/ansis diff --git a/bench/index.bench.js b/bench/index.bench.js new file mode 100644 index 0000000..5f908da --- /dev/null +++ b/bench/index.bench.js @@ -0,0 +1,83 @@ +import { describe, bench } from 'vitest'; + +// vendor libraries +import chalk from 'chalk'; +import colorsJs from 'colors'; +import * as colorette from 'colorette'; +import ansiColors from 'ansi-colors'; +import cliColor from 'cli-color'; +import colorCli from 'colors-cli/safe.js'; +import kleur from 'kleur'; +import * as kleurColors from 'kleur/colors'; +import picocolors from 'picocolors'; +import * as kolorist from 'kolorist'; +import { Ansis, green, red, yellow, hex, rgb } from 'ansis'; + +// create a new instance of Ansis for correct measure in benchmark +const ansis = new Ansis(); + +import packages from './packages.js'; + +const CI = process.env.CI; +const options = { + warmupTime: CI ? 500 : 100, + time: CI ? 2000 : 500, +}; + +// TODO: +// fix - the bench with `kolorist` occurs fatal error: Allocation failed - JavaScript heap out of memory +// fix - unreal/FALSE bench results, e.g. ansis cannot be 3x faster than chalk with the `lib.red('foo')` test + +describe(`Using 1 style (red)`, () => { + bench(packages['colors'], () => colorsJs.red('foo'), options); + bench(packages['colorette'], () => colorette.red('foo'), options); + bench(packages['picocolors'], () => picocolors.red('foo'), options); + bench(packages['cli-color'], () => cliColor.red('foo'), options); + bench(packages['colors-cli'], () => colorCli.red('foo'), options); + bench(packages['ansi-colors'], () => ansiColors.red('foo'), options); + bench(packages['kleur'], () => kleur.red('foo'), options); + //bench(packages['kolorist'], () =>kolorist.red('foo'), options); + bench(packages['chalk'], () => chalk.red('foo'), options); + bench(packages['ansis'], () => ansis.red('foo'), options); +}); + +describe(`Using 2 styles (red, bold)`, () => { + bench(packages['colors'], () => colorsJs.red.bold('foo'), options); + bench(packages['colorette'], () => colorette.red(colorette.bold('foo')), options); + bench(packages['picocolors'], () => picocolors.red(picocolors.bold('foo')), options); + bench(packages['cli-color'], () => cliColor.red.bold('foo'), options); + bench(packages['colors-cli'], () => colorCli.red.bold('foo'), options); + bench(packages['ansi-colors'], () => ansiColors.red.bold('foo'), options); + bench(packages['kleur'], () => kleur.red().bold('foo'), options); + //bench(packages['kolorist'], () =>kolorist.red(kolorist.bold('foo')), options); + bench(packages['chalk'], () => chalk.red.bold('foo'), options); + bench(packages['ansis'], () => ansis.red.bold('foo'), options); +}); + +describe(`Using 3 styles (red, bold, underline)`, () => { + bench(packages['colors'], () => colorsJs.red.bold.underline('foo'), options); + bench(packages['colorette'], () => colorette.red(colorette.bold(colorette.underline('foo'))), options); + bench(packages['picocolors'], () => picocolors.red(picocolors.bold(picocolors.underline('foo'))), options); + bench(packages['cli-color'], () => cliColor.red.bold.underline('foo'), options); + bench(packages['colors-cli'], () => colorCli.red.bold.underline('foo'), options); + bench(packages['ansi-colors'], () => ansiColors.red.bold.underline('foo'), options); + bench(packages['kleur'], () => kleur.red().bold().underline('foo'), options); + //bench(packages['kolorist'], () =>kolorist.red(kolorist.bold(kolorist.underline('foo'))), options); + bench(packages['chalk'], () => chalk.red.bold.underline('foo'), options); + bench(packages['ansis'], () => ansis.red.bold.underline('foo'), options); +}); + +describe(`Using 7 styles (bgWhite red, bold, italic, dim, underline, strikethrough)`, () => { + bench(packages['colors'], () => colorsJs.bgWhite.red.bold.italic.dim.underline.strikethrough('foo'), options); + bench(packages['colorette'], () => colorette.bgWhite(colorette.red(colorette.bold(colorette.italic(colorette.dim(colorette.underline(colorette.strikethrough('foo'))))))), + options); + bench(packages['picocolors'], + () => picocolors.bgWhite(picocolors.red(picocolors.bold(picocolors.italic(picocolors.dim(picocolors.underline(picocolors.strikethrough('foo'))))))), options); + bench(packages['cli-color'], () => cliColor.bgWhite.red.bold.italic.inverse.underline.strike('foo'), options); + bench(packages['colors-cli'], () => colorCli.white_b.red.bold.italic.inverse.underline.strike('foo'), options); + bench(packages['ansi-colors'], () => ansiColors.bgWhite.red.bold.italic.dim.underline.strikethrough('foo'), options); + bench(packages['kleur'], () => kleur.bgWhite().red().bold().italic().dim().underline().strikethrough('foo'), options); + //bench(packages['kolorist'], () =>kolorist.bgWhite(kolorist.red(kolorist.bold(kolorist.italic(kolorist.dim(kolorist.underline(kolorist.strikethrough('foo'))))))), options); + bench(packages['chalk'], () => chalk.bgWhite.red.bold.italic.dim.underline.strikethrough('foo'), options); + bench(packages['ansis'], () => ansis.bgWhite.red.bold.italic.dim.underline.strikethrough('foo'), options); +}); diff --git a/bench/index.js b/bench/index.js index 8ff5d19..1129011 100644 --- a/bench/index.js +++ b/bench/index.js @@ -34,15 +34,21 @@ import colorsJs from 'colors'; import * as colorette from 'colorette'; import ansiColors from 'ansi-colors'; import cliColor from 'cli-color'; -import colorCli from 'colors-cli/lib/color-safe.js'; +import colorCli from 'colors-cli/safe.js'; import kleur from 'kleur'; import * as kleurColors from 'kleur/colors'; +import * as kolorist from 'kolorist'; import picocolors from 'picocolors'; import { Ansis, green, red, yellow, hex, rgb } from 'ansis'; import spectrum from '../examples/fixtures/spectrum.js'; import { getColorSpace } from '../src/color-support.js'; +import packages from './packages.js'; + +// create a new instance of Ansis for correct measure in benchmark +const ansis = new Ansis(); + const colorSpace = getColorSpace(); const log = console.log; @@ -52,26 +58,24 @@ if (colorSpace < 3) { log('The result of some tests can be NOT correct! Choose a modern terminal, e.g. iTerm.\n'); } -// create a new instance of Ansis for correct measure in benchmark -const ansis = new Ansis(); - // All vendor libraries to be tested const vendors = [ - { name: 'colors-js', lib: colorsJs }, - { name: 'colorette', lib: colorette }, - { name: 'picocolors', lib: picocolors }, - { name: 'cli-color', lib: cliColor }, - { name: 'color-cli', lib: colorCli }, - { name: 'ansi-colors', lib: ansiColors }, + { name: packages['colors'], lib: colorsJs }, + { name: packages['colorette'], lib: colorette }, + { name: packages['picocolors'], lib: picocolors }, + { name: packages['cli-color'], lib: cliColor }, + { name: packages['colors-cli'], lib: colorCli }, + { name: packages['ansi-colors'], lib: ansiColors }, { name: 'kleur/colors', lib: kleurColors }, - { name: 'kleur', lib: kleur }, - { name: 'chalk', lib: chalk }, - { name: 'ansis', lib: ansis }, + { name: packages['kleur'], lib: kleur }, + { name: packages['chalk'], lib: chalk }, + { name: packages['ansis'], lib: ansis }, ]; const benchStyle = new Ansis(); const bench = new Bench({ minOpsWidth: 12, + //delay: 150, suiteNameColor: benchStyle.bgYellow.black, benchNameColor: benchStyle.magenta, opsColor: benchStyle.greenBright, @@ -86,6 +90,58 @@ let fixture; log(hex('#F88').inverse.bold` -= Benchmark =- `); +bench('Using 1 style (red)'). + add(packages['colors'], () => colorsJs.red('foo')). + add(packages['colorette'], () => colorette.red('foo')). + add(packages['picocolors'], () => picocolors.red('foo')). + add(packages['cli-color'], () => cliColor.red('foo')). + add(packages['colors-cli'], () => colorCli.red('foo')). + add(packages['ansi-colors'], () => ansiColors.red('foo')). + add(packages['kleur'], () => kleur.red('foo')). + add(packages['kolorist'], () => kolorist.red('foo')). + add(packages['chalk'], () => chalk.red('foo')). + add(packages['ansis'], () => ansis.red('foo')). + run(); + +bench(`Using 2 styles (red, bold)`). + add(packages['colors'], () => colorsJs.red.bold('foo')). + add(packages['colorette'], () => colorette.red(colorette.bold('foo'))). + add(packages['picocolors'], () => picocolors.red(picocolors.bold('foo'))). + add(packages['cli-color'], () => cliColor.red.bold('foo')). + add(packages['colors-cli'], () => colorCli.red.bold('foo')). + add(packages['ansi-colors'], () => ansiColors.red.bold('foo')). + add(packages['kleur'], () => kleur.red().bold('foo')). + add(packages['kolorist'], () => kolorist.red(kolorist.bold('foo'))). + add(packages['chalk'], () => chalk.red.bold('foo')). + add(packages['ansis'], () => ansis.red.bold('foo')). + run(); + +bench(`Using 3 styles (red, bold, underline)`). + add(packages['colors'], () => colorsJs.red.bold.underline('foo')). + add(packages['colorette'], () => colorette.red(colorette.bold(colorette.underline('foo')))). + add(packages['picocolors'], () => picocolors.red(picocolors.bold(picocolors.underline('foo')))). + add(packages['cli-color'], () => cliColor.red.bold.underline('foo')). + add(packages['colors-cli'], () => colorCli.red.bold.underline('foo')). + add(packages['ansi-colors'], () => ansiColors.red.bold.underline('foo')). + add(packages['kleur'], () => kleur.red().bold().underline('foo')). + add(packages['kolorist'], () => kolorist.red(kolorist.bold(kolorist.underline('foo')))). + add(packages['chalk'], () => chalk.red.bold.underline('foo')). + add(packages['ansis'], () => ansis.red.bold.underline('foo')). + run(); + +bench(`Using 5 styles (bgWhite red, bold, italic, underline)`). + add(packages['colors'], () => colorsJs.bgWhite.red.bold.italic.underline('foo')). + add(packages['colorette'], () => colorette.bgWhite(colorette.red(colorette.bold(colorette.italic(colorette.underline('foo')))))). + add(packages['picocolors'], () => picocolors.bgWhite(picocolors.red(picocolors.bold(picocolors.italic(picocolors.underline('foo')))))). + add(packages['cli-color'], () => cliColor.bgWhite.red.bold.italic.underline('foo')). + add(packages['colors-cli'], () => colorCli.white_b.red.bold.italic.underline('foo')). + add(packages['ansi-colors'], () => ansiColors.bgWhite.red.bold.italic.underline('foo')). + add(packages['kleur'], () => kleur.bgWhite().red().bold().italic().underline()('foo')). + add(packages['kolorist'], () => kolorist.bgWhite(kolorist.red(kolorist.bold(kolorist.italic(kolorist.underline('foo')))))). + add(packages['chalk'], () => chalk.bgWhite.red.bold.italic.underline('foo')). + add(packages['ansis'], () => ansis.bgWhite.red.bold.italic.underline('foo')). + run(); + // Colorette bench // https://github.com/jorgebucaran/colorette/blob/main/bench/index.js fixture = createFixture(vendors, coloretteBench); @@ -104,54 +160,54 @@ bench('Colorette bench'). // Base colors bench('Base colors'). - add('colors-js', () => baseColors.forEach((style) => colorsJs[style]('foo'))). - add('colorette', () => baseColors.forEach((style) => colorette[style]('foo'))). - add('picocolors', () => baseColors.forEach((style) => picocolors[style]('foo'))). - add('cli-color', () => baseColors.forEach((style) => cliColor[style]('foo'))). - add('color-cli', () => baseColors.forEach((style) => colorCli[style]('foo'))). - add('ansi-colors', () => baseColors.forEach((style) => ansiColors[style]('foo'))). + add(packages['colors'], () => baseColors.forEach((style) => colorsJs[style]('foo'))). + add(packages['colorette'], () => baseColors.forEach((style) => colorette[style]('foo'))). + add(packages['picocolors'], () => baseColors.forEach((style) => picocolors[style]('foo'))). + add(packages['cli-color'], () => baseColors.forEach((style) => cliColor[style]('foo'))). + add(packages['colors-cli'], () => baseColors.forEach((style) => colorCli[style]('foo'))). + add(packages['ansi-colors'], () => baseColors.forEach((style) => ansiColors[style]('foo'))). add('kleur/colors', () => baseColors.forEach((style) => kleurColors[style]('foo'))). - add('kleur', () => baseColors.forEach((style) => kleur[style]('foo'))). - add('chalk', () => baseColors.forEach((style) => chalk[style]('foo'))). - add('ansis', () => baseColors.forEach((style) => ansis[style]('foo'))). + add(packages['kleur'], () => baseColors.forEach((style) => kleur[style]('foo'))). + add(packages['chalk'], () => baseColors.forEach((style) => chalk[style]('foo'))). + add(packages['ansis'], () => baseColors.forEach((style) => ansis[style]('foo'))). run(); // Chained styles bench('Chained styles'). - add('colors-js', () => baseColors.forEach((style) => colorsJs[style].bold.underline.italic('foo'))). + add(packages['colors'], () => baseColors.forEach((style) => colorsJs[style].bold.underline.italic('foo'))). add('colorette (not supported)', () => baseColors.forEach((style) => colorette[style].bold.underline.italic('foo'))). add('picocolors (not supported)', () => baseColors.forEach((style) => picocolors[style].bold.underline.italic('foo')), ). - add('cli-color', () => baseColors.forEach((style) => cliColor[style].bold.underline.italic('foo'))). - add('color-cli', () => baseColors.forEach((style) => colorCli[style].bold.underline.italic('foo'))). - add('ansi-colors', () => baseColors.forEach((style) => ansiColors[style].bold.underline.italic('foo'))). + add(packages['cli-color'], () => baseColors.forEach((style) => cliColor[style].bold.underline.italic('foo'))). + add(packages['colors-cli'], () => baseColors.forEach((style) => colorCli[style].bold.underline.italic('foo'))). + add(packages['ansi-colors'], () => baseColors.forEach((style) => ansiColors[style].bold.underline.italic('foo'))). add('kleur/colors (not supported)', () => baseColors.forEach((style) => kleurColors[style].bold.underline.italic('foo')), ). - // add('kleur', () => baseColors.forEach((style) => kleur[style]().bold().underline().italic('foo'))). // alternate syntax - add('chalk', () => baseColors.forEach((style) => chalk[style].bold.underline.italic('foo'))). - add('ansis', () => baseColors.forEach((style) => ansis[style].bold.underline.italic('foo'))). + // add(packages['kleur'], () => baseColors.forEach((style) => kleur[style]().bold().underline().italic('foo'))). // alternate syntax + add(packages['chalk'], () => baseColors.forEach((style) => chalk[style].bold.underline.italic('foo'))). + add(packages['ansis'], () => baseColors.forEach((style) => ansis[style].bold.underline.italic('foo'))). run(); // Nested calls bench('Nested calls'). - add('colors-js', () => + add(packages['colors'], () => baseColors.forEach((style) => colorsJs[style](colorsJs.bold(colorsJs.underline(colorsJs.italic('foo'))))), ). - add('colorette', () => + add(packages['colorette'], () => baseColors.forEach((style) => colorette[style](colorette.bold(colorette.underline(colorette.italic('foo'))))), ). - add('picocolors', () => + add(packages['picocolors'], () => baseColors.forEach((style) => picocolors[style](picocolors.bold(picocolors.underline(picocolors.italic('foo'))))), ). - add('cli-color', () => + add(packages['cli-color'], () => baseColors.forEach((style) => cliColor[style](cliColor.bold(cliColor.underline(cliColor.italic('foo'))))), ). - add('color-cli', () => + add(packages['colors-cli'], () => baseColors.forEach((style) => colorCli[style](colorCli.bold(colorCli.underline(colorCli.italic('foo'))))), ). - add('ansi-colors', () => + add(packages['ansi-colors'], () => baseColors.forEach((style) => ansiColors[style](ansiColors.bold(ansiColors.underline(ansiColors.italic('foo'))))), ). add('kleur/colors', () => @@ -159,39 +215,39 @@ bench('Nested calls'). kleurColors[style](kleurColors.bold(kleurColors.underline(kleurColors.italic('foo')))), ), ). - add('kleur', () => baseColors.forEach((style) => kleur[style](kleur.bold(kleur.underline(kleur.italic('foo')))))). - add('chalk', () => baseColors.forEach((style) => chalk[style](chalk.bold(chalk.underline(chalk.italic('foo')))))). - add('ansis', () => baseColors.forEach((style) => ansis[style](ansis.bold(ansis.underline(ansis.italic('foo')))))). + add(packages['kleur'], () => baseColors.forEach((style) => kleur[style](kleur.bold(kleur.underline(kleur.italic('foo')))))). + add(packages['chalk'], () => baseColors.forEach((style) => chalk[style](chalk.bold(chalk.underline(chalk.italic('foo')))))). + add(packages['ansis'], () => baseColors.forEach((style) => ansis[style](ansis.bold(ansis.underline(ansis.italic('foo')))))). run(); // Nested styles fixture = createFixture(vendors, nestedFixture); bench('Nested styles'). - add('colors.js', () => fixture[9](colorsJs)). - add('colorette', () => fixture[0](colorette)). - add('picocolors', () => fixture[1](picocolors)). - add('cli-color', () => fixture[2](cliColor)). - add('color-cli', () => fixture[3](colorCli)). - add('ansi-colors', () => fixture[4](ansiColors)). + add(packages['colors'], () => fixture[9](colorsJs)). + add(packages['colorette'], () => fixture[0](colorette)). + add(packages['picocolors'], () => fixture[1](picocolors)). + add(packages['cli-color'], () => fixture[2](cliColor)). + add(packages['colors-cli'], () => fixture[3](colorCli)). + add(packages['ansi-colors'], () => fixture[4](ansiColors)). add('kleur/colors', () => fixture[5](kleurColors)). - add('kleur', () => fixture[6](kleur)). - add('chalk', () => fixture[7](chalk)). - add('ansis', () => fixture[8](ansis)). + add(packages['kleur'], () => fixture[6](kleur)). + add(packages['chalk'], () => fixture[7](chalk)). + add(packages['ansis'], () => fixture[8](ansis)). run(); // Deep nested styles fixture = createFixture(vendors, deepNestedFixture); bench('Deep nested styles'). - add('colors.js', () => fixture[9](colorsJs)). - add('colorette', () => fixture[0](colorette)). - add('picocolors', () => fixture[1](picocolors)). - add('cli-color', () => fixture[2](cliColor)). - add('color-cli', () => fixture[3](colorCli)). - add('ansi-colors', () => fixture[4](ansiColors)). + add(packages['colors'], () => fixture[9](colorsJs)). + add(packages['colorette'], () => fixture[0](colorette)). + add(packages['picocolors'], () => fixture[1](picocolors)). + add(packages['cli-color'], () => fixture[2](cliColor)). + add(packages['colors-cli'], () => fixture[3](colorCli)). + add(packages['ansi-colors'], () => fixture[4](ansiColors)). add('kleur/colors', () => fixture[5](kleurColors)). - add('kleur', () => fixture[6](kleur)). - add('chalk', () => fixture[7](chalk)). - add('ansis', () => fixture[8](ansis)). + add(packages['kleur'], () => fixture[6](kleur)). + add(packages['chalk'], () => fixture[7](chalk)). + add(packages['ansis'], () => fixture[8](ansis)). run(); // Check support of correct break style at new line @@ -200,34 +256,34 @@ bench('Deep nested styles'). //const breakStyleAtNewLineFixture = `\nAnsis\nNEW LINE\nNEXT NEW LINE\n`; // bench('New Line') // .add('colors.js', () => colorsJs.bgGreen(breakStyleAtNewLineFixture)) -// .add('ansi-colors', () => ansiColors.bgGreen(breakStyleAtNewLineFixture)) -// .add('chalk', () => chalk.bgGreen(breakStyleAtNewLineFixture)) -// .add('ansis', () => ansis.bgGreen(breakStyleAtNewLineFixture)) +// .add(packages['ansi-colors'], () => ansiColors.bgGreen(breakStyleAtNewLineFixture)) +// .add(packages['chalk'], () => chalk.bgGreen(breakStyleAtNewLineFixture)) +// .add(packages['ansis'], () => ansis.bgGreen(breakStyleAtNewLineFixture)) // .run(); -bench('RGB colors').add('chalk', () => { +bench('RGB colors').add(packages['chalk'], () => { for (let i = 0; i < 256; i++) chalk.rgb(i, 150, 200)('foo'); -}).add('ansis', () => { +}).add(packages['ansis'], () => { for (let i = 0; i < 256; i++) rgb(i, 150, 200)('foo'); }).run(); // HEX colors // the hex(), rgb(), bgHex(), bgRgb() methods support only chalk and ansis bench('HEX colors'). - add('chalk', () => chalk.hex('#FBA')('foo')). - add('ansis', () => hex('#FBA')('foo')). + add(packages['chalk'], () => chalk.hex('#FBA')('foo')). + add(packages['ansis'], () => hex('#FBA')('foo')). run(); // Spectrum HEX colors bench('Spectrum HEX colors'). - add('chalk', () => { + add(packages['chalk'], () => { let str = ''; spectrum.forEach(color => { str += chalk.hex(color)('█'); }); return str; }). - add('ansis', () => { + add(packages['ansis'], () => { let str = ''; spectrum.forEach(color => { str += hex(color)('█'); @@ -238,7 +294,7 @@ bench('Spectrum HEX colors'). // Template literals bench('Template literals'). - add('ansis', () => red`red ${yellow`yellow ${green`green`} yellow`} red`). + add(packages['ansis'], () => red`red ${yellow`yellow ${green`green`} yellow`} red`). run(); function coloretteBench(c) { diff --git a/bench/package.json b/bench/package.json index 271e632..a82ddeb 100644 --- a/bench/package.json +++ b/bench/package.json @@ -4,19 +4,22 @@ "type": "module", "types": "lib/bench.d.ts", "scripts": { - "bench": "node ./", + "bench": "node ./index.js", + "bench:vi": "vitest bench", "bench:ansis": "node ./ansis.js" }, "dependencies": { - "ansi-colors": "4.1.1", + "ansi-colors": "4.1.3", "ansis": "file:../dist", "benchmark": "2.1.4", - "chalk": "5.0.0", - "cli-color": "2.0.1", + "chalk": "5.3.0", + "cli-color": "2.0.4", "colors": "1.4.0", - "colorette": "2.0.16", - "colors-cli": "1.0.28", - "kleur": "4.1.4", - "picocolors": "1.0.0" + "colorette": "2.0.20", + "colors-cli": "1.0.33", + "kleur": "4.1.5", + "kolorist": "1.8.0", + "picocolors": "1.0.0", + "vitest": "^1.4.0" } } diff --git a/bench/packages.js b/bench/packages.js new file mode 100644 index 0000000..9f01373 --- /dev/null +++ b/bench/packages.js @@ -0,0 +1,9 @@ +import pkgJson from './package.json' assert { type: 'json' }; +import mainPkgJson from '../package.json' assert { type: 'json' }; + +const packages = Object.fromEntries( + Object.entries(pkgJson.dependencies).map(([name, version]) => [name, `${name}@${version}`])); + +packages['ansis'] = `ansis@${mainPkgJson.version}`; + +export default packages; diff --git a/bench/vitest.config.js b/bench/vitest.config.js new file mode 100644 index 0000000..94ede10 --- /dev/null +++ b/bench/vitest.config.js @@ -0,0 +1,3 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({}); diff --git a/compare/README.md b/compare/README.md index e15511d..f72b5d9 100644 --- a/compare/README.md +++ b/compare/README.md @@ -11,7 +11,7 @@ npm i ## Run ``` -npm run compare +npm start ``` ## View and edit in browser diff --git a/compare/index.js b/compare/index.js index bb557d5..b605fcb 100644 --- a/compare/index.js +++ b/compare/index.js @@ -6,11 +6,15 @@ import colorsJs from 'colors'; import * as colorette from 'colorette'; import ansiColors from 'ansi-colors'; import cliColor from 'cli-color'; -import colorCli from 'colors-cli/lib/color-safe.js'; +import colorCli from 'colors-cli/safe.js'; import kleur from 'kleur'; import * as kleurColors from 'kleur/colors'; +import * as kolorist from 'kolorist'; import picocolors from 'picocolors'; -import ansis, { greenBright, redBright, bgRed, black } from 'ansis'; +import ansis, { greenBright, redBright, bgRed, bgBlueBright, black, yellow, hex } from 'ansis'; + +import { hexToRgb } from '../src/utils.js'; +import spectrum from './spectrum.js'; const log = console.log; @@ -49,64 +53,87 @@ const kleurDeepNestedChainedFixture = () => { }; function showSupportOfDeepNestedStyling() { - log(black.bgGreenBright(`\n Supports the deep nested styling `)); + log(bgBlueBright(`\n -= Supports the deep nested styling =- `)); + logWithLabel(greenBright.inverse` OK `, 'ansis: ', deepNestedFixture(ansis)); + logWithLabel(greenBright.inverse` OK `, 'chalk: ', deepNestedFixture(chalk)); + logWithLabel(greenBright.inverse` OK `, 'kolorist: ', deepNestedFixture(kolorist)); logWithLabel(greenBright.inverse` OK `, 'colors.js: ', deepNestedFixture(colorsJs)); logWithLabel(greenBright.inverse` OK `, 'colorette: ', deepNestedFixture(colorette)); logWithLabel(greenBright.inverse` OK `, 'picocolors: ', deepNestedFixture(picocolors)); logWithLabel(greenBright.inverse` OK `, 'cli-color: ', deepNestedFixture(cliColor)); - logWithLabel(black.bgYellow` BUG `, 'color-cli: ', deepNestedFixture(colorCli)); + logWithLabel(black.bgYellow` BUG `, 'colors-cli: ', deepNestedFixture(colorCli)); logWithLabel(greenBright.inverse` OK `, 'ansi-colors: ', deepNestedFixture(ansiColors)); logWithLabel(greenBright.inverse` OK `, 'kleur/colors: ', deepNestedFixture(kleurColors)); logWithLabel(greenBright.inverse` OK `, 'kleur: ', deepNestedFixture(kleur)); - logWithLabel(greenBright.inverse` OK `, 'chalk: ', deepNestedFixture(chalk)); - logWithLabel(greenBright.inverse` OK `, 'ansis: ', deepNestedFixture(ansis)); } function showSupportOfDeepNestedChainedStyling() { - log(black.bgGreenBright(`\n Supports the deep nested chained styling `)); + log(bgBlueBright(`\n -= Supports the deep nested chained styling =- `)); + logWithLabel(greenBright.inverse` OK `, 'ansis: ', deepNestedChainedFixture(ansis)); + logWithLabel(greenBright.inverse` OK `, 'chalk: ', deepNestedChainedFixture(chalk)); + logWithLabel(bgRed` FAIL `, 'kolorist: ', redBright`not supported`); logWithLabel(greenBright.inverse` OK `, 'colors.js: ', deepNestedChainedFixture(colorsJs)); logWithLabel(bgRed` FAIL `, 'colorette: ', redBright`not supported`); logWithLabel(bgRed` FAIL `, 'picocolors: ', redBright`not supported`); logWithLabel(greenBright.inverse` OK `, 'cli-color: ', deepNestedChainedFixture(cliColor)); - logWithLabel(black.bgYellow` BUG `, 'color-cli: ', deepNestedChainedFixture(colorCli)); + logWithLabel(black.bgYellow` BUG `, 'colors-cli: ', deepNestedChainedFixture(colorCli)); logWithLabel(greenBright.inverse` OK `, 'ansi-colors: ', deepNestedChainedFixture(ansiColors)); logWithLabel(greenBright.inverse` OK `, 'kleur: ', kleurDeepNestedChainedFixture()); - logWithLabel(greenBright.inverse` OK `, 'chalk: ', deepNestedChainedFixture(chalk)); - logWithLabel(greenBright.inverse` OK `, 'ansis: ', deepNestedChainedFixture(ansis)); } function showSupportOfNestedTemplateStrings() { - log(black.bgGreenBright(`\n Supports the nested template strings `)); + log(bgBlueBright(`\n -= Supports the nested template strings =- `)); + logWithLabel(greenBright.inverse` OK `, 'ansis: ', nestedTemplateStringFixture(ansis)); + logWithLabel(bgRed` FAIL `, 'chalk: ', nestedTemplateStringFixture(chalk)); + logWithLabel(bgRed` FAIL `, 'kolorist: ', nestedTemplateStringFixture(kolorist)); logWithLabel(bgRed` FAIL `, 'colors.js: ', nestedTemplateStringFixture(colorsJs)); logWithLabel(bgRed` FAIL `, 'colorette: ', nestedTemplateStringFixture(colorette)); logWithLabel(bgRed` FAIL `, 'picocolors: ', nestedTemplateStringFixture(picocolors)); logWithLabel(bgRed` FAIL `, 'cli-color: ', nestedTemplateStringFixture(cliColor)); - logWithLabel(bgRed` FAIL `, 'color-cli: ', nestedTemplateStringFixture(colorCli)); + logWithLabel(bgRed` FAIL `, 'colors-cli: ', nestedTemplateStringFixture(colorCli)); logWithLabel(bgRed` FAIL `, 'ansi-colors: ', nestedTemplateStringFixture(ansiColors)); logWithLabel(bgRed` FAIL `, 'kleur: ', nestedTemplateStringFixture(kleur)); - logWithLabel(bgRed` FAIL `, 'chalk: ', nestedTemplateStringFixture(chalk)); - logWithLabel(greenBright.inverse` OK `, 'ansis: ', nestedTemplateStringFixture(ansis)); } function showSupportOfBreakStyleAtNewLine() { - log(black.bgGreenBright(`\n Supports the break style at New Line `)); - - logWithLabel(greenBright.inverse` OK `, 'colors.js: ', colorsJs.bgGreen(breakStyleAtNewLineFixture)); - logWithLabel(bgRed` FAIL `, 'colorette: ', colorette.bgGreen(breakStyleAtNewLineFixture)); - logWithLabel(bgRed` FAIL `, 'picocolors: ', picocolors.bgGreen(breakStyleAtNewLineFixture)); - logWithLabel(bgRed` FAIL `, 'cli-color: ', cliColor.bgGreen(breakStyleAtNewLineFixture)); - logWithLabel(bgRed` FAIL `, 'color-cli: ', colorCli.green_b(breakStyleAtNewLineFixture)); - logWithLabel(greenBright.inverse` OK `, 'ansi-colors: ', ansiColors.bgGreen(breakStyleAtNewLineFixture)); - logWithLabel(bgRed` FAIL `, 'kleur/colors: ', kleurColors.bgGreen(breakStyleAtNewLineFixture)); - logWithLabel(bgRed` FAIL `, 'kleur: ', kleur.bgGreen(breakStyleAtNewLineFixture)); - logWithLabel(greenBright.inverse` OK `, 'chalk: ', chalk.bgGreen(breakStyleAtNewLineFixture)); - logWithLabel(greenBright.inverse` OK `, 'ansis: ', ansis.bgGreen(breakStyleAtNewLineFixture)); + log(bgBlueBright(`\n -= Supports the break style at New Line =- `)); + + logWithLabel(greenBright.inverse` OK `, 'ansis: ', ansis.bgCyan(breakStyleAtNewLineFixture)); + logWithLabel(greenBright.inverse` OK `, 'chalk: ', chalk.bgCyan(breakStyleAtNewLineFixture)); + logWithLabel(bgRed` FAIL `, 'kolorist: ', kolorist.bgCyan(breakStyleAtNewLineFixture)); + logWithLabel(greenBright.inverse` OK `, 'colors.js: ', colorsJs.bgCyan(breakStyleAtNewLineFixture)); + logWithLabel(bgRed` FAIL `, 'colorette: ', colorette.bgCyan(breakStyleAtNewLineFixture)); + logWithLabel(bgRed` FAIL `, 'picocolors: ', picocolors.bgCyan(breakStyleAtNewLineFixture)); + logWithLabel(bgRed` FAIL `, 'cli-color: ', cliColor.bgCyan(breakStyleAtNewLineFixture)); + logWithLabel(bgRed` FAIL `, 'colors-cli: ', colorCli.cyan_b(breakStyleAtNewLineFixture)); + logWithLabel(greenBright.inverse` OK `, 'ansi-colors: ', ansiColors.bgCyan(breakStyleAtNewLineFixture)); + logWithLabel(bgRed` FAIL `, 'kleur/colors: ', kleurColors.bgCyan(breakStyleAtNewLineFixture)); + logWithLabel(bgRed` FAIL `, 'kleur: ', kleur.bgCyan(breakStyleAtNewLineFixture)); +} + +function showFallbackToSupportedColorSpace() { + let fallbackColors; + + log(bgBlueBright(`\n -= Supports the fallback to color space =- `)); + + fallbackColors = ''; + spectrum.forEach(color => {fallbackColors += hex(color)('█');}); + logWithLabel(greenBright.inverse` OK `, 'ansis: ', fallbackColors); + + fallbackColors = ''; + spectrum.forEach(color => {fallbackColors += chalk.hex(color)('█');}); + logWithLabel(greenBright.inverse` OK `, 'chalk: ', fallbackColors); + + fallbackColors = ''; + spectrum.forEach(color => {fallbackColors += kolorist.trueColor(...hexToRgb(color))('█');}); + logWithLabel(black.bgYellow` +/- `, 'kolorist: ', fallbackColors + yellow` (not for 16 color space)`); } showSupportOfDeepNestedStyling(); showSupportOfDeepNestedChainedStyling(); showSupportOfNestedTemplateStrings(); showSupportOfBreakStyleAtNewLine(); +showFallbackToSupportedColorSpace(); diff --git a/compare/package.json b/compare/package.json index fb6dadc..5b55e84 100644 --- a/compare/package.json +++ b/compare/package.json @@ -6,15 +6,15 @@ "start": "node ./" }, "dependencies": { - "ansi-colors": "4.1.1", + "ansi-colors": "4.1.3", "ansis": "file:../dist", - "benchmark": "2.1.4", - "chalk": "5.0.0", - "cli-color": "2.0.1", + "chalk": "5.3.0", + "cli-color": "2.0.4", "colors": "1.4.0", - "colorette": "2.0.16", - "colors-cli": "1.0.28", - "kleur": "4.1.4", + "colorette": "2.0.20", + "colors-cli": "1.0.33", + "kleur": "4.1.5", + "kolorist": "1.8.0", "picocolors": "1.0.0" } } diff --git a/compare/spectrum.js b/compare/spectrum.js new file mode 100644 index 0000000..6a486d6 --- /dev/null +++ b/compare/spectrum.js @@ -0,0 +1,50 @@ +export default [ + '#ff0000', + '#ff0021', + '#ff0041', + '#ff0062', + '#ff0082', + '#ff00a3', + '#ff00c3', + '#ff00e4', + '#fa00ff', + '#d900ff', + '#b900ff', + '#9800ff', + '#7800ff', + '#5700ff', + '#3700ff', + '#1600ff', + '#000bff', + '#002bff', + '#004cff', + '#006cff', + '#008dff', + '#00adff', + '#00ceff', + '#00eeff', + '#00ffef', + '#00ffcf', + '#00ffae', + '#00ff8e', + '#00ff6d', + '#00ff4d', + '#00ff2c', + '#00ff0c', + '#15ff00', + '#36ff00', + '#56ff00', + '#77ff00', + '#97ff00', + '#b8ff00', + '#d8ff00', + '#f9ff00', + '#ffe500', + '#ffc400', + '#ffa400', + '#ff8300', + '#ff6300', + '#ff4200', + '#ff2200', + '#ff0100', +]; diff --git a/examples/ansis-styles-demo.js b/examples/ansis-styles-demo.js index a9fc4b1..9fc0181 100644 --- a/examples/ansis-styles-demo.js +++ b/examples/ansis-styles-demo.js @@ -48,13 +48,9 @@ const out = `${bold`bold`} ${dim`dim`} ${italic`italic`} ${underline`underline`} '\n' + [ '#d93611', - //'#d97511', '#d9d609', - //'#a0d911', '#18d911', - //'#11d9c2', '#099dd9', - //'#1157d9', '#7a09f6', '#c509d9', '#f10794', diff --git a/package.json b/package.json index 3fe22af..9c4bfbf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ansis", - "version": "3.0.0", + "version": "3.0.1", "description": "Colorize text in terminal and console output with ANSI colors & styles", "keywords": [ "ansi", diff --git a/pkg/package.json b/pkg/package.json index c8db9c6..49f8cb9 100644 --- a/pkg/package.json +++ b/pkg/package.json @@ -1,6 +1,6 @@ { "name": "ansis", - "version": "3.0.0", + "version": "3.0.1", "description": "Colorize text in terminal and console output with ANSI colors & styles", "keywords": [ "ansi", diff --git a/src/ansi-codes.js b/src/ansi-codes.js index 76c2cad..7c71083 100644 --- a/src/ansi-codes.js +++ b/src/ansi-codes.js @@ -9,6 +9,10 @@ const esc = hasColor ? (open, close) => ({ open: `\x1b[${open}m`, close: `\x1b[$ const closeCode = 39; const bgCloseCode = 49; +const ESC = '\x1b'; +const BEL = '\x07'; +const ZWSP = '\u200B'; + // defaults, true color let fnAnsi256 = (code) => esc(`38;5;${code}`, closeCode); let fnBgAnsi256 = (code) => esc(`48;5;${code}`, bgCloseCode); @@ -17,6 +21,11 @@ let fnBgRgb = (r, g, b) => esc(`48;2;${r};${g};${b}`, bgCloseCode); const createRgbFn = (fn) => (r, g, b) => fn(rgbToAnsi256(r, g, b)); +const createHexFn = (fn) => (hex) => { + let [r, g, b] = hexToRgb(hex); + return fn(r, g, b); +}; + if (colorSpace === 1) { // ANSI 16 colors fnAnsi256 = (code) => esc(ansi256To16(code), closeCode); @@ -25,8 +34,6 @@ if (colorSpace === 1) { fnBgRgb = (r, g, b) => esc(rgbToAnsi16(r, g, b) + 10, bgCloseCode); } else if (colorSpace === 2) { // ANSI 256 colors - //fnRgb = (r, g, b) => fnAnsi256(rgbToAnsi256(r, g, b)); - //fnBgRgb = (r, g, b) => fnBgAnsi256(rgbToAnsi256(r, g, b)); fnRgb = createRgbFn(fnAnsi256); fnBgRgb = createRgbFn(fnBgAnsi256); } @@ -47,50 +54,62 @@ export const baseStyles = { strike: esc(9, 29), // alias for strikethrough // foreground colors - black: esc(30, closeCode), - red: esc(31, closeCode), - green: esc(32, closeCode), - yellow: esc(33, closeCode), - blue: esc(34, closeCode), - magenta: esc(35, closeCode), - cyan: esc(36, closeCode), - white: esc(37, closeCode), + // black: esc(30, closeCode), + // red: esc(31, closeCode), + // green: esc(32, closeCode), + // yellow: esc(33, closeCode), + // blue: esc(34, closeCode), + // magenta: esc(35, closeCode), + // cyan: esc(36, closeCode), + // white: esc(37, closeCode), grey: esc(90, closeCode), // UK spelling alias for blackBright gray: esc(90, closeCode), // US spelling alias for blackBright - blackBright: esc(90, closeCode), - redBright: esc(91, closeCode), - greenBright: esc(92, closeCode), - yellowBright: esc(93, closeCode), - blueBright: esc(94, closeCode), - magentaBright: esc(95, closeCode), - cyanBright: esc(96, closeCode), - whiteBright: esc(97, closeCode), + // blackBright: esc(90, closeCode), + // redBright: esc(91, closeCode), + // greenBright: esc(92, closeCode), + // yellowBright: esc(93, closeCode), + // blueBright: esc(94, closeCode), + // magentaBright: esc(95, closeCode), + // cyanBright: esc(96, closeCode), + // whiteBright: esc(97, closeCode), // background colors - bgBlack: esc(40, bgCloseCode), - bgRed: esc(41, bgCloseCode), - bgGreen: esc(42, bgCloseCode), - bgYellow: esc(43, bgCloseCode), - bgBlue: esc(44, bgCloseCode), - bgMagenta: esc(45, bgCloseCode), - bgCyan: esc(46, bgCloseCode), - bgWhite: esc(47, bgCloseCode), + // bgBlack: esc(40, bgCloseCode), + // bgRed: esc(41, bgCloseCode), + // bgGreen: esc(42, bgCloseCode), + // bgYellow: esc(43, bgCloseCode), + // bgBlue: esc(44, bgCloseCode), + // bgMagenta: esc(45, bgCloseCode), + // bgCyan: esc(46, bgCloseCode), + // bgWhite: esc(47, bgCloseCode), bgGrey: esc(100, bgCloseCode), // UK spelling alias for bgBlackBright bgGray: esc(100, bgCloseCode), // US spelling alias for bgBlackBright - bgBlackBright: esc(100, bgCloseCode), - bgRedBright: esc(101, bgCloseCode), - bgGreenBright: esc(102, bgCloseCode), - bgYellowBright: esc(103, bgCloseCode), - bgBlueBright: esc(104, bgCloseCode), - bgMagentaBright: esc(105, bgCloseCode), - bgCyanBright: esc(106, bgCloseCode), - bgWhiteBright: esc(107, bgCloseCode), + // bgBlackBright: esc(100, bgCloseCode), + // bgRedBright: esc(101, bgCloseCode), + // bgGreenBright: esc(102, bgCloseCode), + // bgYellowBright: esc(103, bgCloseCode), + // bgBlueBright: esc(104, bgCloseCode), + // bgMagentaBright: esc(105, bgCloseCode), + // bgCyanBright: esc(106, bgCloseCode), + // bgWhiteBright: esc(107, bgCloseCode), }; -const createHexFn = (fn) => (hex) => { - let [r, g, b] = hexToRgb(hex); - return fn(r, g, b); -}; +// generate ANSI 16 colors dynamically and save ~450 bytes +let styles = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white']; +let bright = 'Bright'; +let code = 30; +let name, bgName; + +for (name of styles) { + bgName = 'bg' + name[0].toUpperCase() + name.slice(1); + + baseStyles[name] = esc(code, closeCode); + baseStyles[name + bright] = esc(code + 60, closeCode); + baseStyles[bgName] = esc(code + 10, bgCloseCode); + baseStyles[bgName + bright] = esc(code + 70, bgCloseCode); + + code++; +} export const styleMethods = { fg: fnAnsi256, @@ -103,6 +122,11 @@ export const styleMethods = { // note: the `...` operator is too slow //bgHex: (hex) => fnBgRgb(...hexToRgb(hex)), bgHex: createHexFn(fnBgRgb), + + // reserved for future: hyperlink (OSC 8) is not widely supported (works in iTerm) + // link: hasColor + // ? (url) => ({ open: ESC + ']8;;' + url + BEL, close: ESC + ']8;;' + BEL }) + // : (url) => ({ open: '', close: `(${ZWSP}${url}${ZWSP})` }), }; export const rgb = fnRgb; diff --git a/src/index.js b/src/index.js index 84ffd8a..a5e10ef 100644 --- a/src/index.js +++ b/src/index.js @@ -12,8 +12,9 @@ import { hexToRgb, replaceAll } from './utils.js'; const { defineProperty, defineProperties, setPrototypeOf } = Object; const stripANSIRegEx = /[›][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; -const regexLF = /(\r*\n)/g; +const regexLFCR = /(\r?\n)/g; const ESC = '\x1b'; +const LF = '\x0a'; const styles = {}; /** @@ -27,20 +28,20 @@ const styles = {}; const wrap = (strings, values, props) => { if (!strings) return ''; - const { openStack, closeStack } = props; + const { _a: openStack, _b: closeStack } = props; // convert the number to the string let string = strings.raw != null ? String.raw(strings, ...values) : strings + ''; - if (~string.indexOf(ESC)) { + if (string.includes(ESC)) { while (props != null) { string = replaceAll(string, props.close, props.open); // much faster than native replaceAll //string = string.replaceAll(props.close, props.open); // too slow! - props = props.props; + props = props._p; } } - if (~string.indexOf('\n')) { - string = string.replace(regexLF, closeStack + '$1' + openStack); + if (string.includes(LF)) { + string = string.replace(regexLFCR, closeStack + '$1' + openStack); } return openStack + string + closeStack; @@ -48,24 +49,24 @@ const wrap = (strings, values, props) => { /** * @param {Object} self - * @param {AnsisProps} self.props + * @param {AnsisProps} self._p * @param {Object} codes * @param {string} codes.open * @param {string} codes.close * @returns {Ansis} */ -const createStyle = ({ props }, { open, close }) => { - const style = (strings, ...values) => wrap(strings, values, style.props); +const createStyle = ({ _p: props }, { open, close }) => { + const style = (strings, ...values) => wrap(strings, values, style._p); let openStack = open; let closeStack = close; if (props != null) { - openStack = props.openStack + open; - closeStack = close + props.closeStack; + openStack = props._a + open; + closeStack = close + props._b; } setPrototypeOf(style, stylePrototype); - style.props = { open, close, openStack, closeStack, props: props }; + style._p = { open, close, _a: openStack, _b: closeStack, _p: props }; style.open = openStack; style.close = closeStack; @@ -97,8 +98,8 @@ const Ansis = function() { for (let name in colors) { let value = colors[name]; // detect whether the value is style property Object {open, close} or a string with hex code of color '#FF0000' - let hasProperty = value.open != null; - let styleCodes = hasProperty ? value : rgb(...hexToRgb(value)); + let isStyle = value.open != null; + let styleCodes = isStyle ? value : rgb(...hexToRgb(value)); styles[name] = { get() { @@ -134,6 +135,7 @@ styles.bgAnsi256 = styles.bg; // note: place it here to allow the compiler to group all constants let stylePrototype; + const ansis = new Ansis(); // for distribution code, the export will be replaced (via @rollup/plugin-replace) with the following export: diff --git a/test/index.test.js b/test/index.test.js index a4e4144..6c412e4 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -31,12 +31,6 @@ describe('style tests', () => { expect(esc(received)).toEqual(esc(expected)); }); - test(`ansis.bgMagenta('foo')`, () => { - const received = ansis.bgMagenta('foo'); - const expected = '\x1b[45mfoo\x1b[49m'; - expect(esc(received)).toEqual(esc(expected)); - }); - test(`ansis.green.bold.underline.italic()`, () => { const received = ansis.green.bold.underline.italic('foo'); const expected = '\x1b[32m\x1b[1m\x1b[4m\x1b[3mfoo\x1b[23m\x1b[24m\x1b[22m\x1b[39m'; @@ -94,6 +88,13 @@ describe('style tests', () => { \x1b[32m\x1b[39m`; expect(esc(received)).toEqual(esc(expected)); }); + + // experimental link: not widely supported + // test(`ansis.link('foo')`, () => { + // const received = ansis.link('https://github.com/webdiscus/ansis')('foo'); + // const expected = '\x1b]8;;https://github.com/webdiscus/ansis\x07foo\x1b]8;;\x07'; + // expect(esc(received)).toEqual(esc(expected)); + // }); }); describe('functional tests', () => { @@ -275,4 +276,170 @@ describe('handling numbers', () => { const expected = '\x1b[31msize: 123px\x1b[39m'; expect(esc(received)).toEqual(esc(expected)); }); +}); + +describe('base ANSI 16 colors', () => { + // foreground colors + test(`ansis.black('foo')`, () => { + const received = ansis.black('foo'); + const expected = '\x1b[30mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.red('foo')`, () => { + const received = ansis.red('foo'); + const expected = '\x1b[31mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.green('foo')`, () => { + const received = ansis.green('foo'); + const expected = '\x1b[32mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.yellow('foo')`, () => { + const received = ansis.yellow('foo'); + const expected = '\x1b[33mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.blue('foo')`, () => { + const received = ansis.blue('foo'); + const expected = '\x1b[34mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.magenta('foo')`, () => { + const received = ansis.magenta('foo'); + const expected = '\x1b[35mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.cyan('foo')`, () => { + const received = ansis.cyan('foo'); + const expected = '\x1b[36mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.white('foo')`, () => { + const received = ansis.white('foo'); + const expected = '\x1b[37mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.blackBright('foo')`, () => { + const received = ansis.blackBright('foo'); + const expected = '\x1b[90mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.redBright('foo')`, () => { + const received = ansis.redBright('foo'); + const expected = '\x1b[91mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.greenBright('foo')`, () => { + const received = ansis.greenBright('foo'); + const expected = '\x1b[92mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.yellowBright('foo')`, () => { + const received = ansis.yellowBright('foo'); + const expected = '\x1b[93mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.blueBright('foo')`, () => { + const received = ansis.blueBright('foo'); + const expected = '\x1b[94mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.magentaBright('foo')`, () => { + const received = ansis.magentaBright('foo'); + const expected = '\x1b[95mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.cyanBright('foo')`, () => { + const received = ansis.cyanBright('foo'); + const expected = '\x1b[96mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.whiteBright('foo')`, () => { + const received = ansis.whiteBright('foo'); + const expected = '\x1b[97mfoo\x1b[39m'; + expect(esc(received)).toEqual(esc(expected)); + }); + + // background colors + test(`ansis.bgBlack('foo')`, () => { + const received = ansis.bgBlack('foo'); + const expected = '\x1b[40mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgRed('foo')`, () => { + const received = ansis.bgRed('foo'); + const expected = '\x1b[41mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgGreen('foo')`, () => { + const received = ansis.bgGreen('foo'); + const expected = '\x1b[42mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgYellow('foo')`, () => { + const received = ansis.bgYellow('foo'); + const expected = '\x1b[43mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgBlue('foo')`, () => { + const received = ansis.bgBlue('foo'); + const expected = '\x1b[44mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgMagenta('foo')`, () => { + const received = ansis.bgMagenta('foo'); + const expected = '\x1b[45mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgCyan('foo')`, () => { + const received = ansis.bgCyan('foo'); + const expected = '\x1b[46mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgWhite('foo')`, () => { + const received = ansis.bgWhite('foo'); + const expected = '\x1b[47mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgBlackBright('foo')`, () => { + const received = ansis.bgBlackBright('foo'); + const expected = '\x1b[100mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgRedBright('foo')`, () => { + const received = ansis.bgRedBright('foo'); + const expected = '\x1b[101mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgGreenBright('foo')`, () => { + const received = ansis.bgGreenBright('foo'); + const expected = '\x1b[102mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgYellowBright('foo')`, () => { + const received = ansis.bgYellowBright('foo'); + const expected = '\x1b[103mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgBlueBright('foo')`, () => { + const received = ansis.bgBlueBright('foo'); + const expected = '\x1b[104mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgMagentaBright('foo')`, () => { + const received = ansis.bgMagentaBright('foo'); + const expected = '\x1b[105mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgCyanBright('foo')`, () => { + const received = ansis.bgCyanBright('foo'); + const expected = '\x1b[106mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); + test(`ansis.bgWhiteBright('foo')`, () => { + const received = ansis.bgWhiteBright('foo'); + const expected = '\x1b[107mfoo\x1b[49m'; + expect(esc(received)).toEqual(esc(expected)); + }); }); \ No newline at end of file