仿vue-cli搭建属于自己的命令行工具

前言

使用过@vue/cli的同学都知道,通过npm install -g @vue/cli全局安装之后,然后通过vue create my-project命令就可以安装项目了而且,在安装的过程中,命令行中有各种交互,比如选择什么预处理器,选择什么测试工具。这些强大的功能都令人羡慕,果然不愧是大佬开发的工具。但是随着前端知识的深入,我们越来越不满足于只是简单地使用这个工具,而是想要了解清楚为什么能够实现这么强大的命令行交互功能。这篇文章就是仿照vue-cli搭建一个属于自己的命令行工具。我们实现的是一个支持在命令行中增删改查的命令行工具。

命令行工具必备包

commander:编写指令和处理命令行参数的包。 我们在使用vue create project这个命令创建项目时,有没有想过为什么这个命令能够实现创建项目的功能,commander包就是用来定义这些命令的。

# 定义查看版本的命令
program
  .version('1.0.0')
  .description('自定义命令行工具')
# 定义添加的命令
program
  .command('add')
  .alias('a')
  .description('添加新用户')
  .action(() => {
    inquirer.prompt(addQuery).then((answers) => {
      addCustomer(answers)
    })
  })

通过commander进行定义我们就可以使用类似于xx --version 来查看版本以及xxx add来实现类似于vue create project这样的功能。
inquirer:命令行交互的工具。 在使用vue/cli创建项目的过程中,我们经常会看到类似于选择什么css预处理器,选择什么测试方法啊这种命令行中的询问,但是实际上我们平常使用命令行时都是直接输入命令,很少使用这种命令行交互的。而inquirer就是node环境下提供的一种命令行交互工具。

inquirer
  .prompt([
    // 命令行中交互式问题
  ])
  .then(answers => {
    // answers 命令行中用户输入的答案
  });

其他还有一些用户命令行美化的工具,比如:
chalk:给命令行文字添加颜色
ora:命令行中添加进度条
好了,到目前为止,我们了解了开发命令行工具常见的包,接下来进入正式的开发。

实现数据库的增删改查

在本文中我们使用的mongodb数据库,Schema和Model的定义以及数据数据库的连接就不一一展示了,我们这里展示数据库的增删改查实现。因为这些实现实际上就是命令行命令对应的实现。

添加用户

const addCustomer = (customer) => {
  Customer.create(customer).then((data) => {
    console.info('新用户已添加...');
    mongoose.connection.close();
  })
}

查找用户

const findCustomer = (name) => {
  //不区分大小写
  const search = new RegExp(name,'i');
  Customer.find({
    $or:[{firstname:search},{lastname:search}]
  }).then((customer) => {
    console.info(customer);
    console.info(`${customer.length}个匹配`);
    mongoose.connection.close();
  })
}

更新用户

const updateCustomer = (_id,customer) => {
  Customer.update({_id},customer).then((customer) => {
    console.info('用户信息已经更新');
    mongoose.connection.close();
  })
}

删除用户

const removeCustomer = (_id) => {
  Customer.remove({_id}).then((customer) => {
    console.info('用户信息已经删除');
    mongoose.connection.close();
  })
}

用户列表

const listCustomer = () => {
  Customer.find().then((customer) => {
    console.info(customer);
    console.info(`${customer.length}个用户`);
    mongoose.connection.close();
  })
}

上面我们定义了数据库的增删改查功能,接下来我们把这些功能通过命令行的指令来实现

编写具体指令

编写添加指令

const program = require('commander');
const inquirer = require('inquirer');
// 定义命令行询问的问题:
const addQuery = [
  {
    type:'input',
    name:'firstname',
    message:'请输入firstname'
  },
  {
    type:'input',
    name:'lastname',
    message:'请输入lastname'
  },
  {
    type:'input',
    name:'phone',
    message:'请输入phone'
  },
  {
    type:'input',
    name:'email',
    message:'请输入email'
  },
]
// 添加指令的实现
program
  .command('add')
  .alias('a')
  .description('添加新用户')
  .action(() => {
    inquirer.prompt(addQuery).then((answers) => {
      addCustomer(answers)
    })
  })
  // 解析命令行参数,必须有
