chalk — 让 console.log 输出彩色字符串的 Node 库
待复核chalk 是一个 Node.js 终端字符串样式库——你写 chalk.red('error'),它返回一段终端能识别的”红色 error”字符串。日常类比:像给字符串套彩色塑料封皮——书本身(你的字)没变,封皮告诉读者(终端)“这里要红、要粗、要下划线”。
实际它返回的就是普通字符串:
import chalk from 'chalk';chalk.red('hello');// → '\x1b[31mhello\x1b[39m'中间那串 \x1b[31m 是 ANSI 转义码(1976 年的协议)。chalk 自己不画颜色,画颜色的是终端(iTerm2 / Terminal.app / Linux tty)。chalk 只负责把 JS 字符串包成”带颜色指令”的形式。
不理解 chalk,下面这些事都没法解释:
- 为什么所有 Node CLI 工具(npm / vite / prettier / next)的报错都能五颜六色——它们都用 chalk 或它的替代品
- 为什么同一个脚本在终端跑是彩色、在 GitHub Actions 日志里却变成乱码
^[[31m——supports-color 检测到非 TTY 自动降级 - 为什么 chalk v5 一发布、半个 npm 生态在 issue 里哀嚎——
require('chalk')突然报错 - 为什么 picocolors / kleur / colorette 一窝蜂出现——chalk 太慢、太大、太 ESM
chalk 干的事可以拆成 三步:
-
查 ANSI 码表:
red对应 SGR 码 31,bold对应 1。类比:拿一本”颜色 → 数字”的字典翻译。这部分数据放在独立小包ansi-styles里。 -
包字符串:把样式拼成
\x1b[Open 码m 你的字 \x1b[Close 码m。多个样式叠加时用单链表串起来,避免每次 spread 拷贝。 -
看终端脸色:调用前先问一下”终端能不能显示颜色”——CI 环境?老 Windows?TERM=dumb?检测逻辑在另一个独立小包
supports-color里,给出 0-3 级 level,chalk 按 level 选码或降级到无色。
三个包加起来才是完整 chalk:链式 API + 数据表 + 能力检测。这种拆分叫 Sindre Sorhus 的 micro-package 风格。
案例 1:最小例子 + 看清字符串真面目
Section titled “案例 1:最小例子 + 看清字符串真面目”import chalk from 'chalk';console.log(chalk.red('error'));console.log(chalk.green.bold('success'));console.log(chalk.bgYellow.black(' WARN '));
// 把样式后的字符串原样打印出来:console.log(JSON.stringify(chalk.red('error')));// → "[31merror[39m"(= \x1b)是 ESC 字符(ASCII 27);[31m 让终端”前景变红”;[39m 是”重置前景色”。终端读到这串字符自动绘制——chalk 没碰像素。
案例 2:嵌套样式为什么不能朴素拼接
Section titled “案例 2:嵌套样式为什么不能朴素拼接”chalk.red(`outer ${chalk.green('inner')} outer`);朴素实现:拼出 \x1b[31m outer \x1b[32m inner \x1b[39m outer \x1b[39m。问题在 inner 闭合(\x1b[39m)时整个前景色被重置,外层的红消失,第二个 outer 变成默认色。
chalk 内部用栈跟踪样式:闭合 inner 时重新打开 outer 的 \x1b[31m,所以最终输出是红 outer + 绿 inner + 红 outer,颜色不会丢。这是状元篇必须知道的细节。
案例 3:truecolor 自动降级
Section titled “案例 3:truecolor 自动降级”chalk.rgb(255, 136, 0)('orange');chalk.hex('#FF8800')('orange');chalk.ansi256(208)('orange');终端 level=3(iTerm2 / 现代 GNOME Terminal)→ 直接发 \x1b[38;2;255;136;0m,24-bit 真彩。
level=2(老 xterm-256color)→ 自动映射到最近的 256 色 208。
level=1(基础 16 色)→ 再降到 yellow(最近邻)。
level=0(CI / TERM=dumb)→ 完全不输出转义码,只剩 'orange' 纯文本。
降级查表逻辑都在 ansi-styles,chalk 只负责”按 level 选哪张码”。
-
v5 全 ESM:
const chalk = require('chalk')抛ERR_REQUIRE_ESM,所有 CJS 老 webpack / Jest config 全瞬间卡住。Sindre 拒绝出双包,issue #1097 成为 chalk 史上最高 reactions issue(2k+)。修法:留 v4 / 切 picocolors / 项目改 ESM。 -
嵌套样式必须经 chalk 拼:你自己
'\x1b[31m' + 'a' + chalk.green('b') + 'c' + '\x1b[39m'和上面案例 2 一样会丢色。要么全交给 chalk,要么自己手动管栈。 -
CI 输出乱码:在 GitHub Actions / Jenkins 里日志里看到
^[[31merror^[[39m这种鬼东西——supports-color 没识别这个 CI 环境,FORCE_COLOR=1 强开 / FORCE_COLOR=0 强关都行。 -
启动时间敏感场景慢:每个 builder getter 都触发
Object.setPrototypeOf+ 创建链表节点。百万次调用累积明显,prettier 测出 chalk 占冷启动 30ms+,切到 picocolors 后降到 < 5ms。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 应用层 CLI / 报错日志 / 进度提示——启动时间不敏感,链式 API 可读性赢
- 需要 truecolor / 256 色的精细输出(chalk 是少数完整支持自动降级的)
- 想配合 ora 做 spinner 上色,commander / yargs 帮 help 文本上色
不适用:
- 工具链 starter dependency(webpack / vite / prettier 这种)——启动时间敏感,应选 picocolors
- 浏览器环境——ANSI 转义码不工作(浏览器 console 用 CSS
%c协议,不是 ANSI) - 需要保留双 CJS+ESM 兼容的库——v5+ 不行,留 v4 或换 kleur / colorette
- 极简脚本 / 一次性 bash 替代——直接
printf '\033[31m%s\033[0m\n' "x"更短
历史小故事(可跳过)
Section titled “历史小故事(可跳过)”- 1976 年:ECMA-48 标准发布(DEC VT100 终端的协议正规化),定义 SGR 参数
\x1b[31m、[1m、[4m,奠定 chalk 50 年后的数据基础。 - 2013-09:Sindre Sorhus 发布 chalk v0.1,灵感来自 colors.js(彼时被 Marak Squires 把玩到不稳定)。
- 2017:chalk v2 引入 tagged template literal,
chalk.red`hi ${name}`风格短暂流行后又在 v5 被移除。 - 2018-11:event-stream 供应链事件爆雷,间接动摇 micro-package 信任,Sindre 启用 2FA + 推动 npm provenance。
- 2019:v3 重构内部 builder,把每属性 defineProperty 改成 setPrototypeOf 共享原型,启动性能提升 3-5x。
- 2022-04:chalk v5 全 ESM 发布,issue #1097 爆 1500+ 评论;同期 picocolors 1.0 抢占 prettier / vite / next 等头部用户。
- 2024:picocolors 在主流工具默认中超越 chalk,chalk 仍占应用层 CLI 大头。
- chalk 没有魔法,只有协议——魔法是 1976 年 ECMA-48 定的,chalk 只是 wrapper。理解协议比记 API 重要。
- micro-package 拆分两面:chalk + ansi-styles + supports-color 让单一职责清晰、复用方便;但信任面变大,event-stream 事件证明了风险。
- 链式 API 不免费:
chalk.red.bold比bold(red('x'))慢 ~10x,背后是 setPrototypeOf + 链表构造。便利性几乎从来不免费。 - ESM 迁移没有温柔答案:维护者推进生态 vs 用户当下要用,张力是 Node 生态结构性问题,chalk v5 是这场拉锯的标志事件。
- 官方 README:chalk/chalk + ansi-styles + supports-color
- 协议教科书:ECMA-48 PDF / Wikipedia ANSI escape code
- 关键讨论:chalk#1097 — Please support CommonJS
- 性能对照:picocolors README benchmark
- Sindre 立场:Pure ESM package gist