跳到主要内容
ansicode
Node.js (CLI tools)

在 Node.js 中使用 ANSI 转义码 —— CLI 工具、isTTY、chalk、picocolors

Node CLI 工具的实战面,`/use/javascript` 仅勾勒了大致:`process.stdout.isTTY` 用于能力检测(管道、重定向、CI 下均为 false)、`process.stdout.columns` 适配终端宽度、chalk 5+ 仅支持 ESM 导致 CommonJS 项目频繁踩坑,以及 `node:tty` + `readline` 用于解码 ANSI 按键序列。如果你在交付 CLI 二进制(npx、`bin/` 入口、oclif、commander),这是你的页面;如果只是为 Next.js / Vite 应用挑选颜色库,请看 JavaScript 页。

推荐库

  • chalk

    可链式调用的颜色 API:`chalk.bold.red('oops')`。自动检测 16 / 256 / 16M 色支持,开箱即用地遵守 `NO_COLOR` + `FORCE_COLOR`。**v5+ 仅支持 ESM** —— CommonJS 调用方必须用 `await import('chalk')` 或停留在 chalk 4。Node 颜色库中知名度最高。

  • picocolors

    约 100 行、零依赖、CJS 与 ESM 双入口。冷启动赢家 —— Vite、Vue、Tailwind、PostCSS 用它正是因为它不引入 chalk 那套 supports-color / ansi-styles 依赖图。

  • kleur

    形态与 chalk 一致、默认 CommonJS、微基准比 chalk 快约 5×。如果你想要链式调用的便利但又要保持 CJS 或在意启动开销,这是好选择。

  • ansi-escapes

    非 SGR 转义码的常量与辅助函数:光标移动、清行、备用屏幕、OSC 8 超链接、OSC 52 剪贴板、iTerm2 imgcat。与仅覆盖 SGR 的 chalk / picocolors 搭配使用。

常用写法

能力检测 —— isTTY + NO_COLOR + FORCE_COLOR + --no-color
// The canonical Node-CLI colour decision. Run this once at startup,
// cache the result, and gate every SGR emission through it. The four
// signals stack in this priority order (highest first):
//   1. --no-color CLI flag    (explicit user intent, beats everything)
//   2. FORCE_COLOR=0          (env-level kill switch)
//   3. NO_COLOR=anything      (the no-color.org standard — presence wins)
//   4. !process.stdout.isTTY  (piped / redirected / CI / daemon — assume false)
//   5. FORCE_COLOR=1|2|3      (force colour even when not a TTY)
function shouldUseColor() {
  if (process.argv.includes('--no-color')) return false;
  if (process.env.FORCE_COLOR === '0') return false;
  if (process.env.NO_COLOR !== undefined) return false;
  if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== '0') return true;
  return Boolean(process.stdout.isTTY);
}

const USE_COLOR = shouldUseColor();
const paint = (sgr, text) =>
  USE_COLOR ? `\x1b[${sgr}m${text}\x1b[0m` : text;

console.log(paint('1;31', 'error:'), 'permission denied');
在 CommonJS 项目里使用 chalk 5 —— 动态 import
// chalk 5+ is ESM-only. `require('chalk')` throws ERR_REQUIRE_ESM.
// In a CJS project (package.json without "type": "module") use dynamic
// import — Node has supported async `import()` in CJS since v12.

// CommonJS module
async function main() {
  const { default: chalk } = await import('chalk');
  console.log(chalk.bold.red('error:'), 'permission denied');
}

main();

// Alternatively, pin chalk 4 (`"chalk": "^4.1.2"`) which is the last
// CJS-compatible release. Same API surface for the chainable colours.
自适应宽度 —— process.stdout.columns + resize 事件
// Render a progress bar / table that re-flows when the terminal resizes.
// process.stdout.columns is undefined when stdout is not a TTY — fall back
// to 80 (the POSIX default) so piped output still produces something sane.

function getWidth() {
  return process.stdout.columns ?? 80;
}

function renderBar(pct) {
  const w = Math.max(10, getWidth() - 8);  // 8 cells for "100% [" + "]"
  const filled = Math.round((w * pct) / 100);
  return `${String(pct).padStart(3)}% [${'█'.repeat(filled)}${' '.repeat(w - filled)}]`;
}

process.stdout.on('resize', () => {
  process.stdout.write('\r\x1b[2K' + renderBar(42));
});

process.stdout.write(renderBar(42));
用 node:tty + readline 读取按键 —— 解码 ANSI 输入
// readline emits decoded keypress events — Node parses CSI / SS3 sequences
// for you and gives you a normalized {name, ctrl, meta, shift} object.

import readline from 'node:readline';

readline.emitKeypressEvents(process.stdin);
if (process.stdin.isTTY) process.stdin.setRawMode(true);

process.stdin.on('keypress', (str, key) => {
  if (key.ctrl && key.name === 'c') process.exit();
  console.log({ str, name: key.name, ctrl: key.ctrl, meta: key.meta, shift: key.shift });
  // Arrow keys arrive as { name: 'up' / 'down' / 'left' / 'right' }
  // Function keys as { name: 'f1' ... 'f12' }
  // CSI u (kitty keyboard protocol) is NOT parsed — raw bytes only.
});

相关序列

其他语言