懶人成就世界
最近總是忙于幫別人解決問題,大家隔著電腦屏幕聊啊聊绘搞,總是不夠真實(shí),驢頭不對(duì)馬嘴乍恐,浪費(fèi)時(shí)間默蚌。得,還是請(qǐng)對(duì)方把代碼端上來漱办,跑一跑看看啥錯(cuò)誤这刷。服務(wù)到位的朋友會(huì)去掉node_modules
丟個(gè)壓縮包過來,不那么講究的童鞋就整個(gè)項(xiàng)目全都丟過來娩井,幾十上百m暇屋,啊,100k的小水管要下到地老天荒洞辣,費(fèi)事咐刨!么得辦法,還是得想個(gè)辦法屋彪,省點(diǎn)事所宰。說來也巧,同事在搞個(gè)基于lerna
的工具腳手架畜挥,為了方便仔粥,直接采用的vue-cli
的模式,這提醒了我蟹但,我也可以搞個(gè)腳手架躯泰,方便你我他呀,那就干脆手?jǐn)]一個(gè)腳手架
吧华糖。
說干咱就干了麦向。首先說下想法,項(xiàng)目很多客叉,那我肯定是希望一個(gè)命令就能幫我下好項(xiàng)目诵竭,但是這樣也比較low,git也能做到兼搏,那就做的比git多一點(diǎn)吧卵慰,很多項(xiàng)目master分支都是個(gè)擺設(shè),通常dev或develop才是項(xiàng)目的真實(shí)代碼佛呻,那我這個(gè)腳手架就要支持分支選擇裳朋,最后一想,干脆吓著,node_modules
我也給你下了吧鲤嫡。這樣送挑,這個(gè)腳手架的大致想法就齊活啦。
開干暖眼!
npm init
肯定是first step
惕耕,生成了package.json之后,就開始考慮需要啥依賴了罢荡,首先涉及到shell赡突,不管是git操作還是下載依賴都需要執(zhí)行sh
,自然就想到了shell.js
区赵, 不過查閱了一下資料惭缰,node
原生就提供sh執(zhí)行工具child_process
,允許我們創(chuàng)建子進(jìn)程去操作笼才,并且既有異步也有同步的漱受,可以返回promise
。說到腳手架骡送,其實(shí)一直很好奇昂羡,那些和使用者進(jìn)行的交互是如何做到的,以前C++
或java
可以等待用戶輸入然后執(zhí)行下一步摔踱,js這樣還真的比較少見虐先,查了資料,我發(fā)現(xiàn)派敷,很多是使用co
庫去做的蛹批,乍看co
,好像沒看見過篮愉,但是一看用法腐芍,哎,不對(duì)试躏,這哥們眼熟猪勇。
co(function* (){
let data1 = yield readFile('path1')
console.log(data1)//顯示path1的文件的內(nèi)容
let data2 = yield readFile('path2')
console.log(data2)//顯示path2的文件內(nèi)容
})
co
基于generator
函數(shù),相當(dāng)于generator
函數(shù)的一個(gè)自動(dòng)執(zhí)行器颠蕴,如上泣刹,yield
執(zhí)行完之后,co
自動(dòng)執(zhí)行了next()
指向下一個(gè)console
函數(shù)犀被,簡單理解就很像async/await
项玛,阮一峰有幾篇講異步同步的文章,從頭看到尾的話應(yīng)該會(huì)很有收獲弱判,附在文尾。
有了異步轉(zhuǎn)同步還不夠锥惋,我們要能獲取用戶輸入
呀昌腰!
var name = yield prompt('username: ');
var pass = yield password('password: ');
var desc = yield multiline('description: ');
var ok = yield confirm('are you sure? ');
看到上面這塊是不是就很眼熟啦开伏,讓你輸入用戶名,密碼遭商,多行描述固灵,是否確認(rèn),獲取用戶輸入的功能就是靠co-prompt
來給我們提供的劫流。co
搭配co-prompt
再加上child_process
巫玻,我們執(zhí)行構(gòu)建的需求就差不多完成了。
'use strict'
// const exec = require('child_process').exec
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const co = require('co')
const prompt = require('co-prompt')
const chalk = require('chalk')
async function nextSh(sh1, sh2) {
console.log(chalk.white('\n 開始拉取代碼...'))
const { error } = await exec(sh1);
if(error) {
console.log(error)
process.exit()
}else {
console.log(chalk.green('\n √ 拉取代碼成功!'))
console.log(chalk.green('\n 開始install...'))
}
const { error : error1 } = await exec(sh2);
if(error1) {
console.log(error1)
process.exit()
}else {
console.log(chalk.green('\n √ 構(gòu)建完成!'))
process.exit()
}
}
module.exports = () => {
// generator函數(shù)
co(function *(){
// 處理用戶輸入的交互
let name = yield prompt('項(xiàng)目名:')
let gitUrl = yield prompt('Git地址:')
let branch = yield prompt('分支是(默認(rèn)是master):')
let install = yield prompt('使用yarn還是npm或是其他進(jìn)行install(默認(rèn)是npm):')
branch = branch || 'master'
install = install || 'npm'
let sh1 = `git clone -b ${branch} ${gitUrl} ${name}`
let sh2 = `cd ${name} && ${install} install`
console.log(chalk.white('\n 開始拉取代碼...'))
exec(sh1, (error) => {
if(error){
console.log(error)
process.exit()
}
console.log(chalk.green('\n √ 拉取代碼成功!'))
console.log(chalk.white('\n 開始install...'))
exec(sh2, (error) => {
if(error){
console.log(error)
process.exit()
}
console.log(chalk.green('\n √ 構(gòu)建完成!'))
process.exit()
})
})
// nextSh(sh1, sh2)
})
}
chalk
是一個(gè)控制字體顯示顏色的庫祠汇,也可以使用另一個(gè)spin
庫仍秤,更美觀一點(diǎn),后續(xù)應(yīng)該會(huì)加上可很。不過這不是重點(diǎn)诗力,上面的代碼中,實(shí)現(xiàn)了兩種執(zhí)行exec
的方式我抠,嵌套和async/await
苇本,不過async/await
需要對(duì)child_process
進(jìn)行util.promisify
包裝,這樣它的返回才是一個(gè)promise
菜拓。理論上瓣窄,co
的這一套也是可以被async/await
去取代的,正所謂萬法皆通纳鼎,正是這個(gè)道理俺夕。
執(zhí)行文件我們寫好了,怎么把它掛載到node
命令上去呢喷橙?那來寫個(gè)命令文件吧啥么。目前Commander
是node.js
命令行界面的完整解決方案,具體它的用法可以去查閱官網(wǎng)贰逾。
#!/usr/bin/env node --harmony
'use strict'
process.env.NODE_PATH = __dirname + '/../node_modules/'
const program = require('commander')
// 獲取version
program.version(require('../package').version)
program.usage('<command>')
program
.command('init')
.description('構(gòu)建一個(gè)已有g(shù)it項(xiàng)目')
.alias('i')
.action(()=>{
// 執(zhí)行init
require('../command/init')()
})
// 必須加上這些悬荣,才可以執(zhí)行commands
program.parse(process.argv)
if (!program.args.length) {
program.help()
}
commander
一定要執(zhí)行parse
命令,process.argv
中包含program
中傳入的args
和options
疙剑,這個(gè)不被執(zhí)行氯迂,那commander
沒有意義。
npm
如何publish
我就不在這里贅述了言缤,網(wǎng)上很多嚼蚀。有一點(diǎn)要說下,我們開發(fā)的過程中npm指向的倉庫可能是淘寶或是其他的源管挟,發(fā)布時(shí)就會(huì)報(bào)錯(cuò)轿曙,執(zhí)行下npm config set registry [http://registry.npmjs.org/](http://registry.npmjs.org/)
就好了。
最后的最后守谓,我們想像執(zhí)行vue-cli init
一樣,直接initPack i
就執(zhí)行命令斋荞,還需要在package .json
中修改bin
。
{
"name": "huanchen-cli",
"version": "1.0.3",
"description": "自制clidemo",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"cli"
],
"author": "1540226204@qq.com",
"license": "ISC",
"repository": {
"type": "git",
"url": "https://github.com/fatehuanchen/huanchen-cli.git"
},
"bin": {
"initPack": "bin/initPack"
},
"dependencies": {
"chalk": "^2.4.2",
"co": "^4.6.0",
"co-prompt": "^1.0.0",
"commander": "^2.19.0"
}
}
可以看的平酿,bin
下的initPack
指向的是當(dāng)前項(xiàng)目下bin目錄下的文件,當(dāng)我們下載cli
時(shí)悦陋,就會(huì)自動(dòng)把initPack
掛載到全局路徑上,就可以直接指向initPack i
了叨恨。
一個(gè)完整的腳手架就擼好了,使用也非常簡單痒钝,懶也要有懶的收獲。
[ 代碼傳送門 ] (https://github.com/fatehuanchen/huanchen-cli.git)
相關(guān)文章: 阮一峰教學(xué):http://www.ruanyifeng.com/blog/2015/05/async.html