• 用commander.js构建自己的脚手架工具


    随着前端技术的发展,工程化逐渐成为了一种趋势。但在实际开发时,搭建项目是一件很繁琐的事情,尤其是在对一个框架的用法还不熟悉的时候。于是很多框架都自带一套脚手架工具,在初始化前端项目的时候就可以不用自己从头搭建,只要在命令行输入初始化命令即可。

    那么,如果想自行开发出这样一个命令行工具来初始化自定义项目,该怎么做呢?研究的过程中,偶然间发现了 commander.js 这个模块,可以帮助命令行工具的开发。于是边研究边整理了这篇笔记。

    一、commander.js的基本用法
    1. 安装

    mkdir commander-example && cd commander-example
    npm install commander --save


    2. 使用

    新建一个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	est --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')
    参数解析:
    ① 版本号<必须>

    ② 自定义标志<可省略>:默认为 -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)

    二、使用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');
    })

    三、构建项目demo
    此demo用于初始化项目副本,因此可以根据自己的需要构建。我们可以利用一些脚手架工具来初始化项目,也可以自己一步步搭建。

    将搭建的项目复制进templates目录下(node_modules下的文件及文件夹可以不用复制),并重命名为demo1;然后在init-commander-tool目录下运行 node bininit-project --help 测试所有命令是否能正常显示。

    四、全局使用
    在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 即可,项目名称可以自己定义。



  • 相关阅读:
    创建react项目
    解决移动端弹窗下页面滚动问题
    前端常用的几种加密方式
    http请求状态码
    vue代理配置
    自动化测试实操案例详解 | Windows应用篇
    Google 再见 Java
    一次诡异的 SQL 数量统计查询不准的问题
    Maven
    淘宝技术分享:手淘亿级移动端接入层网关的技术演进之路
  • 原文地址:https://www.cnblogs.com/cangqinglang/p/10642891.html
Copyright © 2020-2023  润新知