懒人成就世界
最近总是忙于帮别人解决问题,大家隔着电脑屏幕聊啊聊,总是不够真实,驴头不对马嘴,浪费时间。得,还是请对方把代码端上来,跑一跑看看啥错误。服务到位的朋友会去掉node_modules
丢个压缩包过来,不那么讲究的童鞋就整个项目全都丢过来,几十上百m,啊,100k的小水管要下到地老天荒,费事!么得办法,还是得想个办法,省点事。说来也巧,同事在搞个基于lerna
的工具脚手架,为了方便,直接采用的vue-cli
的模式,这提醒了我,我也可以搞个脚手架,方便你我他呀,那就干脆手撸一个脚手架
吧。
说干咱就干了。首先说下想法,项目很多,那我肯定是希望一个命令就能帮我下好项目,但是这样也比较low,git也能做到,那就做的比git多一点吧,很多项目master分支都是个摆设,通常dev或develop才是项目的真实代码,那我这个脚手架就要支持分支选择,最后一想,干脆,node_modules
我也给你下了吧。这样,这个脚手架的大致想法就齐活啦。
开干!
npm init
肯定是first step
,生成了package.json之后,就开始考虑需要啥依赖了,首先涉及到shell,不管是git操作还是下载依赖都需要执行sh
,自然就想到了shell.js
, 不过查阅了一下资料,node
原生就提供sh执行工具child_process
,允许我们创建子进程去操作,并且既有异步也有同步的,可以返回promise
。说到脚手架,其实一直很好奇,那些和使用者进行的交互是如何做到的,以前C++
或java
可以等待用户输入然后执行下一步,js这样还真的比较少见,查了资料,我发现,很多是使用co
库去做的,乍看co
,好像没看见过,但是一看用法,哎,不对,这哥们眼熟。
co(function* (){
let data1 = yield readFile('path1')
console.log(data1)//显示path1的文件的内容
let data2 = yield readFile('path2')
console.log(data2)//显示path2的文件内容
})
co
基于generator
函数,相当于generator
函数的一个自动执行器,如上,yield
执行完之后,co
自动执行了next()
指向下一个console
函数,简单理解就很像async/await
,阮一峰有几篇讲异步同步的文章,从头看到尾的话应该会很有收获,附在文尾。
有了异步转同步还不够,我们要能获取用户输入
呀!
var name = yield prompt('username: ');
var pass = yield password('password: ');
var desc = yield multiline('description: ');
var ok = yield confirm('are you sure? ');
看到上面这块是不是就很眼熟啦,让你输入用户名,密码,多行描述,是否确认,获取用户输入的功能就是靠co-prompt
来给我们提供的。co
搭配co-prompt
再加上child_process
,我们执行构建的需求就差不多完成了。
'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 √ 构建完成!'))
process.exit()
}
}
module.exports = () => {
// generator函数
co(function *(){
// 处理用户输入的交互
let name = yield prompt('项目名:')
let gitUrl = yield prompt('Git地址:')
let branch = yield prompt('分支是(默认是master):')
let install = yield prompt('使用yarn还是npm或是其他进行install(默认是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 √ 构建完成!'))
process.exit()
})
})
// nextSh(sh1, sh2)
})
}
chalk
是一个控制字体显示颜色的库,也可以使用另一个spin
库,更美观一点,后续应该会加上。不过这不是重点,上面的代码中,实现了两种执行exec
的方式,嵌套和async/await
,不过async/await
需要对child_process
进行util.promisify
包装,这样它的返回才是一个promise
。理论上,co
的这一套也是可以被async/await
去取代的,正所谓万法皆通,正是这个道理。
执行文件我们写好了,怎么把它挂载到node
命令上去呢?那来写个命令文件吧。目前Commander
是node.js
命令行界面的完整解决方案,具体它的用法可以去查阅官网。
#!/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('构建一个已有git项目')
.alias('i')
.action(()=>{
// 执行init
require('../command/init')()
})
// 必须加上这些,才可以执行commands
program.parse(process.argv)
if (!program.args.length) {
program.help()
}
commander
一定要执行parse
命令,process.argv
中包含program
中传入的args
和options
,这个不被执行,那commander
没有意义。
npm
如何publish
我就不在这里赘述了,网上很多。有一点要说下,我们开发的过程中npm指向的仓库可能是淘宝或是其他的源,发布时就会报错,执行下npm config set registry [http://registry.npmjs.org/](http://registry.npmjs.org/)
就好了。
最后的最后,我们想像执行vue-cli init
一样,直接initPack i
就执行命令,还需要在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
指向的是当前项目下bin目录下的文件,当我们下载cli
时,就会自动把initPack
挂载到全局路径上,就可以直接指向initPack i
了。
一个完整的脚手架就撸好了,使用也非常简单,懒也要有懒的收获。
[ 代码传送门 ] (https://github.com/fatehuanchen/huanchen-cli.git)
相关文章: 阮一峰教学:http://www.ruanyifeng.com/blog/2015/05/async.html