彩色命令行,Node21自带函数1行实现 ,Bun也兼容, 附Bun.color实现渐变色的代码

今天你会学到这些关键词

| 关键词 | 解释 | | :-- | :-- | | styleText | Node.js 21.7.0+ 内置的彩色文字输出函数,1行代码搞定 | | Bun.color | Bun 运行时提供的颜色格式转换工具,支持 hex/rgb/hsl | | ANSI | 终端的"遥控器指令",比如 \x1b[31m 就是让文字变红的控制码,所有彩色文字的底层都是它 | | chalk | 周下载3亿次的 Node.js 颜色库 |

1行代码 vs 几十行代码

只要你用 Node.js 开发,就一定安装过 chalk。不信你用 npm why chalk 看下你的项目。

这个看似不起眼的动作背后,是一个周下载量超过 3 亿次的 npm 包。仅仅为了让控制台输出几个带颜色的字,整个生态重复了几十亿次"装包-引入-使用"的流程。

直到 Node.js 21.7.0 内置了 util.styleText()

bash 复制代码
import { styleText } from 'node:util';
console.log(styleText('red', '我是红色,构建失败!'));

一行代码。 不需要装包。不需要 require。红色文字直接输出。

对于"错误标红、警告标黄"这类日常需求,它完全可以替代 chalk。简单、直接、零依赖。

styleText 有多简单?

上面的代码已经展示了最简用法。不需要思考 ANSI 转义序列,不需要处理 NO_COLOR 环境变量,不需要关心终端色域------Node.js 帮你把一切都封装好了。

它的能力边界也很清晰:

  • • ✅ 支持颜色名称:red, green, yellow, blue...

  • • ❌ 不支持 hex:[#ff8800](javascript:;)

  • • ❌ 不支持 rgb:rgb(255,0,0)

  • • ❌ 不支持渐变色

  • • ✅ 支持背景色、粗体、下划线等组合(通过数组格式)

对于"日志分级"这种刚需------红色错误、黄色警告、绿色成功------styleText 完全够用了,而且比 chalk 更短、更直接。

但问题在于:如果你需要 hex 色、渐变色、背景色组合呢?

Node.js 没有给你选择。要么回到 chalk,要么手动拼 ANSI 序列。

而 Bun,选择了另一条路。

Bun.color

Bun 是兼容这行代码的:

bash 复制代码
import { styleText } from 'node:util';
console.log(styleText('red', '我是红色,构建失败!'));

此外 ,Bun 还提供了 Bun.color(),一个颜色格式转换工具,能把 hex、rgb、hsl 转成 ANSI 转义序列,但输出得你自己写

想要和 styleText 同等的效果?你得先封装:

bash 复制代码
/**
 * 带颜色输出文字到终端
 * @param text    - 要输出的文字内容
 * @param color   - 文字颜色,支持 hex 字符串(如 "#ff0000")或原生 ANSI 转义序列(如 "\x1b[30m")
 * @param bgColor - 背景颜色,支持 hex 字符串
 * @param reset   - 是否在末尾追加重置样式(\x1b[0m),默认 true。设为 false 可用于串联多个不同颜色的输出
 */
function cPrint({ text, color, bgColor, reset = true }) {
  // 前景色:若已是 ANSI 转义序列则直接使用,否则通过 Bun.color 将 hex 转为 ANSI
  const ansiFg = color ? (color.startsWith("\x1b") ? color : Bun.color(color, "ansi")) : "";
  // 背景色:将 hex 转为 ANSI 前景码后,把 38(前景)替换为 48(背景)
  const ansiBg = bgColor ? Bun.color(bgColor, "ansi").replace("38", "48") : "";
  // 按序拼接:背景码 → 前景码 → 文字内容 →(可选)重置样式
  process.stdout.write(ansiBg + ansiFg + text + (reset ? "\x1b[0m" : ""));
}

// 示例:输出红色文字
cPrint({ text: "红色文字", color: "#f00" });

甚至可以封装一个渐变函数

bash 复制代码
/**
 * 渐变输出文字到终端
 * @param text       - 要输出的文字
 * @param startColor - 起始颜色 hex(如 "#ff0000")
 * @param endColor   - 结束颜色 hex(如 "#0000ff")
 */
function gradientPrint(text, startColor, endColor) {
  const chars = [...text];
  const len = chars.length;
  if (len === 0) return;

  const sr = parseInt(startColor.slice(1, 3), 16);
  const sg = parseInt(startColor.slice(3, 5), 16);
  const sb = parseInt(startColor.slice(5, 7), 16);
  const er = parseInt(endColor.slice(1, 3), 16);
  const eg = parseInt(endColor.slice(3, 5), 16);
  const eb = parseInt(endColor.slice(5, 7), 16);

  for (let i = 0; i < len; i++) {
    const t = len > 1 ? i / (len - 1) : 0;
    const r = Math.round(sr + (er - sr) * t);
    const g = Math.round(sg + (eg - sg) * t);
    const b = Math.round(sb + (eb - sb) * t);
    const interpHex = "#" + [r, g, b].map(c => c.toString(16).padStart(2, "0")).join("");
    process.stdout.write(Bun.color(interpHex, "ansi") + chars[i]);
  }
  process.stdout.write("\x1b[0m");
}

代码虽然长,但它实现了 styleText 做不到的事:hex 背景色、渐变色输出。这些都是 Bun 原生支持的,chalk都不支持渐变色。

两者使用场景

因为 styleText Bun 也支持,所以 Bun 中也是 styleText 配合 Bun.color 组合使用。

  • 简单场景 (日志分级、基础颜色):styleText 完胜。1 行代码搞定,chalk已经不需要。

  • 复杂场景(hex 品牌色、渐变色、背景组合):Bun 完胜。Node.js 要么回到 chalk,要么手动拼 ANSI。

完整代码

封面图中效果的代码:

bash 复制代码
/**
 * 带颜色输出文字到终端
 * @param text    - 要输出的文字内容
 * @param color   - 文字颜色,支持 hex 字符串(如 "#ff0000")或原生 ANSI 转义序列(如 "\x1b[30m")
 * @param bgColor - 背景颜色,支持 hex 字符串
 * @param reset   - 是否在末尾追加重置样式(\x1b[0m),默认 true。设为 false 可用于串联多个不同颜色的输出
 */
function cPrint({ text, color, bgColor, reset = true }) {
  // 前景色:若已是 ANSI 转义序列则直接使用,否则通过 Bun.color 将 hex 转为 ANSI
  const ansiFg = color ? (color.startsWith("\x1b") ? color : Bun.color(color, "ansi")) : "";
  // 背景色:将 hex 转为 ANSI 前景码后,把 38(前景)替换为 48(背景)
  const ansiBg = bgColor ? Bun.color(bgColor, "ansi").replace("38", "48") : "";
  // 按序拼接:背景码 → 前景码 → 文字内容 →(可选)重置样式
  process.stdout.write(ansiBg + ansiFg + text + (reset ? "\x1b[0m" : ""));
}

/**
 * 渐变输出文字到终端
 * @param text       - 要输出的文字
 * @param startColor - 起始颜色 hex(如 "#ff0000")
 * @param endColor   - 结束颜色 hex(如 "#0000ff")
 */
function gradientPrint(text, startColor, endColor) {
  const chars = [...text];
  const len = chars.length;
  if (len === 0) return;

  const sr = parseInt(startColor.slice(1, 3), 16);
  const sg = parseInt(startColor.slice(3, 5), 16);
  const sb = parseInt(startColor.slice(5, 7), 16);
  const er = parseInt(endColor.slice(1, 3), 16);
  const eg = parseInt(endColor.slice(3, 5), 16);
  const eb = parseInt(endColor.slice(5, 7), 16);

  for (let i = 0; i < len; i++) {
    const t = len > 1 ? i / (len - 1) : 0;
    const r = Math.round(sr + (er - sr) * t);
    const g = Math.round(sg + (eg - sg) * t);
    const b = Math.round(sb + (eb - sb) * t);
    const interpHex = "#" + [r, g, b].map(c => c.toString(16).padStart(2, "0")).join("");
    process.stdout.write(Bun.color(interpHex, "ansi") + chars[i]);
  }
  process.stdout.write("\x1b[0m");
}




const colors = [
  "#f44336",
  "#e91e63",
  "#9c27b0",
  "#673ab7",
  "#3f51b5",
  "#2196f3",
  "#00bcd4",
  "#009688",
  "#4caf50",
  "#cddc39",
  "#ffeb3b",
  "#ffc107",
  "#ff9800",
  "#ff5722",
  "#795548",
  "#9e9e9e",
  "#607d8b",
];
for (const hex of colors) {
  const r = parseInt(hex.slice(1, 3), 16);
  const g = parseInt(hex.slice(3, 5), 16);
  const b = parseInt(hex.slice(5, 7), 16);

  const rgba = `rgba(${r}, ${g}, ${b}, 1)`;

  const rr = r / 255;
  const gg = g / 255;
  const bb = b / 255;
  const max = Math.max(rr, gg, bb);
  const min = Math.min(rr, gg, bb);
  const l = (max + min) / 2;
  let h = 0, s = 0;
  if (max !== min) {
    const d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    if (max === rr) h = ((gg - bb) / d + (gg < bb ? 6 : 0)) * 60;
    else if (max === gg) h = ((bb - rr) / d + 2) * 60;
    else h = ((rr - gg) / d + 4) * 60;
  }
  const hsl = `hsl(${h}, ${s}, ${l})`;

  const contrast = l > 0.5 ? "\x1b[30m" : "\x1b[37m";

  cPrint({ text: hex.padEnd(9), color: contrast, bgColor: hex, reset: false });
  process.stdout.write("\x1b[0m");
  process.stdout.write(" ");
  gradientPrint(hex, hex, "#000000");
  cPrint({ text: "  → " + rgba.padEnd(30) + hsl + "\n", color: hex });
}
相关推荐
锋行天下2 小时前
关于websocket,真实场景踩坑经验
前端·后端
PinkSun2 小时前
我用Spring AI做了个简历优化工具(1):Structured Output实战,让AI返回Java对象
后端
Asize2 小时前
重生之我在 Vibe Coding 时代当程序员:第十二课,Prompt 不是咒语,是可以沉淀的业务接口
前端·人工智能·python
东风微鸣2 小时前
Argo CD 用户管理:本地用户配置与权限分离实践
git·后端
Yeats_Liao2 小时前
Java网络编程(五):Selector选择器与高并发实现
java·后端·架构
小小龙学IT2 小时前
Go语言后端开发入门指南
开发语言·后端·golang
布兰妮甜2 小时前
Vue 项目 `localhost:3000` 打不开?404 常见原因排查指南
前端·javascript·vue.js·vuecli·4040排查
森林的尽头是阳光2 小时前
前端使用postman快速造数据
前端·javascript·vue·postman·造数·本地测试
土星碎冰机2 小时前
实现飞书群推送报错接口,critical复现curl
后端·飞书