前端日常開發(fā)中抄谐,會遇見各種各樣的cli鸵隧,比如一行命令幫你打包的webpack碳抄,一行命令幫你生成vue項目模板的vue-cli箫爷,還有創(chuàng)建react項目的create-react-app等等等等犁苏。這些工具極大地方便了我們的日常工作硬萍,讓計算機自己去干繁瑣的工作,而我們围详,就可以節(jié)省出大量的時間用于學習朴乖、交流、開發(fā)助赞、逛steam买羞。
但是有時候一些十分特別的需求,我們是找不到適合的cli工具去做的雹食。比如說畜普,你的項目十分龐大,你給項目添加一個新的路由群叶,要經(jīng)過創(chuàng)建目錄 -> 創(chuàng)建.vue文件 -> 更新vue-router的路由列表
這一趟流程吃挑,就算快捷鍵創(chuàng)建目錄文件用得再熟悉钝荡,也比不過你一行命令來得快,特別是路由目錄嵌套深舶衬,.vue文件初始化模板復雜的時候埠通。
所以呢,何不為自己項目寫一個cli逛犹?就專門做這些繁瑣的活端辱?
0x1 hello world
nodejs的cli,本質就是跑node腳本嘛圾浅,基本上每位前端er都會:
// index.js
console.log('hello world')
然后命令行調用
> node index.js
## 輸出:
> hello world
可以做得更逼真一點掠手,我們在package.json里面的scripts字段上添加一下腳本名:
{
"scripts":{
"hello":"node index.js"
}
}
然后命令行調用:
> npm run hello
但是,看到這里你肯定會說狸捕,人家webpack還有vue-cli都是“有名字”的喷鸽!什么vue-cli init app
、webpack -p
的灸拍,多漂亮做祝,看看這個命令行,node index.js
鸡岗,還npm run hello
混槐,誰不會啊,丑不拉幾的轩性,怕又不是來水文章的哦声登?差評!揣苏!
別急啊各位大人悯嗓,接下來就說說,如何給這個node腳本起個名字卸察。
0x2 起名字
姑且脯厨,先把這個cli的名字命名為hello-cli
,就是我們能夠在命令行里面坑质,輸入hello-cli
合武,然后它就打印一句hello world
,沒有node
也沒有npm
涡扼,就是:
這里稼跳,我們需要做幾步操作:
- index.js文件頂部聲明執(zhí)行環(huán)境:
添加// index.js #!/usr/bin/env node console.log('hello world')
#!/usr/bin/env node
或者#!/usr/bin/node
,這是告訴系統(tǒng)壳澳,下面這個腳本岂贩,使用nodejs來執(zhí)行。當然,這個系統(tǒng)不包括windows萎津,因為windows下有個JScript的歷史遺留物在卸伞,會讓你的腳本跑不起來。
#!/usr/bin/env node
的意思是讓系統(tǒng)自己去找node的執(zhí)行程序锉屈。
#!/usr/bin/node
的意思是荤傲,明確告訴系統(tǒng),node的執(zhí)行程序在路徑為/usr/bin/node
颈渊。 - 添加package.json的bin字段遂黍。
可以在index.js當前的目錄下執(zhí)行npm init
創(chuàng)建一個package.json,然后在package.json里面俊嗽,添加一個bin字段:
bin字段里面寫上這個命令行的名字雾家,也就是{ "name": "hello-test", "version": "1.0.0", "bin":{ "hello-cli":"index.js" } }
hello-cli
,它告訴npm绍豁,里面的js腳本可以通過命令行的方式執(zhí)行芯咧,以hello-cli
的命令調用。當然命令行的名字你想寫什么都是你的自由竹揍,比如:
- 在當前package.json目錄下敬飒,打開命令行工具,執(zhí)行
npm link
芬位,將當前的代碼在npm全局目錄下留個快捷方式无拗。
npm檢測到package.json里面存在一個bin字段,它就同時在全局npm包目錄下生成了一個可執(zhí)行文件:
當我們在系統(tǒng)命令行直接執(zhí)行hello-cli
的時候昧碉,實際上就是執(zhí)行這里的腳本英染。
因為安裝node的時候,npm將這個目錄配置為系統(tǒng)變量環(huán)境了被饿,當你執(zhí)行命令的時候税迷,系統(tǒng)會先找系統(tǒng)命令和系統(tǒng)變量,然后到變量環(huán)境里面去查找這個命令名锹漱,然后找到這個目錄后,發(fā)現(xiàn)匹配上了該命令名的可執(zhí)行文件慕嚷,接著就直接執(zhí)行它哥牍。vue-cli也好,webpack-cli也好喝检,都是這樣執(zhí)行的嗅辣。
這樣,你的第一個cli腳本就成功安裝了挠说,可以在命令行里面澡谭,直接敲你的cli名字,看看結果輸出吧损俭。
另外蛙奖,如果你僅希望你的cli腳本僅在項目里執(zhí)行潘酗,則需要在你項目里面新建一個目錄,重復上述的操作雁仲,只是在第三步的時候仔夺,不要llink到全局里面去,而是使用npm i -D file:<你的腳本cli目錄路徑>
攒砖,把它當成項目的依賴安裝到node_modules里面去缸兔,如果安裝成功,那么在項目的package.json你會看到多了一條依賴吹艇,這條依賴的值不是版本號惰蜜,而是你腳本的路徑。然后在node_modules里面會有一個.bin目錄受神,里面就存放著你的可執(zhí)行文件抛猖。
局部安裝建議用
npm i -D file:xxx
,這樣它會在package.json留條記錄路克,方便其他小伙伴看到樟结。自然,你的腳本最好也是放進項目目錄里面精算。
當然瓢宦,這樣安裝的cli腳本,必須在項目的package.json的scripts字段上聲明腳本命令灰羽,然后通過npm run
的方式執(zhí)行驮履。
哦?這樣子使用的話不就回到最最最開始的時候那種原始的npm run hello
一樣么廉嚼。
是的玫镐,但是有質的區(qū)別。使用node index.js
這種方式調用的話固然簡單靈活怠噪,但是嚴重依賴腳本路徑恐似,一旦目錄結構發(fā)生變動,寫在scripts的命令就要更改一次傍念;但是使用npm安裝之后矫夷,本地的cli腳本就被拉到node_modules里面,目錄結構變動對其影響不大憋槐。其次是不利于分享與發(fā)布双藕,如果你想把你的cli腳本發(fā)布出去贴唇,那么有一個好聽響亮的名字纠屋,比起在說明文檔里面告訴使用者如何找到你的腳本路徑再用node執(zhí)行它,簡直好上那么一萬倍不是么浆洗?
這里也給我們提供了一個cli開發(fā)流程思路:
- 初期開發(fā)可以通過
node index.js
來看效果。 - 測試的時候可以通過
npm link
的方式進行安裝測試嘶摊。 - 發(fā)布
0x3 參數(shù)讀取:process.argv
名字有了延蟹,輸出也有了,看看我們跟那些大名鼎鼎的cli工具更卒,在形式上還差點啥等孵?對了,人家可以支持不同參數(shù)選項的蹂空,還可以根據(jù)輸入的不同俯萌,產(chǎn)生不同的結果。
這樣吧上枕,我們給這個cli加一個功能咐熙,既然叫hello-cli
,那不能只會hello world
吧辨萍,必須要見誰就說hello
才行:
> hello-cli older
## 輸出
> hello older
雖然這個功能很簡單棋恼,但是至少也是實現(xiàn)了“根據(jù)輸入的不同,產(chǎn)生不同結果”的效果锈玉。
命令行上的參數(shù)爪飘,可以通過process
這個變量獲取,process
是一個全局對象而不是一個包拉背,不需要通過require
引入师崎。通過process
這個對象我們可以拿到當前腳本執(zhí)行環(huán)境等一系列信息,其中就包括命令行的輸入情況椅棺,這個信息犁罩,保存在process.argv
這個屬性里。我們可以打印一下:
//index.js
console.log(process.argv);
打印結果:
可以看出两疚,argv是個數(shù)組床估,前兩位是固定的,分別是node程序的路徑和腳本存放的位置诱渤,從第三位開始才是額外輸入的內(nèi)容丐巫。那么實現(xiàn)上面的功能就很簡單了,只要讀取argv數(shù)組的第三位勺美,然后輸出出來就可以了鞋吉。
//index.js
console.log(`hello ${process.argv[2]||'world'}`)
npm社區(qū)中也有一些優(yōu)秀的命令行參數(shù)解析包,比如yargs,tj的commander.js等等
如果你想使用比較復雜的參數(shù)或者命令励烦,建議還是用第三方包比較好,手寫解析太耗精力了泼诱。
0x4 子進程
現(xiàn)在坛掠,你可以自由自在的寫你自己的cli腳本了。
如果你希望寫一個項目打完包自動推上git的cli,或者自動從git倉庫里面拉取項目啟動模板屉栓,那么舷蒲,你需要通過node的child_process
模塊開啟子進程,在子進程內(nèi)調用git命令:
//test.js
const child_process = require('child_process');
let subProcess=child_process.exec("git version",function(err,stdout){
if(err)console.log(err);
console.log(stdout);
subProcess.kill()
});
不僅是git命令友多,包括系統(tǒng)命令牲平、其他cli命令都可以在這里執(zhí)行。特別是系統(tǒng)命令域滥,使用系統(tǒng)命令對文件目錄進行操作纵柿,效率比fs高到不知道哪里去了。
社區(qū)上也有一些不錯的包启绰,比如阮一峰老師推薦的shelljs
0x5 美化輸出
如果你不那么希望你的cli用起來那么“硬核”昂儒,希望更人性化一點,比如提供一些友好的輸入委可、提示啊渊跋,給你的輸出加點顏色區(qū)分重點啊,寫個簡單的進度條啊等等着倾,那么你就需要美化一下你的輸出了拾酝。
除了顏色這部分,不使用第三方包實現(xiàn)起來非常繁瑣復雜卡者,其他的功能蒿囤,都可以試試自己寫。
顏色部分使用了第三方包colors虎眨,這里就不演示了蟋软。
其他都是由nodejs自帶的readline模塊實現(xiàn)的。
//index.js
const readline = require('readline');
const unloadChar='-';
const loadedChar='=';
const rl=readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('你想對誰說聲hello嗽桩? ',answer=>{
let i = 0;
let time = setInterval(()=>{
if(i>10){
clearInterval(time);
readline.cursorTo(process.stdout, 0, 0);
readline.clearScreenDown(process.stdout);
console.log(`hello ${answer}`);
process.exit(0)
return
}
readline.cursorTo(process.stdout,0,1);
readline.clearScreenDown(process.stdout);
renderProgress('saying hello',i);
i++
},200);
});
function renderProgress(text,step){
const PERCENT = Math.round(step*10);
const COUNT = 2;
const unloadStr = new Array(COUNT*(10-step)).fill(unloadChar).join('');
const loadedStr = new Array(COUNT*(step)).fill(loadedChar).join('');
process.stdout.write(`${text}:【${loadedStr}${unloadStr}|${PERCENT}%】`)
}
- 首先岳守,通過
readline.createInterface
方法創(chuàng)建一個interface
類,這個類下面有一個方法.question
碌冶,用這個方法在命令行上拋出一個問題湿痢,在第二個參數(shù)傳入一個函數(shù)進行監(jiān)聽。一旦用戶輸入完畢敲下回車扑庞,就會觸發(fā)回調函數(shù)譬重。 - 然后我們在回調函數(shù)里面寫了個計時器,假裝我們在處理某些事務罐氨。
- 使用
readline.cursorTo
這個方法臀规,可以改變命令行上的光標的位置。
readline.cursorTo(process.stdout, 0, 0);
是移動到第1列第1行上栅隐,
readline.cursorTo(process.stdout, 0, 1);
是移動到第1列第2行上塔嬉。 - 使用
readline.clearScreenDown
這個方法玩徊,是讓命令行從當前行開始,到最后一行結束谨究,將這兩行之間所有內(nèi)容清除恩袱。 -
renderProgress
是自己封裝的一個方法,通過process.stdout.write
方法輸出一行看起來像是進度條的字符串到命令行上胶哲。 - 所以在計時器里面畔塔,當計數(shù)小于10的時候,我們讓光標移到第一行上鸯屿,然后清除所有輸出澈吨,輸出進度條字符串;當計數(shù)大于10的時候碾盟,我們關掉計時器棚辽,清除輸出,打印結果冰肴。
- 最后不要忘記關掉進程屈藐,可以使用
interface
這個類的.close
方法關掉readline進程,也可以直接調用process.exit
退出熙尉。
繪制的思路跟canvas繪制動畫一樣联逻,只不過canvas是清除畫布,而命令行這里是通過readline.clearScreenDown
清除輸出检痰。
這樣包归,一個簡易的,人性化的铅歼,帶點點進度條動畫的命令行cli工具就寫好了公壤,你也可以發(fā)揮你的想象力,去寫一些更有趣的效果出來椎椰。
畢竟我們前端厦幅,有瀏覽器我們可以寫動畫,沒了瀏覽器我們一樣可以寫動畫慨飘。