本文首發於個人博客,轉載請註明出處: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
屬性,沒法經過代碼獲取到子進程的任何標準輸出,因此我最終並無採用。