本文首发于个人博客,转载请注明出处:https://kohpoll.github.io/blo...javascript
最近在用子进程运行 gulpfile.js 的时候发现终端上的颜色所有没有了,非常奇怪。通过一些研究,最终解决了问题,同时也总结了一些相关知识。但愿对你有帮助。html
相信你们都知道,咱们日常使用的 terminal 是能够输出各类颜色的,并不只仅只是充满 geek 味道的黑绿。这些颜色实际上是经过一种叫作 ANSI/VT100
控制序列来标记的,这些字符自己不可见,会被 shell 解析后使用合适的颜色来渲染。java
好比,命令 echo -e "\033[31m red \033[39m"
就能够在终端打印红色的文字。node
其中的 \033[31m
、\033[39m
就是特殊的控制序列,\033[31m
表示红色的前景(文字)色,\033[39m
表示默认的前景(文字)色。而选项 -e
则是让 echo
启用反斜杠控制字符的转换(默认是不转换的)。git
实际上,咱们还可使用 echo -e "\x1b[31m red \x1b[39m"
来输出红色文字。\x1b
是十六进制表示,恰好等于八进制表示的 033
。github
除了前景色,还能够经过序列来表示背景色。更多的序列,能够参考:http://misc.flogisoft.com/bas...。shell
那在 Node 中咱们该如何打印带颜色的内容呢?其实很简单,咱们只要使用同样的控制序列就能够了。数据库
好比下面的代码,均可以打印红色文字。npm
console.log('\u001b[31m red \u001b[39m'); console.log('\033[31m red \033[39m'); // 这行代码在 strict 模式下将会报错 console.log('\x1b[31m red \x1b[39m');
可是这么多的控制序列很难记住,写起来很麻烦,给别人看更是如天书通常难懂。我推荐直接使用 chalk 这样的 npm 包,代码瞬间简洁、清晰了许多:gulp
const chalk = require('chalk'); console.log(chalk.red('red'));
chalk
实际上直接使用了 ansi-styles 这个包,从其源码中能够看到,原理就是对字符串增长特殊的控制序列:https://github.com/chalk/ansi...
我在以前的文章中介绍过 Node 子进程的用法,感兴趣的能够进行阅读。
我比较喜欢使用 spawn
的方式,由于它能够经过 stream 的方式操做子进程的输出,很是方便。好比下面的代码:
const log = []; const path = require('path'); const child = require('child_process').spawn( 'node', [ require.resolve('gulp/bin/gulp'), '--gulpfile', path.join(__dirname, 'gulpfile.js'), '--cwd', process.cwd() ], { 'stdio': 'pipe', 'cwd': process.cwd() } ); child.stdout.pipe(process.stdout); child.stdout.on('data', (data) => { // collect the data log.push(data.toString('utf8')) });
这里指定 stdio: 'pipe'
后,咱们能够操做子进程的 stdout
,好比将它的输出收集起来而后写入数据库作记录之类的。
若是尝试运行上面的脚本会发现,以前直接经过 gulp
执行 gulpfile.js
时漂亮的颜色消失了,所有变成默认颜色了。
实际上,在 Node 中执行的进程咱们能够经过 process.stdout.isTTY
这个属性来判断它是否在终端(terminal)终端环境中执行。
经过 spawn
并配置了 stdio: 'pipe'
开启的子进程,process.stdout.isTTY
属性会是 undefined
。
好比下面的代码:
// parent.js const child = require('child_process').spawn( 'node', ['./child.js'], {'stdio': 'pipe'} ); child.stdout.pipe(process.stdout); // child.js console.log('process.stdout.isTTY=', process.stdout.isTTY);
在终端执行 node parent.js
会输出 process.stdout.isTTY= undefined
。
单独执行 node child.js
会输出 process.stdout.isTTY= true
。
gulp
背后的颜色功能,实际上使用的是 chalk
。
chalk
会使用 supports-color 作是否支持终端颜色的判断。从其源码中咱们看到,它正是经过 process.stdout.isTTY
来进行判断的,若是该属性不为 true
,则认为不支持:https://github.com/chalk/supp...
所以,因为被认为是在不支持终端颜色的环境中执行,咱们以前代码中的全部输出就都再也不带有颜色了。
另外咱们还看到,supports-color
还会检查命令行参数中是否有 --color
选项,若是有的话就直接启用,再也不检查 proess.stdout.isTTY
。这正是我要的解决方案,因而,将代码修改为以下后,问题获得解决:
const log = []; const path = require('path'); const child = require('child_process').spawn( 'node', [ require.resolve('gulp/bin/gulp'), '--gulpfile', path.join(__dirname, 'gulpfile.js'), '--cwd', process.cwd(), '--color' // preserve the terminal color ], { 'stdio': 'pipe', 'cwd': process.cwd() } ); child.stdout.pipe(process.stdout); child.stdout.on('data', (data) => { // collect the data log.push(data.toString('utf8')) });
实际上,咱们也能够指定 stdio: 'inherit'
来让子进程直接使用父进程的 IO。这时子进程的 process.stdout.isTTY
会是 true
。这是由于直接使用了父进程的 IO,因此它实际上仍是在终端环境中执行的。
可是,这个方法会致使子进程再也不拥有 stdout
属性,没法经过代码获取到子进程的任何标准输出,因此我最终并无采用。