随着NodeJs的不断发展,对于前端来说要做的东西也就更多,Vue脚手架React脚手架等等等一系列的东西都脱颖而出,进入到人们的视野当中,对于这些脚手架工具来讲也只是停留在应用阶段,从来没有想过脚手架是如何实现的?vue init webpack 项目名称是如何通过这样的命令创建了一个项目,其最重要的模块就是今天要说的Commander。
Commander模块又国外TJ大神所编写
项目地址:Commander
Commander基本用法
Commander文档写的很详细,跟着文章详细的学习一下,Commander是一个Nodejs模块,需要在Node环境中运行,在使用前确认一下Node环境是否已安装。
npm?install?commander?--save
在Commander模块下存在option方法用来定义commander的选项options,用来作为选项的文档。
var?program?=?require(‘commander‘);
program
??.option(‘-g,?--git?[type]‘,?‘Add?[marble]‘,?‘Angie‘)
??.parse(process.argv);console.log("process.argv",process.argv)console.log("program.args",program.args)console.log(‘you?ordered?a?pizza?with:‘);if?(program.git)?console.log(‘??-?git‘);console.log(‘??-?%s?git‘,?program.git);上面的示例将解析来自process.argv的args和options,然后将剩下的参数(未定义的参数)赋值给commander对象的args属性(program.args),program.args是一个数组。
打印输出一下process.argv和program.args并查看了一下输出结果如下,使用如下命令运行一下文件:
node?index?-g?type?Aaron
process.argv?[‘F:\\node\\installation\\node.exe‘, ??????????????‘C:\\Users\\wo_99\\Desktop\\cli-dome\\index‘,??? ??????????????‘-g‘, ??????????????‘type‘, ??????????????‘Aaron‘?] program.args?[?‘Aaron‘?]
option方法可以接收三个参数:
必须:分为长短标识,中间用逗号、竖线或者空格分割;标志后面可跟必须参数或可选参数,前者用<>包含,后者用[]包含。省略不报错:在使用 --help 命令时显示标志描述可省略:当没有传入参数时则会使用默认值若我们执行node index -g得到的结果则是Angie git其第三个参数则作为了默认值填写在了对应的位置上。除了上面所说还可以使用如下命令:
//??执行?-g?参数?a //??执行?-b?参数?s node?index?-g?a?-b?s //??执行?-g和-b?传入a参数给-g //??-b?参数暂时不知道怎么传入 node?index?-gb?a
调用版本会默认将-V和--version选项添加到命令中。当存在这些选项中的任何一个时,该命令将打印版本号并退出。
var?program?=?require(‘commander‘); ? program ????.version(‘0.0.1‘) ????.parse(process.argv);//??执行命令//??node?index?-V//??输出结果//??0.0.1
如果希望程序响应-v选项而不是-V选项,只需使用与option方法相同的语法将自定义标志传递给version方法,版本标志可以被命名为任何值,但是长选项是必需的。
var?program?=?require(‘commander‘); program ??.version(‘0.0.1‘,?‘-e,?--version‘);
该方法允许使用命令行去执行一段命令,也就是一段:
var?program?=?require(‘commander‘);
program
??.version(‘0.0.1‘,?‘-V,?--version‘)
??.command(‘rm?<dir>‘)
??.action(function?(dir,?cmd)?{????console.log(‘remove?‘?+?dir?+?(cmd.recursive???‘?recursively‘?:?‘‘))
??});
program.parse(process.argv);//??执行命令//??node?index?rm?/aaa?-r//??输出结果//??remove?/aaa?recursively?????即:代码中console内容command函数接收三个参数:
必须:命令后面可跟用<>或[]包含的参数;命令的最后一个参数可以是可变的,像实例中那样在数组后面加入...标志;在命令后面传入的参数会被传入到action的回调函数以及program.args数组中。可省略:如果存在,且没有显示调用action(fn),就会启动子命令程序,否则会报错可省略:可配置noHelp、isDefault等使执行命令时,将验证该命令的options,任何未知的option都将报错。但是,如果基于action的命令如果没有定义action,则不验证options。
var?program?=?require(‘commander‘);
program
??.version(‘0.0.1‘,?‘-V,?--version‘)
??.command(‘rm?<dir>‘)
??.option(‘-r,?--recursive‘,?‘Remove?recursively‘)
??.action(function?(dir,?cmd)?{????console.log(‘remove?‘?+?dir?+?(cmd.recursive???‘?recursively‘?:?‘‘))
??});
program.parse(process.argv);console.log(program.args)//??执行命令//??node?index?rm?/aaa?-r?/ddd//??输出结果//??remove?/ddd?recursively//??[?‘/aaa‘?]提供帮助信息
var?program?=?require(‘commander‘); program ??.version(‘0.1.0‘) ??.helpOption(‘-h,--HELP‘) ??.option(‘-f,?--foo‘,?‘enable?some?foo‘) ??.option(‘-b,?--bar‘,?‘enable?some?bar‘) ??.option(‘-B,?--baz‘,?‘enable?some?baz‘); program.parse(process.argv);//??执行命令//??node?index?-h?或?node?index?--HELP/*??输出结果 ?*??Options: ?*????-V,?--version??output?the?version?number ?*????-f,?--foo??????enable?some?foo ?*????-b,?--bar??????enable?some?bar ?*????-B,?--baz??????enable?some?baz ?*????-h,--HELP??????output?usage?information? ?*/
输出帮助信息并立即退出。可选的回调cb允许在显示帮助文本之前对其进行后处理。helpOption也提供长名,-h,--HELP前面为短名后面为长名调用时使用两这都是可以的。与version的使用是类似的。
用来描述命令,也就是命令的说明,上面说过command第二个参数也同样是命令的描述,当与description同时存在的话,则会优先于第二个参数的描述,description则会作为全局描述在最顶部显示。该描述只用在使用-HELP的时候才能看见。
var?program?=?require(‘commander‘);
program
??.version(‘0.0.1‘,?‘-V,?--version‘)
??.command(‘rm?<dir>‘,"arg?is?description")
??.description("this?is?description")
??.option(‘-r,?--recursive‘,?‘Remove?recursively‘)
??.action(function?(dir,?cmd)?{????console.log(‘remove?‘?+?dir?+?(cmd.recursive???‘?recursively‘?:?‘‘))
??});
program.parse(process.argv);//??执行命令//??node?index?-h//??输出结果/*
this?is?description
Options:
??-V,?--version????output?the?version?number
??-r,?--recursive??Remove?recursively???????
??-h,?--help???????output?usage?information?
Commands:
??rm?<dir>?????????arg?is?description???????
??help?[cmd]???????display?help?for?[cmd]?
*/通过上面的输出结果可以看的出,rm命令最后的描述是arg is description,若删除第二个参数则会输出this is description。
用于捕获option与command,当其被使用贼会被触发函数。
var?program?=?require(‘commander‘);
program
??.version(‘0.0.1‘,?‘-V,?--version‘)
??.command(‘rm?<dir>‘,"arg?is?description")
??.option(‘-r,?--recursive‘,?‘Remove?recursively‘)
??.option(‘-g,?--git?[type]‘,?‘Add?[marble]‘,?‘Angie‘)
??.option(‘-a,?--am‘,"ampm")
??.action(()?=>?{????console.log(123)
??});
program.on(‘option:am‘,?function?()?{??console.log("on:am")
});
program.on(‘option:recursive‘,?function?()?{??console.log("option:recursive")
});
program.on(‘command:rm‘,?function?()?{??console.log("command:rm")
});
program.on(‘option:git‘,?function?()?{??console.log("option:git")
});
program.on(‘command:*‘,?function?()?{??console.log(987)??console.error(‘Invalid?command:?%s\nSee?--help?for?a?list?of?available?commands.‘,?program.args.join(‘?‘));
??process.exit(1);
});
program.on(‘--help‘,?function()?{??console.log(‘****************‘);??console.log(‘Examples:‘);??console.log(‘****************‘);??console.log(‘??$?deploy?exec?sequential‘);??console.log(‘??$?deploy?exec?async‘);
});
program.parse(process.argv);分别执行command和option,会依次触发对应的函数,但是command:*具体是什么时候触发的?
command和option已经定义但是没有进行事件捕获时会触发以上情况就会触发command:*对应的事件,option:紧紧跟随的是option的长名。才会捕获到该事件。
开发本地模块
创建项目文件如下:
├─bin │??└─init-project.js ├─lib │??└─install.js └─templates ????└─dome1
创建好项目目录以后,安装如下依赖包:
命令:npm install --save-dev chalk commander fs-extra through2 vinyl-fs which path
首先在init-project.js中第一行添加#! /usr/bin/env node,这是用来指定脚本的执行程序,这里的Node可以用!/usr/bin/node,若用户将Node安装在非默认路径下会找不到Node。So~最好选择env环境变量查找Node安装目录。
init-project.js
#!?/usr/bin/env?node//?引入依赖var?program?=?require(‘commander‘);var?vfs?=?require(‘vinyl-fs‘);var?through?=?require(‘through2‘);const?chalk?=?require(‘chalk‘);const?fs?=?require(‘fs-extra‘);const?path?=?require(‘path‘);//?定义版本号以及命令选项program
??.version(‘1.0.0‘)
??.option(‘-i?--init?[name]‘,?‘init?a?project‘,?‘myFirstProject‘)
program.parse(process.argv);if?(program.init)?{??//?获取将要构建的项目根目录
??var?projectPath?=?path.resolve(program.init);??//?获取将要构建的的项目名称
??var?projectName?=?path.basename(projectPath);??console.log(`Start?to?init?a?project?in?${chalk.green(projectPath)}`);??//?根据将要构建的项目名称创建文件夹
??fs.ensureDirSync(projectName);??//?获取本地模块下的demo1目录
??var?cwd?=?path.join(__dirname,?‘../templates/demo1‘);??//?从demo1目录中读取除node_modules目录下的所有文件并筛选处理
??vfs.src([‘**/*‘,?‘!node_modules/**/*‘],?{?cwd:?cwd,?dot:?true?}).
??pipe(through.obj(function?(file,?enc,?callback)?{????if?(!file.stat.isFile())?{??????return?callback();
????}????this.push(file);????return?callback();
??}))????//?将从demo1目录下读取的文件流写入到之前创建的文件夹中
????.pipe(vfs.dest(projectPath))
????.on(‘end‘,?function?()?{??????console.log(‘Installing?packages...‘)??????//?将node工作目录更改成构建的项目根目录下
??????process.chdir(projectPath);??????//?执行安装命令
??????require(‘../lib/install‘);
????})
????.resume();
}install.js
//?引入依赖var?which?=?require(‘which‘);const?chalk?=?require(‘chalk‘);var?childProcess?=?require(‘child_process‘);//?开启子进程来执行npm?install命令function?runCmd(cmd,?args,?fn)?{
??args?=?args?||?[];??var?runner?=?childProcess.spawn(cmd,?args,?{????stdio:?‘inherit‘
??});
??runner.on(‘close‘,?function?(code)?{????if?(fn)?{
??????fn(code);
????}
??})
}//?查找系统中用于安装依赖包的命令function?findNpm()?{??var?npms?=?[‘tnpm‘,?‘cnpm‘,?‘npm‘];??for?(var?i?=?0;?i?<?npms.length;?i++)?{????try?{??????//?查找环境变量下指定的可执行文件的第一个实例
??????which.sync(npms[i]);??????console.log(‘use?npm:?‘?+?npms[i]);??????return?npms[i]
????}?catch?(e)?{
????}
??}??throw?new?Error(chalk.red(‘please?install?npm‘));
}var?npm?=?findNpm();
runCmd(which.sync(npm),?[‘install‘],?function?()?{??console.log(npm?+?‘?install?end‘);
})完成如上代码之后,更改package.json添加属性如下:
{??"bin":?{????"q-init":?"./bin/init-project.js"
??}
}注:自定义指令后指定的文件,一定要添加.js后缀文件名,否则会抛出错误。
接下来剩下的就是测试了,对于测试来说不需要把安装包推到npm中,npm为了方便,提供了npm link命令,可以实现预发布。在项目根目录中使用npm link没有报错的话,就说明推送成功了。现在就可以在全局中使用q-init了。
在全局中使用initP -h命令,能够输出所编译的help信息就说明可以初始化项目了。
Usage:?init-project?[options] Options: ??-V,?--version?????output?the?version?number ??-i?--init?[name]??init?a?project?(default:?"myFirstProject") ??-h,?--help????????output?usage?information
总结
commander在Vue-cli、creat-app(react)中都起到了很大的作用,这种创建脚手架的方式与vue-cli的方式不同,vue-cli则是使用git远程拉取项目再完成初始化,这样一来要比这种更加的方便灵活,每次模板变更不需要再次上传包,只需要更改git仓库就好了,方便快捷。
原文:https://blog.51cto.com/u_11020803/2820796