program.parse(process.argv);

从上面的代码中,我们可以看出我们主要使用了commander和inquirer这两个包,通过使用command定义具体指令,其中: .command('add'):是定义add指令。这样的话可以在命令行中直接运行 node command.js add .alias('a'):是定义add的简写,可以直接使用node command.js a来实现 .description:是描述指令的功能,在help()时提示用户 .action():是定义指令的具体执行内容。也是最关键的部分。

如果我们我们想在命令行中提供交互,就可以在action的回调函数中使用inquirer进行询问。具体的询问内容通过一个数组提供,数组元素的内容可以在inquirer进行查看。这样的话,我们就可以通过使用node command.js add(备注:command.js是我编写命令的文件)来运行了。而且我们也可以在命令行中看到各种命令行交互。这样的话,我们初步实现了命令行工具的功能。接下来我们继续实现查找,修改,删除用户命令。

编写查找用户指令

查找用户的指令和添加用户指令的编写方式类似,只不过查找时不需要进行询问了。

program
  .command('find <name>')
  .alias('f')
  .description('查找用户')
  .action((name) => {
    findCustomer(name)
  })

编写删除用户指令

program
  .command('remove <_id>')
  .alias('r')
  .description('删除用户')
  .action((_id) => {
    removeCustomer(_id);
  })

编写更新用户指令

program
  .command('update <_id>')
  .alias('u')
  .description('更新用户')
  .action((_id) => {
    inquirer.prompt(addQuery).then((answers) => {
      console.log(_id);
      updateCustomer(_id,answers)
    })
  })

编写获取所有用户列表指令

program
.command('list')
.alias('l')
.description('获取所有用户')
.action(() => {
  listCustomer();
})

bin和npm link简化命令执行

上面我们定义了增啥改查指令,通过node command + add(或者find,remove等)可以实现命令的执行。但是我们每次运行时还是需要指定通过node来运行,而且需要指定运行的文件地址,但是实际上,我们在是哟个vue/cli时,只是直接通过vue命令就能够运行的,不需要node。因此,这里我们肯定可以优化命令的执行,而通过在package.json中定义bin目录,可以帮助我们实现这个功能。

  // bin用来指定每个文件所对应的可执行文件路径
  "bin": {
    "command": "bin/command.js"(假设command.js在bin目录下)
  },

上面代码指定:command命令对应的可执行文件为bin子目录下的command.js。npm会寻找这个文件,在node_modules/.bin目录下建立链接。在上面的例子中,command.js会建立符号链接npm_modules/.bin/command.js。由于node_modules/.bin/目录会在运行时加入系统的PATH变量,因此运行npm命令时,就可以不带路径,直接通过命令来调用这些脚本。 然后,通过在根目录执行npm link(把命令挂载到全局),这样我们每次只需要输入command,就相当于执行node bin/command.js
注意:

  1. 使用npm link时可能出现报错,如果有报错先使用npm unlink解绑,解绑完成之后在运行npm link 进行绑定。
  2. 为了确保在不同环境中都能够通过node运行命令,我们需要在编写指令的开头添加以下语句。
#!/usr/bin/env node     // 必须添加这行代码
const program = require('commander');

接下来我们就可以通过command add这样直接运行命令了。一个简易的命令行工具实现数据库的增删改查就实现了。

总结

到目前为止,一个简易的命令行工具就实现了,它具体的实现如下:

  1. 通过commander定义命令
  2. 通过inquirer提供命令行交互
  3. 使用mongoose实现数据库的增删改查

具备了一个cli工具的初步功能。这些都是一些简单的功能,但是帮助我们理解了命令行工具的具体实现,加深了理解。以后我们再次使用vue/cli脚手架时,每一步我们都可以想象一下它内部可能是怎么实现的,而不是只知道使用,不知道具体原理。
最后,完结撒花。

本文使用 mdnice 排版