在 spawn 的子進程中保持命令行顏色

本文首發於個人博客,轉載請註明出處: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 是十六進制表示,恰好等於八進制表示的 033github

除了前景色,還能夠經過序列來表示背景色。更多的序列,能夠參考: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...

spawn

我在以前的文章中介紹過 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 時漂亮的顏色消失了,所有變成默認顏色了。

process.stdout.isTTY

實際上,在 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

實際上,咱們也能夠指定 stdio: 'inherit' 來讓子進程直接使用父進程的 IO。這時子進程的 process.stdout.isTTY 會是 true。這是由於直接使用了父進程的 IO,因此它實際上仍是在終端環境中執行的。

可是,這個方法會致使子進程再也不擁有 stdout 屬性,沒法經過代碼獲取到子進程的任何標準輸出,因此我最終並無採用。

參考資料

相關文章
相關標籤/搜索