nodejs如何在终端输出带颜色的命令行
参考:
colors源码
ansi-styles源码
chalk源码
ANSI转义字符
通过控制台输出各种颜色的字符——ANSIConsole、JANSI
知乎 ~ Node.js Color 模块实现入门浅析
已有实现方案
colors
、chalk
、cli-color
等
ANSI控制码(ANSI escape code)
ANSI 具体介绍可查看维基百科)的解释。
如果要实现控制终端输出文字颜色或者背景色等,首先需要了解ANSI控制码。
ANSI控制码用于在字符显示系统中控制光标移动和字符色彩等,常用于BBS系统中。
ANSI ESCAPE SEQUENCES又称为VT100系列控制码,国内译为ANSI控制码。顾名思义,需要VT100系列终端的支持,当然现在已经不在局限于VT100了,包括xterm,linux都能很好完成。
ANSI控制码开始的标志都为ESC[,ESC对应ASCII码表的033(八进制),linux命令echo用-e启用转义,\033来输入ESC,\033[31m即为ESC[31m。
但是,通常在使用时通常用十六进制来表示ESC,即\u001b。
注意:不同的终端对ANSI控制码的支持度不一样,所以不能保证所有系统上都可以实现效果。
ASCII控制字符
二进制 | 十进制 | 十六进制 | 缩写 | 可以显示的表示法 | 名称/意义 |
---|---|---|---|---|---|
0000 0000 | 0 | 00 | NUL | ␀ | 空字符(Null) |
0000 0001 | 1 | 01 | SOH | ␁ | 标题开始 |
0000 0010 | 2 | 02 | STX | ␂ | 本文开始 |
0000 0011 | 3 | 03 | ETX | ␃ | 本文结束 |
0000 0100 | 4 | 04 | EOT | ␄ | 传输结束 |
0000 0101 | 5 | 05 | ENQ | ␅ | 请求 |
0000 0110 | 6 | 06 | ACK | ␆ | 确认回应 |
0000 0111 | 7 | 07 | BEL | ␇ | 响铃 |
0000 1000 | 8 | 08 | BS | ␈ | 退格 |
0000 1001 | 9 | 09 | HT | ␉ | 水平定位符号 |
0000 1010 | 10 | 0A | LF | ␊ | 换行键 |
0000 1011 | 11 | 0B | VT | ␋ | 垂直定位符号 |
0000 1100 | 12 | 0C | FF | ␌ | 换页键 |
0000 1101 | 13 | 0D | CR | ␍ | 归位键 |
0000 1110 | 14 | 0E | SO | ␎ | 取消变换(Shift out) |
0000 1111 | 15 | 0F | SI | ␏ | 启用变换(Shift in) |
0001 0000 | 16 | 10 | DLE | ␐ | 跳出数据通讯 |
0001 0001 | 17 | 11 | DC1 | ␑ | 设备控制一(XON 启用软件速度控制) |
0001 0010 | 18 | 12 | DC2 | ␒ | 设备控制二 |
0001 0011 | 19 | 13 | DC3 | ␓ | 设备控制三(XOFF 停用软件速度控制) |
0001 0100 | 20 | 14 | DC4 | ␔ | 设备控制四 |
0001 0101 | 21 | 15 | NAK | ␕ | 确认失败回应 |
0001 0110 | 22 | 16 | SYN | ␖ | 同步用暂停 |
0001 0111 | 23 | 17 | ETB | ␗ | 区块传输结束 |
0001 1000 | 24 | 18 | CAN | ␘ | 取消 |
0001 1001 | 25 | 19 | EM | ␙ | 连接介质中断 |
0001 1010 | 26 | 1A | SUB | ␚ | 替换 |
0001 1011 | 27 | 1B | ESC | ␛ | 跳出 |
0001 1100 | 28 | 1C | FS | ␜ | 文件分割符 |
0001 1101 | 29 | 1D | GS | ␝ | 组群分隔符 |
0001 1110 | 30 | 1E | RS | ␞ | 记录分隔符 |
0001 1111 | 31 | 1F | US | ␟ | 单元分隔符 |
0111 1111 | 127 | 7F | DEL | ␡ | 删除 |
通过阅读下面的文章可以知道,其实就是通过控制码进行样式渲染。
可参阅: 通过Ansi Escape Codes酷炫玩转命令行!
原文地址:http://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html
编码延伸阅读
- 阮一峰 ~ 字符编码笔记:ASCII,Unicode 和 UTF-8
- 各种编码UNICODE、UTF-8、ANSI、ASCII、GB2312、GBK详解
- 字符编码的那些事
- 知乎 ~ Unicode 和 UTF-8 有何区别?
chalk源码分析
版本:2.4.1
为什么要解读chalk,通过查看上面介绍的几个实现方案,chalk是有对ansi颜色输出支持的判断,所以兼容性处理更好。
chalk包含了几个重要依赖:
- escape-string-regexp — 字符串转义功能
对特殊字符进行转义,e.g. ‘$’ => ‘\$’ - supports-color — 判断当前系统终端支持的颜色(8色、16色、256色)
- ansi-styles — ANSI控制码对应的颜色值
先对chalk的整体架构进行了整理,源码很少,但是涉及到原型的东西蛮多的,容易弄混,所以整理成图:
现在分析下源码:
1. module.exports
1 | // 实际输出的是chalk.template,看第2点 |
2. Chalk定义
1 | // 定义Chalk, 生成chalk与chalk.template |
3. styles
styles
可以说是所有使用方法的集合,因为所有的使用方法都是通过Object.defineProperties(xxx, styles)
生成的。
styles到底长什么样呢?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// styles结构示例
{
red: {
get() {
return build.call(...);
}
},
black: {
get() {
return build.call(...);
}
},
// 颜色转换方法与直接取颜色值使用方法不同,结构也有所差异
ansi: {
get() {
return function() {
return build.call(...);
}
}
}
...
}
下图是在vsCode中断点截取的styles的构造
我们在使用chalk时的一个例子就是console.log(clalk.red('Hello'))
,通过上面的原型结构我们知道chalk.red
其实就是调用了Chalk.prototype.red
,那具体做了什么呢,接着看源码第224行:1
Object.defineProperties(Chalk.prototype, styles);
其实为了将styles的所有方法实现在原型上,所以作者才用get(){}属性的,如果不懂的话去查defineProperties方法。
4.styles由来
从源码55 ~ 115行,可以看出,ansi-styles
模块就是其由来(也就是源码开头定义的ansiStyles)。
这个模块定义了每个颜色对应的ANSI控制码
,并从color-convert
模块生成了一系列颜色转换工具函数,也就是chalk中的hex()、rbg()等方法的由来;
1 | // ansiStyles结构 |
6. 链式操作的实现
console.log(chalk.red.underline('Hello'))
这种操作是如何实现的呢
可以看下最初的图,其实里面已经描述的很清楚了,下面从源码讲解下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// 1. styles每个方法的定义
styles[key] = {
get() {
const codes = ansiStyles[key];
// 每个方法其实都返回了build函数的执行结果,即builder
return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key);
}
};
// 2. 定义builder的原型
const proto = Object.defineProperties(() => {}, styles);
function build(_styles, _empty, key) {
const builder = function () {
return applyStyle.apply(builder, arguments);
};
...
// `__proto__` is used because we must return a function, but there is
// no way to create a function with a different prototype
// builder的原型指向为proto
builder.__proto__ = proto; // eslint-disable-line no-proto
return builder;
}
// 3. 生成Chalk原型
// 可以看出proto和Chalk其实一模一样
Object.defineProperties(Chalk.prototype, styles);
// 总结
// 因为每个builder方法原型都指向了proto,proto中的方法又都是builder函数,即原型又指向proto,构成无限循环,所以可以无限链式调用
chalk.red.underline -> Chalk.prototype.red -> builder方法(即red方法) -> builder原型指向proto -> builder.__proto__.underline
7. 返回
最终调用返回结果就是返回了一个包含ANSI控制码的字符串
e.g. "\u001b[31m\u001b[4m Hello \u001b[24m\u001b[39m"
(红色带下划线)
其实就是在每次调用后再this._styles结构中增加了每种修饰的open与close