Skip to content

Commit 21ae59d

Browse files
authored
Merge pull request #43 from webdiscus/next
Next
2 parents e729bab + 613ddcb commit 21ae59d

19 files changed

+698
-480
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Changelog
22

3+
## 4.2.0 (2025-09-20)
4+
5+
- feat: add support named truecolor via `ansis.extend()`.
6+
Foreground methods are created from the provided color names, and matching background methods `bg*` are generated automatically.
7+
Example:
8+
```js
9+
import ansis from 'ansis';
10+
import colorNames from 'css-color-names';
11+
12+
const color = ansis.extend(colorNames);
13+
14+
console.log(color.orange('Orange foreground'));
15+
console.log(color.bgOrange('Orange background')); // auto-generated from "orange"
16+
```
17+
This release removes the last barrier for projects migrating from Chalk v4 that used named truecolor, e.g.
18+
`chalk.keyword('orange')('text')`. Ansis now provides this feature with a simpler, more intuitive API.
19+
320
## 4.1.0 (2025-05-28)
421

522
- feat: add readonly `level` property to get the detected color support level:

README.md

Lines changed: 82 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,16 @@ Ansis is focused on [small size](#compare-size) and [speed](#benchmark) while pr
3838
- Nested [tagged template strings](#template-literals): ``` red`Error: ${blue`file.js`} not found!` ```
3939
- [ANSI styles](#styles): `dim` **`bold`** _`italic`_ <u>`underline`</u> <s>`strikethrough`</s>
4040
- [ANSI 16 colors](#base-colors): `red`, `redBright`, `bgRed`, `bgRedBright`, ...
41-
- [ANSI 256 colors](#256-colors): `fg()`, `bg()`
42-
- [Truecolor](#truecolor) (**RGB & HEX**): `rgb()`, `bgRgb()`, `hex()`, `bgHex()`
43-
- [Extend](#extend-colors) base colors with [**named truecolor**](https://drafts.csswg.org/css-color/#named-colors) via `ansis.extend()`, then use colors by name, e.g. `ansis.pink()`
41+
- [ANSI 256 colors](#256-colors) via methods: `fg(num)`, `bg(num)`
42+
- [Truecolor](#truecolor) via methods: `rgb(r,g,b)`, `bgRgb(r,g,b)`, `hex('#rrggbb')`, `bgHex('#rrggbb')`
43+
- [Named truecolors](#extend-colors) (extend with colors such as [orange, pink, navy, ...](https://drafts.csswg.org/css-color/#named-colors)): `ansis.pink()`, `ansis.bgPink()`, ...
4444
- Auto-detects [color support](#color-support): Truecolor, 256 colors, 16 colors, no colors
4545
- Automatic [fallback](#fallback): Truecolor → 256 colors → 16 colors → no colors
4646
- Raw ANSI escape codes: ``` `File ${red.open}not found${red.close} in directory` ```
4747
- Strip ANSI escape codes with `ansis.strip()`
4848
- Supports [ENV variables](#cli-vars) and [flags](#cli-flags): [`NO_COLOR`](using-env-no-color), [`FORCE_COLOR`](#using-env-force-color), [`COLORTERM`](#using-env-colorterm), `--no-color`, `--color`
49-
- Enables reliable [CLI testing](#cli-testing) with forced [color levels](#color-levels): no color, 16, 256 or Truecolor
50-
- Replacement for [`chalk`](#replacing-chalk) [`ansi-colors`](#replacing-ansi-colors) [`colorette`](#replacing-colorette) [`picocolors`](#replacing-picocolors) and others [alternatives](#alternatives)
49+
- Reliable [CLI testing](#cli-testing) with forced [color levels](#color-levels): no color, 16, 256 or Truecolor
50+
- Drop-in replacement for [`chalk`](#replacing-chalk) [`ansi-colors`](#replacing-ansi-colors) [`colorette`](#replacing-colorette) [`picocolors`](#replacing-picocolors) and others [alternatives](#alternatives)
5151

5252

5353
> 🚀 **You might also like** [`flaget`](https://github.com/webdiscus/flaget) - a smaller (5 kB) and faster alternative to [`yargs-parser`](https://www.npmjs.com/package/yargs-parser) (85 kB) for CLI argument parsing.
@@ -449,7 +449,7 @@ If a terminal supports only 16 colors then ANSI 256 colors will be interpolated
449449
</a>
450450
</div>
451451

452-
#### Usage example
452+
#### Example
453453

454454
```js
455455
import { bold, fg, bg } from 'ansis';
@@ -512,26 +512,30 @@ If you use the `hex()`, `rgb()` or `ansis256()` functions in a terminal not supp
512512

513513
![output](https://github.com/webdiscus/ansis/raw/master/docs/img/ansis-fallback.png?raw=true "Fallback to ANSI colors")
514514

515+
See also [fallback for named truecolors](#fallback-for-named-truecolors).
516+
515517
#### [↑ top](#top)
516518

517519
<a id="extend-colors" name="extend-colors"></a>
518520

519-
## Named truecolor
521+
## Named truecolors
520522

521523
Ansis supports full 24-bit color via `ansis.rgb(r, g, b)` and `ansis.hex('#rrggbb')`.\
522-
If you prefer [**named colors**](http://dev.w3.org/csswg/css-color/#named-colors) (e.g. `beige`, `orange`, `pink`, `royalblue`, etc.) instead of writing hex or RGB values by hand,
523-
resolve color names in your app and register them as extended styles on an Ansis instance via `ansis.extend()`.
524-
Then you can call `color.pink()` rather than using `ansis.hex()` or `ansis.rgb()` directly.
524+
If you prefer [**named colors**](http://dev.w3.org/csswg/css-color/#named-colors) (e.g. `orange`, `pink`, `navy`, etc.)
525+
instead of writing hex or RGB values by hand, resolve color names in your app and register them as extended styles on an Ansis instance via `ansis.extend()`.
526+
Then you can call e.g., `color.pink()` or `color.bgPink()` rather than using `ansis.hex('#ffc0cb')` or `ansis.bgHex('#ffc0cb')` directly.
527+
528+
> [!IMPORTANT]
529+
> Foreground methods are created from the provided color names, and matching background methods `bg*` are generated automatically.
525530
526531
> [!NOTE]
527-
> To keep Ansis small, it doesn't bundle large color name tables.\
532+
> To keep Ansis small, it doesn't bundle large truecolors name table.\
528533
> Use any mapping package you like, e.g. [css-color-names](https://www.npmjs.com/package/css-color-names) (~6 kB).
534+
> ```bash
535+
> npm i css-color-names
536+
> ```
529537
530-
```bash
531-
npm i css-color-names
532-
```
533-
534-
**Example (extend with all color names)**
538+
**Example (extend with all [CSS color names](http://dev.w3.org/csswg/css-color/#named-colors) )**
535539
536540
```js
537541
import ansis from 'ansis';
@@ -542,47 +546,59 @@ import colorNames from 'css-color-names';
542546
const color = ansis.extend(colorNames);
543547
544548
// All color names are now avaliable as chainable methods on the extended instance:
545-
console.log(color.pink('Pink color'));
546-
console.log(color.pink.underline('Pink color underlined'));
549+
console.log(color.pink('Pink foreground'));
550+
console.log(color.bgPink('Pink background')); // auto-generated from "pink"
551+
```
547552
548-
// You can achieve the same result without extend():
549-
console.log(ansis.hex(colorNames.pink).underline('Pink color via hex'));
553+
If you prefer to keep the `ansis` namespace:
554+
555+
```js
556+
import { Ansis } from 'ansis';
557+
import colorNames from 'css-color-names';
558+
559+
// Create a new instance and extend it with colors
560+
const ansis = new Ansis().extend(colorNames);
561+
console.log(ansis.pink('Pink foreground'));
562+
console.log(ansis.bgPink('Pink background'));
550563
```
551564
552-
Of course, you can define a custom subset with only the names you actually use.
565+
Of course, you can define a custom subset with only the colors you actually use.
553566
554567
> [!TIP]
555568
> Need help picking a color name? Try the [Name that Color](https://chir.ag/projects/name-that-color/#FF681F) tool - paste a hex and get its closest color name.
556569
557570
**Example (custom subset)**
558571
559572
```js
560-
import { Ansis } from 'ansis';
573+
import ansis from 'ansis';
561574
562575
const myTheme = {
563576
orange: '#ffa500',
564577
pink: '#ffc0cb',
565578
};
566579
567-
// Create a new instance and extend it with only your custom names
568-
const ansis = new Ansis().extend(myTheme);
580+
// Extend with only your colors
581+
const color = ansis.extend(myTheme);
569582
570583
// You can still use base styles together with extended ones
571-
const { orange, pink, red } = ansis;
584+
const { orange, pink, bgPink, red } = color;
572585
573-
console.log(ansis.orange.bold('orange bold')); // extended + base
574-
console.log(orange.italic`orange italic`); // tagged-template syntax
575-
console.log(pink`pink color`); // extended as a tag
576-
console.log(red('built-in red still works')); // built-in remains intact
586+
console.log(color.orange('orange foreground')); // extended foreground
587+
console.log(color.bgOrange('orange background')); // extended background
588+
console.log(orange.italic`orange italic`); // extended + base style
589+
console.log(pink`pink foreground`); // extended as a tag
590+
console.log(bgPink`pink background`); // extended as a tag
591+
console.log(red('built-in red still works')); // built-in remains intact
577592
```
578593
579594
**TypeScript example**
580595
581596
```ts
582597
import ansis, { AnsiColors } from 'ansis';
583598
584-
// Extends the built-in `AnsiColors` type with custom user defined color names
585-
type AnsiColorsExtend<T extends string> = AnsiColors | (T & Record<never, never>);
599+
// Extends the built-in `AnsiColors` type with truecolor names
600+
// and their auto-generated bg* color names
601+
type AnsiColorsExtend<T extends string> = AnsiColors | T | `bg${Capitalize<T>}`;
586602
587603
const myTheme = {
588604
orange: '#ffa500',
@@ -597,9 +613,15 @@ const log = (style: AnsiColorsExtend<keyof typeof myTheme>, message: string) =>
597613
console.log(color[style](message));
598614
}
599615
600-
log('red', 'base color OK'); // ✅ built-in
601-
log('orange', 'extended OK'); // ✅ extended
602-
// log('unknown', 'nope'); // ❌ TypeScript error
616+
log('red', 'red color'); // ✅ built-in
617+
log('bgRed', 'red background'); // ✅ built-in background
618+
log('orange', 'orange color'); // ✅ extended
619+
log('bgOrange', 'orange background'); // ✅ auto-generated background from extended
620+
621+
console.log(color.pink`pink foreground`); // ✅ extended
622+
console.log(color.bgPink`pink background`); // ✅ auto-generated background from extended
623+
624+
// log('unknown', 'nope'); // ❌ TypeScript error
603625
```
604626
605627
> [!WARNING]
@@ -611,6 +633,32 @@ log('orange', 'extended OK'); // ✅ extended
611633
> color.bold.orange('bold orange'); // ❌ won't work: extended is on a sub-chain
612634
> ```
613635
636+
637+
<a id="fallback-for-named-truecolors"></a>
638+
### Fallback for named truecolors
639+
640+
Ansis automatically interpolates named truecolors to the highest available color level supported by the current environment.
641+
So you can safely use named truecolors anywhere without worrying about compatibility.
642+
643+
Example:
644+
```js
645+
import ansis from 'ansis';
646+
import colorNames from 'css-color-names';
647+
648+
const color = ansis.extend(colorNames);
649+
650+
console.log(color.orange('Text'));
651+
```
652+
653+
Output depending on terminal color support:
654+
655+
| Color level | Result | Example output |
656+
|--------------------|------------------------------------|------------------------------------|
657+
| Truecolor / 24-bit | `rgb(255,165,0)` (orange) | `\x1b[38;2;255;165;0mText\x1b[39m` |
658+
| 256 colors | [palette index](#256-colors) `214` | `\x1b[38;5;214mText\x1b[39m` |
659+
| 16 colors | code `93` (bright yellow) | `\x1b[93mText\x1b[39m` |
660+
| No color | plain text | `Text` |
661+
614662
---
615663
616664
#### [↑ top](#top)

bench/index.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,22 @@ import Bench from './lib/bench.js';
4141
import packages from './packages.js';
4242
import { colorLevels, LEVEL_256COLORS } from '../src/color-levels.js';
4343

44+
// Single source of truth: which libs to run
45+
// - Edit the DEFAULT_ENABLED array, OR
46+
// - override via env: BENCH_LIBS="chalk,ansis,picocolors" node bench.js
47+
const DEFAULT_ENABLED = [
48+
...Object.keys(packages),
49+
];
50+
51+
const ENABLED = (process.env.BENCH_LIBS || DEFAULT_ENABLED.join(',')).split(',').map(s => packages[s.trim()]).filter(Boolean);
52+
53+
// test only this libs
54+
// const ENABLED = [
55+
// packages['chalk'],
56+
// packages['ansis'],
57+
// packages['picocolors'],
58+
// ];
59+
4460
// create a new instance of Ansis for correct measure in benchmark
4561
const ansis = new Ansis();
4662
const colorLevel = ansis.level;
@@ -65,11 +81,22 @@ const bench = new Bench({
6581
rmeColor: benchStyle.cyan,
6682
statUnitColor: benchStyle.dim,
6783
failColor: benchStyle.red.bold,
68-
});
84+
}, ENABLED);
6985

7086
log();
7187
log(hex('#F88').inverse.bold` -= Benchmark =- `);
7288

89+
// RGB colors
90+
bench('RGB colors').
91+
add(packages['chalk'], () => { for (let i = 0; i < 256; i++) chalk.rgb(i, 150, 200)('foo'); }).
92+
add(packages['ansis'], () => { for (let i = 0; i < 256; i++) ansis.rgb(i, 150, 200)('foo'); }).
93+
run();
94+
95+
// HEX colors (only chalk & ansis support hex()/rgb()/bgHex()/bgRgb())
96+
bench('HEX colors').
97+
add(packages['chalk'], () => chalk.hex('#FBA')('foo')).
98+
add(packages['ansis'], () => ansis.hex('#FBA')('foo')).run();
99+
73100
const text3 = 'foo';
74101
const text60 = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit sed.';
75102

@@ -103,7 +130,6 @@ bench('Simple long text, 60 chars, using 1 style').
103130
add(packages['@colors/colors'], () => colorsJs.red(text60)).
104131
run();
105132

106-
107133
// Fastest way for 2 styles
108134
bench(`Use 2 styles`).
109135
add(packages['chalk'], () => chalk.red.bold('foo')).
@@ -247,11 +273,9 @@ bench('Picocolors complex bench').
247273
run();
248274

249275
// Check support of correct break style at new line
250-
251-
// Break style at new line
252276
const breakStyleAtNewLineFixture = `\nAnsis\nNEW LINE\nNEXT NEW LINE\n`;
253277
bench('New Line').
254-
add('colors.js', () => colorsJs.bgGreen(breakStyleAtNewLineFixture)).
278+
add(packages['@colors/colors'], () => colorsJs.bgGreen(breakStyleAtNewLineFixture)).
255279
add(packages['ansi-colors'], () => ansiColors.bgGreen(breakStyleAtNewLineFixture)).
256280
add(packages['chalk'], () => chalk.bgGreen(breakStyleAtNewLineFixture)).
257281
// 2x slower as chalk because chalk use own implementation, but ansis save 400 bytes and uses regexp, this speed is not critical

bench/lib/bench.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,18 @@ class Bench {
7676
name = 'Bench';
7777
benchNames = [];
7878
maxNameWidth = 0;
79+
enabledBenchmarks = null;
7980

8081
/**
8182
* @param {BenchOptions} options
8283
* @returns {function(suiteName: string): Bench}
8384
*/
84-
constructor(options = {}) {
85+
constructor(options = {}, enabledBenchmarks) {
8586
this.options = Object.assign(defaultOptions, options);
8687
showResult = showResult.bind(this);
8788

89+
this.enabledBenchmarks = new Set([...enabledBenchmarks]);
90+
8891
return (suiteName) => {
8992
if (suiteName) this.name = suiteName;
9093
this.suite = new Benchmark.Suite(suiteName);
@@ -101,6 +104,10 @@ class Bench {
101104
* @returns {Bench}
102105
*/
103106
add(name, fn) {
107+
if (!this.enabledBenchmarks.has(name)) {
108+
return this;
109+
}
110+
104111
this.benchNames.push(name);
105112
this.suite.add(name, {
106113
onStart: () => {},

bench/package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
"types": "lib/bench.d.ts",
66
"scripts": {
77
"bench": "node ./index.js",
8-
"bench:truecolor": "node truecolor.bench.js",
9-
"bench:vi": "vitest bench vitest.bench.js"
8+
"bench:truecolor": "node truecolor.bench.js"
109
},
1110
"dependencies": {
1211
"@colors/colors": "1.6.0",
@@ -20,7 +19,6 @@
2019
"colors-cli": "1.0.33",
2120
"kleur": "4.1.5",
2221
"kolorist": "1.8.0",
23-
"picocolors": "1.1.1",
24-
"vitest": "^3.2.0"
22+
"picocolors": "1.1.1"
2523
}
2624
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ansis",
3-
"version": "4.1.0",
3+
"version": "4.2.0",
44
"description": "A small and fast ANSI color library",
55
"keywords": [
66
"ansi",

package.npm-node10.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name":"ansis",
3-
"version":"4.1.0-node10",
3+
"version":"4.2.0-node10",
44
"description":"ANSI color lib",
55
"keywords":["ansi","colors","cli"],
66
"license":"ISC",

package.npm-node14.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name":"ansis",
3-
"version":"4.1.0",
3+
"version":"4.2.0",
44
"description":"ANSI color lib",
55
"keywords":["ansi","colors","cli"],
66
"license":"ISC",

package.test-node14.json

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,7 @@
1414
}
1515
},
1616
"scripts": {
17-
"test": "vitest run",
18-
"test:unit": "vitest run ./test/unit.test.js",
19-
"test:ansi16": "vitest run ./test/ansi16.test.js",
20-
"test:ansi256": "vitest run ./test/ansi256.test.js",
21-
"test:functional": "vitest run ./test/functional.test.js",
22-
"test:flags": "vitest run ./test/flags.test.js",
23-
"test:package": "vitest run ./test/package.test.js",
24-
"test:cjs": "node ./test/package/cjs/test.cjs",
25-
"test:esm": "node ./test/package/esm/test.mjs",
26-
"test:tsc": "vitest run ./test/ts-compiler.test.js",
27-
"test:levels": "vitest run ./test/color-levels.test.js"
17+
"test": "vitest run"
2818
},
2919
"engines": {
3020
"node": ">=14"

rollup.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const babelOptions = {
2828
// https://github.com/terser/terser#compress-options
2929
const terserOptions = (ecma) => ({
3030
ecma,
31+
toplevel: true,
3132
compress: {
3233
ecma,
3334
passes: 3,
@@ -37,7 +38,6 @@ const terserOptions = (ecma) => ({
3738
//unsafe: true,
3839
//unsafe_comps: true,
3940
},
40-
toplevel: true,
4141
});
4242

4343
/**

0 commit comments

Comments
 (0)