随着前端技术的发展,工程化逐渐成为了一种趋势。但在实际开发时,搭建项目是一件很繁琐的事情,尤为是在对一个框架的用法还不熟悉的时候。因而不少框架都自带一套脚手架工具,在初始化前端项目的时候就能够不用本身从头搭建,只要在命令行输入初始化命令便可。前端
那么,若是想自行开发出这样一个命令行工具来初始化自定义项目,该怎么作呢?研究的过程当中,偶然间发现了 commander.js 这个模块,能够帮助命令行工具的开发。因而边研究边整理了这篇笔记。node
1、commander.js的基本用法
1. 安装npm
mkdir commander-example && cd commander-example
npm install commander --save
2. 使用json
新建一个bin目录,而后在该目录下新建一个test.js文件,文件内容:数组
// 引入依赖 var program = require('commander'); // 定义版本和参数选项 program .version('0.1.0', '-v, --version') .option('-i, --init', 'init something') .option('-g, --generate', 'generate something') .option('-r, --remove', 'remove something'); // 必须在.parse()以前,由于node的emit()是即时的 program.on('--help', function(){ console.log(' Examples:'); console.log(''); console.log(' this is an example'); console.log(''); }); program.parse(process.argv); if(program.init) { console.log('init something') } if(program.generate) { console.log('generate something') } if(program.remove) { console.log('remove something') }
而后在命令行里输入测试:框架
node bin\test --help
获得以下结果:函数
Usage: test [options] Options: -v, --version output the version number -i, --init init something -g, --generate generate something -r, --remove remove something -h, --help output usage information Examples:
3. API解析工具
· version测试
做用:定义命令程序的版本号
用法示例:.version('0.0.1', '-v, --version')
参数解析:
① 版本号<必须>ui
② 自定义标志<可省略>:默认为 -V 和 --version
· option
做用:用于定义命令选项
用法示例:.option('-n, --name<path>', 'name description', 'default name')
参数解析:
① 自定义标志<必须>:分为长短标识,中间用逗号、竖线或者空格分割;标志后面可跟必须参数或可选参数,前者用 <> 包含,后者用 [] 包含
② 选项描述<省略不报错>:在使用 --help 命令时显示标志描述
③ 默认值<可省略>
· command
做用:添加命令名称
用法示例:.command('rmdir <dir> [otherDirs...]', 'install description', opts)
参数解析:
① 命令名称<必须>:命令后面可跟用 <> 或 [] 包含的参数;命令的最后一个参数能够是可变的,像实例中那样在数组后面加入 ... 标志;在命令后面传入的参数会被传入到 action 的回调函数以及 program.args 数组中
② 命令描述<可省略>:若是存在,且没有显示调用action(fn),就会启动子命令程序,不然会报错
③ 配置选项<可省略>:可配置noHelp、isDefault等
· description
做用:定义命令的描述
用法示例:.description('rmdir desc')
· action
做用:定义命令的回调函数
用法示例:.action(fn)
· parse
做用:用于解析process.argv,设置options以及触发commands
用法示例:.parse(process.argv)
2、使用commander.js开发本地模块init-commander-tool
1. 新建目录以下:
init-commander-tool |-bin |-init-project.js |-lib |-install.js |-templates
2. 运行 npm init 来初始化项目,项目名称设置为init-commander-tool,并安装依赖包:
``` "devDependencies": { "chalk": "^2.4.1", "commander": "^2.15.1", "fs-extra": "^6.0.1", "path": "^0.12.7", "through2": "^2.0.3", "vinyl-fs": "^3.0.3", "which": "^1.3.1" } ```
npm init
npm install
3. init-porject.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(); }
· #! /usr/bin/env node
指定脚本的执行程序,这里是node。也能够用!/usr/bin/node,但若是用户将node安装在非默认路径下,会找不到node。因此最好选择用env(包含环境变量)来查找node安装目录。
· vinyl-fs:
Vinyl用于描述文件的元数据对象;该模块主要暴露了两个方法src和dest,它们各自返回数据流;不一样的是前者提供Vinyl对象,后者使用Vinyl对象;简单的说就是一个读取文件,另外一个往磁盘写文件
param@src:src(globs[, options])
第一个参数为字符串或字符串数组,代表文件位置。若是是数组,则会按照从前到后的顺序来执行,但带有!(非)符号的路径应该放在后面。第二个参数为选项对象,查看具体配置
param@dest:dest(folder[, options])
第一个参数为文件夹路径或函数,若是是后者,则它会被用于处理每个Vinyl文件对象,且该函数必须返回一个文件夹路径。第二个参数为选项对象,查看具体配置。该方法返回文件对象流,并将他们写入磁盘以及传递到管道下游,因此你能够继续在管道中进行操做。若是文件拥有symlink属性,就会建立一个符号连接。
· through2:
through2主要是对node中streams.Transform的简单封装,让其使用起来更加简单。具体用法可查看through2**
4. 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'); })
3、构建项目demo
此demo用于初始化项目副本,所以能够根据本身的须要构建。咱们能够利用一些脚手架工具来初始化项目,也能够本身一步步搭建。
将搭建的项目复制进templates目录下(node_modules下的文件及文件夹能够不用复制),并重命名为demo1;而后在init-commander-tool目录下运行 node bin\init-project --help 测试全部命令是否能正常显示。
4、全局使用
在package.json里面添加bin字段:
// bin项用来指定各个内部命令对应的可执行文件的位置 "bin": { "initP": "./bin/init-project" },
而后在init-commander-tool目录下运行 npm link
,将本地模块连接到全局环境下,这样就能够在任何地方使用initP命令了。
在命令行中键入:initP --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
最后在须要初始化项目的地方运行 initP --init myProject
便可,项目名称能够本身定义。