随着NodeJs
的不断发展,对于前端来讲要作的东西也就更多,Vue
脚手架React
脚手架等等等一系列的东西都脱颖而出,进入到人们的视野当中,对于这些脚手架工具来说也只是停留在应用阶段,历来没有想过脚手架是如何实现的?vue init webpack 项目名称
是如何经过这样的命令建立了一个项目,其最重要的模块就是今天要说的Commander
。javascript
Commander
模块又国外TJ
大神所编写前端
项目地址:Commandervue
Commander基本用法
Commander
文档写的很详细,跟着文章详细的学习一下,Commander
是一个Nodejs
模块,须要在Node
环境中运行,在使用前确认一下Node
环境是否已安装。java
npm install commander --save
在Commander
模块下存在option
方法用来定义commander
的选项options
,用来做为选项的文档。node
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
是一个数组。react
打印输出一下process.argv
和program.args
并查看了一下输出结果以下,使用以下命令运行一下文件:webpack
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
方法能够接收三个参数:git
必须
:分为长短标识,中间用逗号、竖线或者空格分割;标志后面可跟必须参数或可选参数,前者用<>
包含,后者用[]
包含。省略不报错
:在使用 --help 命令时显示标志描述可省略
:当没有传入参数时则会使用默认值若咱们执行node index -g
获得的结果则是Angie git
其第三个参数则做为了默认值填写在了对应的位置上。除了上面所说还可使用以下命令:github
// 执行 -g 参数 a // 执行 -b 参数 s node index -g a -b s // 执行 -g和-b 传入a参数给-g // -b 参数暂时不知道怎么传入 node index -gb a
调用版本会默认将-V
和--version
选项添加到命令中。当存在这些选项中的任何一个时,该命令将打印版本号并退出。web
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
仓库就行了,方便快捷。