淺析node命令行交互原理
什么是命令行交互
當(dāng)我們使用腳手架去創(chuàng)建一個項(xiàng)目的時(shí)候,通常會通過命令行交互來獲取一些信息:比如填項(xiàng)目名稱;選擇項(xiàng)目模板;選擇版本;需要安裝哪些額外的工具等等。這些我們雖然經(jīng)常用到,但是想必對于其中的原理還是不太了解。
常用的命令行交互庫
在開發(fā)腳手架項(xiàng)目時(shí),如果需要命令行交互去獲取一些信息,最常用的庫是inquirer,其使用也非常簡單:下面給出簡單的示例:
var inquirer = require('inquirer'); inquirer.prompt([ { type: 'list', name: 'projectType', message: '請選擇初始化類型', default: TYPE_PROJECT, choices: [ { name: '項(xiàng)目', value: TYPE_PROJECT, }, { name: '組件', value: TYPE_COMPONENT, }, ], }, ]); // 詢問項(xiàng)目的基本信息 inquirer.prompt([ { type: 'input', name: 'projectName', message: '請確認(rèn)項(xiàng)目名稱', default: this.projectName, validate: function(v) { const done = this.async(); setTimeout(function() { // 校驗(yàn)規(guī)則: // 1. 首字母必須為英文字符 // 2. 尾字母必須為英文或數(shù)字,不能為字符 // 3. 字符僅允許"-_" // 4. 長度必須大于等于1 if (!/^[a-zA-Z][\w-]{0,}[a-zA-Z0-9]$/.test(v)) { // Pass the return value in the done callback done('請輸入合法的項(xiàng)目名稱'); return; } // Pass the return value in the done callback done(null, true); }, 0); } }, { type: 'input', name: 'projectVersion', message: '請輸入項(xiàng)目版本號', default: '1.0.0', validate: function(v) { const done = this.async(); setTimeout(() => { if (!semver.valid(v)) { done('請輸入合法的版本號'); return; } done(null, true); }, 0) }, filter: function(v) { if (!!semver.valid(v)) { return semver.valid(v) } else { return v } } } ])
開發(fā)命令行工具常用庫或API
readline
readline是node提供的原生API,它可以幫助我們實(shí)現(xiàn)命令行交互,如下是一個簡單的案例:
const readline = require('node:readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question('What do you think of Node.js? ', (answer) => { // TODO: Log the answer in a database console.log(`Thank you for your valuable feedback: ${answer}`); rl.close(); });
運(yùn)行這個代碼就會出現(xiàn)命令行輸入的效果,輸入完成回車會在回調(diào)里拿到輸入的內(nèi)容,調(diào)用rl.close()會結(jié)束交互。
ansi-escapes
ANSI轉(zhuǎn)義序列是用于在視頻文本終端和終端模擬器上控制光標(biāo)位置、顏色、字體樣式和其他選項(xiàng)的帶內(nèi)信令的標(biāo)準(zhǔn)。某些字節(jié)序列(大多數(shù)以ASCII轉(zhuǎn)義字符和方括號字符開始)嵌入到文本中。終端將這些序列解釋為命令,而不是逐字顯示的文本。
當(dāng)我們想改變命令行的文字樣式、位置等就可以使用這個標(biāo)準(zhǔn),比如我想打印紅色的文字,那么我會這樣寫:
console.log('\x1B[31m%s\x1B[0m', 'I am red');
\x1B[是固定寫法,31表示紅色字體背景,m表示設(shè)置代碼后面字符的顏色和樣式,%s是log方法第二個參數(shù)的占位符,0m表示打印完后還原正常樣式。執(zhí)行的效果如下
除了顏色當(dāng)然還可以設(shè)置其他樣式,例如下劃線等,替換code即可,具體可以參考:ANSI escape code - HandWiki 里的code表。
// 打印紅色文字 console.log('\x1B[31m%s\x1B[0m', 'I am red'); // 打印文字添加下劃線 console.log('\x1B[4m%s\x1B[0m', 'underline'); // 紅色文字下劃線 console.log('\x1B[31m\x1B[4m%s\x1B[0m', 'I am red and underline');
除了樣式還可以處理光標(biāo)位置,比如想打印完一行后隔兩行再繼續(xù)打印,則可以這樣寫:
// 紅色文字下劃線 console.log('\x1B[31m\x1B[4m%s\x1B[0m', 'I am red and underline'); // cursor 移動 console.log('\x1B[2B%s\x1B[0m', 'I am red and underline');
將m換成B,B前面的數(shù)字表示要空幾行,效果如下:
還有更多的可能你想要的效果,可以查看 ANSI escape code - HandWiki
rxjs
RxJS
是一個用于處理異步編程的 JavaScript
庫,目標(biāo)是使編寫異步和基于回調(diào)的代碼更容易。
自己開發(fā)一個命令行列表選擇功能
流程圖
代碼實(shí)現(xiàn)
首先模仿inquirer的使用方式搭一個簡單的架子:
const option = { type: 'list', name: 'name', message: 'select a name', choices: [ { name: 'sam', value: 'sam' }, { name: 'tom', value: 'tom' }, { name: 'jerry', value: 'jerry' } ] } function prompt(option) { return new Promise((resolve, reject) => {}) } prompt(option).then((answer) => { console.log(answer) })
然后實(shí)現(xiàn)一個List類,由它來做具體的實(shí)現(xiàn):
const { EventEmitter } = require('events'); const rl = require('readline'); const MuteStream = require('mute-stream'); const { fromEvent } = require('rxjs'); const ansi = require('ansi-escapes'); class List extends EventEmitter { constructor(option) { super(); this.name = option.name; this.message = option.message; this.choices = option.choices; this.input = process.stdin; // 通過mute-stream來實(shí)現(xiàn)控制臺的輸入輸出 const ms = new MuteStream(); ms.pipe(process.stdout); this.output = ms; // 創(chuàng)建readline接口 this.rl = rl.createInterface({ input: this.input, output: this.output, }); // 默認(rèn)選中 this.selected = option.default || 0; this.height = this.choices.length + 1; // 監(jiān)聽keypress事件 this.keypress = fromEvent(this.rl.input, 'keypress').subscribe( this.onKeypress ); // 是否選擇完畢 this.done = false; } // 鍵盤事件 onKeypress = (keyMap) => { // 獲取key const key = keyMap[1]; if (key.name === 'up') { // 上鍵點(diǎn)擊 if (this.selected > 0) { this.selected--; } } else if (key.name === 'down') { // 下鍵點(diǎn)擊 if (this.selected < this.choices.length - 1) { this.selected++; } } else if (key.name === 'return') { // 回車點(diǎn)擊 this.done = true; } this.render(); // 完成選擇后退出 if (this.done) { this.close() this.emit('exit', this.choices[this.selected]) } } render() { // 解除mute狀態(tài) this.output.unmute(); // 清除控制臺 this.clean(); // 寫入list內(nèi)容 this.output.write(this.getContent()); // 開啟mute狀態(tài) 限制用戶不可輸入 this.output.mute(); } getContent() { if (this.done) { return `\x1B[1m${this.message} \x1B[22m\x1B[36m${this.choices[this.selected].name}\x1B[39m\n`; } else { const title = `\x1B[32m?\x1B[39m \x1B[1m${this.message}(use arrow keys)\x1B[22m\n`; const list = this.choices.map((item, index) => { // 選中的選項(xiàng)前面加上? if (index === this.selected) { return `\x1B[36m? ${item.name}\x1B[39m`; } return ` ${item.name}`; }); return title + list.join('\n'); } } // 清除控制臺 clean() { const emptyLines = ansi.eraseLines(this.height); this.output.write(emptyLines); } close() { this.output.unmute(); this.rl.output.end(); this.rl.pause(); this.rl.close(); this.keypress.unsubscribe(); } }
然后使用List
function prompt(option) { return new Promise((resolve, reject) => { try { const list = new List(option); // 渲染列表 list.render(); list.on('exit', (answer) => { resolve(answer); }); } catch (error) { reject(error); } }); } prompt(option).then((answer) => { console.log('answer', answer); });
運(yùn)行效果就是這樣:
到此這篇關(guān)于淺析node命令行交互原理的文章就介紹到這了,更多相關(guān)node 命令行交互內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Node.js、Socket.IO和GPT-4構(gòu)建AI聊天機(jī)器人的項(xiàng)目實(shí)踐
本文主要介紹了Node.js、Socket.IO和GPT-4構(gòu)建AI聊天機(jī)器人的項(xiàng)目實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05node.js中實(shí)現(xiàn)GET和POST請求的代碼示例
在很多場景中,我們的服務(wù)器都需要跟用戶的瀏覽器打交道,如發(fā)送驗(yàn)證碼、登錄表單提交,請求服務(wù)器數(shù)據(jù)一般都使用GET請求,表單提交到服務(wù)器一般都使用POST請求,本文詳細(xì)介紹了在Node.js中如何處理GET和POST請求,需要的朋友可以參考下2024-12-12webpack打包、編譯、熱更新Node內(nèi)存不足問題解決
Webpack是現(xiàn)在主流的功能強(qiáng)大的模塊化打包工具,在使用Webpack時(shí),如果不注意性能優(yōu)化,有非常大的可能會產(chǎn)生性能問題,下面這篇文章主要給大家介紹了關(guān)于webpack打包、編譯、熱更新Node內(nèi)存不足問題解決的相關(guān)資料,需要的朋友可以參考下2023-03-03node版本與node-sass版本不兼容時(shí)的問題及解決
這篇文章主要介紹了node版本與node-sass版本不兼容時(shí)的問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04前端常見面試題之a(chǎn)sync/await和promise的區(qū)別
async/await是異步代碼的新方式,以前的方法有回調(diào)函數(shù)和Promise,下面這篇文章主要給大家介紹了關(guān)于前端常見面試題之a(chǎn)sync/await和promise區(qū)別的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07使用express+multer實(shí)現(xiàn)node中的圖片上傳功能
這篇文章主要介紹了使用express+multer實(shí)現(xiàn)node中的圖片上傳功能,需要的朋友可以參考下2018-02-02零基礎(chǔ)搭建Node.js、Express、Ejs、Mongodb服務(wù)器及應(yīng)用開發(fā)入門
這篇文章主要介紹了零基礎(chǔ)搭建Node.js、Express、Ejs、Mongodb服務(wù)器及應(yīng)用開發(fā)入門,本文在windows8系統(tǒng)下完成本教程,其它系統(tǒng)也可參考,需要的朋友可以參考下2014-12-12