diff --git a/.github/workflows/benchmarks.yaml b/.github/workflows/benchmarks.yaml index 6ddbb36..11a812e 100644 --- a/.github/workflows/benchmarks.yaml +++ b/.github/workflows/benchmarks.yaml @@ -17,15 +17,17 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: 18 + node-version: 22 - run: npm install - name: Ensure color support detection run: node tests/environments.js + - name: Install esbuild + run: npm install esbuild - name: Simple API calls - run: node benchmarks/simple.js + run: node benchmarks/simple.mjs - name: Complex formatting expression - run: node benchmarks/complex.js + run: node benchmarks/complex.mjs - name: Library module's init time - run: node benchmarks/loading.js - - name: NPM package size - run: node benchmarks/size.js + run: node benchmarks/loading.mjs + - name: Total loaded code size + run: node benchmarks/size.mjs diff --git a/benchmarks/complex.js b/benchmarks/complex.mjs old mode 100755 new mode 100644 similarity index 57% rename from benchmarks/complex.js rename to benchmarks/complex.mjs index 9821cc5..7d23c69 --- a/benchmarks/complex.js +++ b/benchmarks/complex.mjs @@ -1,34 +1,20 @@ -#!/usr/bin/env node +import { run, bench, summary } from "mitata" -// Benchmark results are unstable. To have more stable results: -// 1. Restart OS. Do not run any applications. Put power cable to laptop. -// 2. Run tests 5 times. -// 3. Took the best result for each candidate. +import * as colorette from "colorette" +import kleur from "kleur" +import * as kleurColors from "kleur/colors" +import chalk from "chalk" +import ansi from "ansi-colors" +import cliColor from "cli-color" +import picocolors from "../picocolors.js" +import * as nanocolors from "nanocolors" -let benchmark = require("benchmark") -let colorette = require("colorette") -let kleur = require("kleur") -let kleurColors = require("kleur/colors") -let chalk = require("chalk") -let ansi = require("ansi-colors") -let cliColor = require("cli-color") -let picocolors = require("../picocolors.js") -let nanocolors = require("nanocolors") +summary(() => { + let index = 1e8 -function formatNumber(number) { - return String(number) - .replace(/\d{3}$/, ",$&") - .replace(/^(\d|\d\d)(\d{3},)/, "$1,$2") -} - -let suite = new benchmark.Suite() -let out - -let index = 1e8 - -suite - .add("chalk", () => { - out = + bench( + "chalk", + () => chalk.red(".") + chalk.yellow(".") + chalk.green(".") + @@ -36,9 +22,11 @@ suite chalk.red( " Add plugin " + chalk.yellow("name") + " to use time limit with " + chalk.yellow(++index) ) - }) - .add("cli-color", () => { - out = + ) + + bench( + "cli-color", + () => cliColor.red(".") + cliColor.yellow(".") + cliColor.green(".") + @@ -49,9 +37,11 @@ suite " to use time limit with " + cliColor.yellow(++index) ) - }) - .add("ansi-colors", () => { - out = + ) + + bench( + "ansi-colors", + () => ansi.red(".") + ansi.yellow(".") + ansi.green(".") + @@ -59,9 +49,11 @@ suite ansi.red( " Add plugin " + ansi.yellow("name") + " to use time limit with " + ansi.yellow(++index) ) - }) - .add("kleur", () => { - out = + ) + + bench( + "kleur", + () => kleur.red(".") + kleur.yellow(".") + kleur.green(".") + @@ -69,9 +61,11 @@ suite kleur.red( " Add plugin " + kleur.yellow("name") + " to use time limit with " + kleur.yellow(++index) ) - }) - .add("kleur/colors", () => { - out = + ) + + bench( + "kleur/colors", + () => kleurColors.red(".") + kleurColors.yellow(".") + kleurColors.green(".") + @@ -82,9 +76,11 @@ suite " to use time limit with " + kleurColors.yellow(++index) ) - }) - .add("colorette", () => { - out = + ) + + bench( + "colorette", + () => colorette.red(".") + colorette.yellow(".") + colorette.green(".") + @@ -95,9 +91,11 @@ suite " to use time limit with " + colorette.yellow(++index) ) - }) - .add("nanocolors", () => { - out = + ) + + bench( + "nanocolors", + () => nanocolors.red(".") + nanocolors.yellow(".") + nanocolors.green(".") + @@ -108,9 +106,11 @@ suite " to use time limit with " + nanocolors.yellow(++index) ) - }) - .add("picocolors", () => { - out = + ) + + bench( + "picocolors", + () => picocolors.red(".") + picocolors.yellow(".") + picocolors.green(".") + @@ -121,15 +121,7 @@ suite " to use time limit with " + picocolors.yellow(`${++index}`) ) - }) - .on("cycle", event => { - let prefix = event.target.name === "picocolors" ? "+ " : " " - let name = event.target.name.padEnd("kleur/colors ".length) - let hz = formatNumber(event.target.hz.toFixed(0)).padStart(10) - process.stdout.write(`${prefix}${name}${picocolors.bold(hz)} ops/sec\n`) - }) - .on("error", event => { - process.stderr.write(picocolors.red(event.target.error.toString()) + "\n") - process.exit(1) - }) - .run() + ) +}) + +await run() diff --git a/benchmarks/loading-runner.js b/benchmarks/loading-runner.js deleted file mode 100644 index 5779a09..0000000 --- a/benchmarks/loading-runner.js +++ /dev/null @@ -1,39 +0,0 @@ -const { performance } = require("perf_hooks") - -let before -function showTime(name) { - let after = performance.now() - process.stdout.write(name + " " + (after - before) + "\n") -} - -before = performance.now() -let chalk = require("chalk") -showTime("chalk") - -before = performance.now() -let cliColor = require("cli-color") -showTime("cli-color") - -before = performance.now() -let ansi = require("ansi-colors") -showTime("ansi-colors") - -before = performance.now() -let kleur = require("kleur") -showTime("kleur") - -before = performance.now() -let kleurColors = require("kleur/colors") -showTime("kleur/colors") - -before = performance.now() -let colorette = require("colorette") -showTime("colorette") - -before = performance.now() -let nanocolors = require("nanocolors") -showTime("nanocolors") - -before = performance.now() -let picocolors = require("../picocolors.js") -showTime("picocolors") diff --git a/benchmarks/loading.js b/benchmarks/loading.js deleted file mode 100755 index 53b99d8..0000000 --- a/benchmarks/loading.js +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env node - -let { execSync } = require("child_process") - -let RUNS = 50 - -let results = {} - -for (let i = 0; i < RUNS; i++) { - let output = execSync("node ./benchmarks/loading-runner.js").toString() - output - .trim() - .split("\n") - .forEach(line => { - let [name, result] = line.split(" ") - results[name] = (results[name] || 0) + parseFloat(result) - }) -} - -for (let name in results) { - let prefix = name === "picocolors" ? "+ " : " " - let title = name.padEnd("kleur/colors ".length) - let time = (Math.round((1000 * results[name]) / RUNS) / 1000) - .toString() - .replace(/\.\d$/, "$&00") - .replace(/\.\d\d$/, "$&0") - process.stdout.write(prefix + title + "\x1B[1m" + time.padStart(6) + "\x1B[22m ms\n") -} diff --git a/benchmarks/loading.mjs b/benchmarks/loading.mjs new file mode 100755 index 0000000..dd8de23 --- /dev/null +++ b/benchmarks/loading.mjs @@ -0,0 +1,59 @@ +import { run, bench, group } from "mitata" +import { buildSync } from "esbuild" +import { createRequire } from "module" +import { dirname } from "path" +import { createContext, compileFunction } from "vm" + +let filename = new URL(import.meta.url).pathname + +function build(contents) { + let root = dirname(filename) + let result = buildSync({ + bundle: true, + write: false, + platform: "node", + stdin: { contents, loader: "js", resolveDir: root }, + }) + let code = result.outputFiles[0].text + return code +} + +function compile(code, context) { + return compileFunction(code, [], { parsingContext: context }) +} + +group(() => { + let codeP = build(`let picocolors = require("../picocolors.js")`) + let contextP = createContext({ require: createRequire(filename), process: { env: {} } }) + bench("picocolors", () => compile(codeP, contextP)) + + let codeAC = build(`let ansi = require("ansi-colors")`) + let contextAC = createContext({ require: createRequire(filename), process: { env: {} } }) + bench("ansi-colors", () => compile(codeAC, contextAC)) + + let codeK = build(`let kleur = require("kleur")`) + let contextK = createContext({ require: createRequire(filename), process: { env: {} } }) + bench("kleur", () => compile(codeK, contextK)) + + let codeKC = build(`let kleurColors = require("kleur/colors")`) + let contextKC = createContext({ require: createRequire(filename), process: { env: {} } }) + bench("kleur/colors", () => compile(codeKC, contextKC)) + + let codeC = build(`let colorette = require("colorette")`) + let contextC = createContext({ require: createRequire(filename), process: { env: {} } }) + bench("colorette", () => compile(codeC, contextC)) + + let codeN = build(`let nanocolors = require("nanocolors")`) + let contextN = createContext({ require: createRequire(filename), process: { env: {} } }) + bench("nanocolors", () => compile(codeN, contextN)) + + let codeCh = build(`let chalk = require("chalk")`) + let contextCh = createContext({ require: createRequire(filename), process: { env: {} } }) + bench("chalk", () => compile(codeCh, contextCh)) + + let codeCC = build(`let cliColor = require("cli-color")`) + let contextCC = createContext({ require: createRequire(filename), process: { env: {} } }) + bench("cli-color", () => compile(codeCC, contextCC)) +}) + +await run() diff --git a/benchmarks/recursion.js b/benchmarks/recursion.js deleted file mode 100755 index 4fa5bdc..0000000 --- a/benchmarks/recursion.js +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env node - -// Benchmark results are unstable. To have more stable results: -// 1. Restart OS. Do not run any applications. Put power cable to laptop. -// 2. Run tests 5 times. -// 3. Took the best result for each candidate. - -let benchmark = require("benchmark") -let colorette = require("colorette") -let kleur = require("kleur") -let kleurColors = require("kleur/colors") -let chalk = require("chalk") -let ansi = require("ansi-colors") -let cliColor = require("cli-color") -let picocolors = require("../picocolors.js") -let picocolorsw = require("../picocolors-w.js") -let nanocolors = require("nanocolors") - -function formatNumber(number) { - return String(number) - .replace(/\d{3}$/, ",$&") - .replace(/^(\d|\d\d)(\d{3},)/, "$1,$2") -} - -let suite = new benchmark.Suite() -let out -let count = 1000 -let input = "lorem ipsum dolor sit amet" - -suite - .add("chalk", () => { - out = chalk.blue(chalk.red(input).repeat(count)) - }) - .add("cli-color", () => { - out = cliColor.blue(cliColor.red(input).repeat(count)) - }) - .add("ansi-colors", () => { - out = ansi.blue(ansi.red(input).repeat(count)) - }) - .add("kleur", () => { - out = kleur.blue(kleur.red(input).repeat(count)) - }) - .add("kleur/colors", () => { - out = kleurColors.blue(kleurColors.red(input).repeat(count)) - }) - .add("colorette", () => { - out = colorette.blue(colorette.red(input).repeat(count)) - }) - .add("nanocolors", () => { - out = nanocolors.blue(nanocolors.red(input).repeat(count)) - }) - .add("picocolors", () => { - out = picocolors.blue(picocolors.red(input).repeat(count)) - }) - .add("updated", () => { - out = picocolorsw.blue(picocolorsw.red(input).repeat(count)) - }) - .on("cycle", event => { - let prefix = event.target.name === "picocolors" ? "+ " : " " - let name = event.target.name.padEnd("kleur/colors ".length) - let hz = formatNumber(event.target.hz.toFixed(0)).padStart(10) - process.stdout.write(`${prefix}${name}${picocolors.bold(hz)} ops/sec\n`) - }) - .on("error", event => { - process.stderr.write(picocolors.red(event.target.error.toString()) + "\n") - process.exit(1) - }) - .run() diff --git a/benchmarks/recursion.mjs b/benchmarks/recursion.mjs new file mode 100644 index 0000000..47c6ba3 --- /dev/null +++ b/benchmarks/recursion.mjs @@ -0,0 +1,49 @@ +import { run, bench, summary } from "mitata" + +import * as colorette from "colorette" +import kleur from "kleur" +import * as kleurColors from "kleur/colors" +import chalk from "chalk" +import ansi from "ansi-colors" +import cliColor from "cli-color" +import picocolors from "../picocolors.js" +import * as nanocolors from "nanocolors" + +let count = 1000 +let input = "lorem ipsum dolor sit amet" + +summary(() => { + bench("chalk", () => { + return chalk.blue(chalk.red(input).repeat(count)) + }) + + bench("cli-color", () => { + return cliColor.blue(cliColor.red(input).repeat(count)) + }) + + bench("ansi-colors", () => { + return ansi.blue(ansi.red(input).repeat(count)) + }) + + bench("kleur", () => { + return kleur.blue(kleur.red(input).repeat(count)) + }) + + bench("kleur/colors", () => { + return kleurColors.blue(kleurColors.red(input).repeat(count)) + }) + + bench("colorette", () => { + return colorette.blue(colorette.red(input).repeat(count)) + }) + + bench("nanocolors", () => { + return nanocolors.blue(nanocolors.red(input).repeat(count)) + }) + + bench("picocolors", () => { + return picocolors.blue(picocolors.red(input).repeat(count)) + }) +}) + +await run() diff --git a/benchmarks/simple.js b/benchmarks/simple.js deleted file mode 100755 index c9bf4df..0000000 --- a/benchmarks/simple.js +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env node - -// Benchmark results are unstable. To have more stable results: -// 1. Restart OS. Do not run any applications. Put power cable to laptop. -// 2. Run tests 5 times. -// 3. Took the best result for each candidate. - -let benchmark = require("benchmark") -let colorette = require("colorette") -let kleur = require("kleur") -let kleurColors = require("kleur/colors") -let chalk = require("chalk") -let ansi = require("ansi-colors") -let cliColor = require("cli-color") -let picocolors = require("../picocolors.js") -let nanocolors = require("nanocolors") - -function formatNumber(number) { - return String(number) - .replace(/\d{3}$/, ",$&") - .replace(/^(\d|\d\d)(\d{3},)/, "$1,$2") -} - -console.log(colorette.green("colorette")) -console.log(kleur.green("kleur")) -console.log(chalk.green("chalk")) -console.log(ansi.green("ansi")) -console.log(cliColor.green("cliColor")) -console.log(picocolors.green("picocolors")) -console.log(nanocolors.green("nanocolors")) - -let suite = new benchmark.Suite() -let out - -suite - .add("chalk", () => { - out = chalk.red("Add plugin to use time limit") - }) - .add("cli-color", () => { - out = cliColor.red("Add plugin to use time limit") - }) - .add("ansi-colors", () => { - out = ansi.red("Add plugin to use time limit") - }) - .add("kleur", () => { - out = kleur.red("Add plugin to use time limit") - }) - .add("kleur/colors", () => { - out = kleurColors.red("Add plugin to use time limit") - }) - .add("colorette", () => { - out = colorette.red("Add plugin to use time limit") - }) - .add("nanocolors", () => { - out = nanocolors.red("Add plugin to use time limit") - }) - .add("picocolors", () => { - out = picocolors.red("Add plugin to use time limit") - }) - .on("cycle", event => { - let prefix = event.target.name === "picocolors" ? "+ " : " " - let name = event.target.name.padEnd("kleur/colors ".length) - let hz = formatNumber(event.target.hz.toFixed(0)).padStart(10) - process.stdout.write(`${prefix}${name}${picocolors.bold(hz)} ops/sec\n`) - }) - .on("error", event => { - process.stderr.write(picocolors.red(event.target.error.toString()) + "\n") - process.exit(1) - }) - .run() diff --git a/benchmarks/simple.mjs b/benchmarks/simple.mjs new file mode 100644 index 0000000..9ee2dcd --- /dev/null +++ b/benchmarks/simple.mjs @@ -0,0 +1,47 @@ +import { run, bench, boxplot } from "mitata" + +import * as colorette from "colorette" +import kleur from "kleur" +import * as kleurColors from "kleur/colors" +import chalk from "chalk" +import ansi from "ansi-colors" +import cliColor from "cli-color" +import picocolors from "../picocolors.js" +import * as nanocolors from "nanocolors" + +console.log(colorette.green("colorette")) +console.log(kleur.green("kleur")) +console.log(chalk.green("chalk")) +console.log(ansi.green("ansi")) +console.log(cliColor.green("cliColor")) +console.log(picocolors.green("picocolors")) +console.log(nanocolors.green("nanocolors")) + +boxplot(() => { + bench("chalk", () => { + return chalk.red("Add plugin to use time limit") + }) + bench("cli-color", () => { + return cliColor.red("Add plugin to use time limit") + }) + bench("ansi-colors", () => { + return ansi.red("Add plugin to use time limit") + }) + bench("kleur", () => { + return kleur.red("Add plugin to use time limit") + }) + bench("kleur/colors", () => { + return kleurColors.red("Add plugin to use time limit") + }) + bench("colorette", () => { + return colorette.red("Add plugin to use time limit") + }) + bench("nanocolors", () => { + return nanocolors.red("Add plugin to use time limit") + }) + bench("picocolors", () => { + return picocolors.red("Add plugin to use time limit") + }) +}) + +await run() diff --git a/benchmarks/size.js b/benchmarks/size.js deleted file mode 100755 index 60c4f35..0000000 --- a/benchmarks/size.js +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env node - -let { get } = require("https") - -let { bold, gray } = require("../picocolors.js") - -async function getJSON(url) { - return new Promise(resolve => { - get(url, res => { - let text = "" - res.on("data", chunk => { - text += chunk - }) - res.on("end", () => { - resolve(JSON.parse(text)) - }) - }) - }) -} - -async function benchmark(lib) { - let data = await getJSON(`https://packagephobia.com/v2/api.json?p=${lib}`) - let size = data.install.bytes - process.stdout.write( - lib.padEnd("ansi-colors ".length) + - bold( - Math.round(size / 1024) - .toString() - .padStart(4) - ) + - " kB\n" - ) -} - -async function start() { - process.stdout.write(gray("Data from packagephobia.com\n")) - await benchmark(" chalk") - await benchmark(" cli-color") - await benchmark(" ansi-colors") - await benchmark(" kleur") - await benchmark(" colorette") - await benchmark(" nanocolors") - await benchmark("+ picocolors") -} - -start() diff --git a/benchmarks/size.mjs b/benchmarks/size.mjs new file mode 100644 index 0000000..b5b9ef8 --- /dev/null +++ b/benchmarks/size.mjs @@ -0,0 +1,30 @@ +import { buildSync } from "esbuild" +import { dirname } from "node:path" + +console.table({ + picocolors: build(`export { default as picocolors } from "../picocolors.js"`), + colorette: build(`export * as colorette from "colorette"`), + chalk: build(`export { default as chalk } from "chalk"`), + kleur: build(`export { default as kleur } from "kleur"`), + "kleur/colors": build(`export * as kleurColors from "kleur/colors"`), + "ansi-colors": build(`export { default as ansi } from "ansi-colors"`), + "cli-color": build(`export { default as cliColor } from "cli-color"`), + nanocolors: build(`export * as nanocolors from "nanocolors"`), +}) + +function build(contents) { + let root = dirname(new URL(import.meta.url).pathname) + let result = buildSync({ + bundle: true, + write: false, + minify: false, + platform: "node", + stdin: { contents, loader: "js", resolveDir: root }, + }) + let code = result.outputFiles[0].text + return { "size (KB)": toKB(code.length) } +} + +function toKB(value) { + return (((value / 1024) * 100) | 0) / 100 +} diff --git a/package.json b/package.json index afdd5ad..e0042c3 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,12 @@ "license": "ISC", "devDependencies": { "ansi-colors": "^4.1.1", - "benchmark": "^2.1.4", "chalk": "^4.1.2", "clean-publish": "^3.0.3", "cli-color": "^2.0.0", "colorette": "^2.0.12", "kleur": "^4.1.4", + "mitata": "^1.0.10", "nanocolors": "^0.2.12", "prettier": "^2.4.1" }, diff --git a/picocolors.js b/picocolors.js index f5ea2a1..d9ede1e 100644 --- a/picocolors.js +++ b/picocolors.js @@ -70,14 +70,14 @@ let createColors = (enabled = isColorSupported) => { cyanBright: init("\x1b[96m", "\x1b[39m"), whiteBright: init("\x1b[97m", "\x1b[39m"), - bgBlackBright: init("\x1b[100m","\x1b[49m"), - bgRedBright: init("\x1b[101m","\x1b[49m"), - bgGreenBright: init("\x1b[102m","\x1b[49m"), - bgYellowBright: init("\x1b[103m","\x1b[49m"), - bgBlueBright: init("\x1b[104m","\x1b[49m"), - bgMagentaBright: init("\x1b[105m","\x1b[49m"), - bgCyanBright: init("\x1b[106m","\x1b[49m"), - bgWhiteBright: init("\x1b[107m","\x1b[49m"), + bgBlackBright: init("\x1b[100m", "\x1b[49m"), + bgRedBright: init("\x1b[101m", "\x1b[49m"), + bgGreenBright: init("\x1b[102m", "\x1b[49m"), + bgYellowBright: init("\x1b[103m", "\x1b[49m"), + bgBlueBright: init("\x1b[104m", "\x1b[49m"), + bgMagentaBright: init("\x1b[105m", "\x1b[49m"), + bgCyanBright: init("\x1b[106m", "\x1b[49m"), + bgWhiteBright: init("\x1b[107m", "\x1b[49m"), } } diff --git a/types.d.ts b/types.d.ts index ecd6143..cd1aec4 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,52 +1,51 @@ export type Formatter = (input: string | number | null | undefined) => string export interface Colors { - isColorSupported: boolean + isColorSupported: boolean - reset: Formatter - bold: Formatter - dim: Formatter - italic: Formatter - underline: Formatter - inverse: Formatter - hidden: Formatter - strikethrough: Formatter + reset: Formatter + bold: Formatter + dim: Formatter + italic: Formatter + underline: Formatter + inverse: Formatter + hidden: Formatter + strikethrough: Formatter - black: Formatter - red: Formatter - green: Formatter - yellow: Formatter - blue: Formatter - magenta: Formatter - cyan: Formatter - white: Formatter - gray: Formatter + black: Formatter + red: Formatter + green: Formatter + yellow: Formatter + blue: Formatter + magenta: Formatter + cyan: Formatter + white: Formatter + gray: Formatter - bgBlack: Formatter - bgRed: Formatter - bgGreen: Formatter - bgYellow: Formatter - bgBlue: Formatter - bgMagenta: Formatter - bgCyan: Formatter - bgWhite: Formatter + bgBlack: Formatter + bgRed: Formatter + bgGreen: Formatter + bgYellow: Formatter + bgBlue: Formatter + bgMagenta: Formatter + bgCyan: Formatter + bgWhite: Formatter - blackBright: Formatter - redBright: Formatter - greenBright: Formatter - yellowBright: Formatter - blueBright: Formatter - magentaBright: Formatter - cyanBright: Formatter - whiteBright: Formatter - - bgBlackBright: Formatter - bgRedBright: Formatter - bgGreenBright: Formatter - bgYellowBright: Formatter - bgBlueBright: Formatter - bgMagentaBright: Formatter - bgCyanBright: Formatter - bgWhiteBright: Formatter + blackBright: Formatter + redBright: Formatter + greenBright: Formatter + yellowBright: Formatter + blueBright: Formatter + magentaBright: Formatter + cyanBright: Formatter + whiteBright: Formatter + bgBlackBright: Formatter + bgRedBright: Formatter + bgGreenBright: Formatter + bgYellowBright: Formatter + bgBlueBright: Formatter + bgMagentaBright: Formatter + bgCyanBright: Formatter + bgWhiteBright: Formatter }