每日一博 | 自己手动实现一个 Node.js-CLI 开发工具
前言 看如今火热的前端框架,都有自己的CLI工具,例如Vue CLI,creat-react-app等等,搭建项目十分的方便。所以我也在想,要不也实现一个CLI工具,不一定要和前面几个那样高大上,但只要能提高工作的效率,就值得试一试。 初始化项目 首先,我们要打开CLI界面,用npm初始化一个项目: npminit package.json { "name": "mycli", "version": "1.0.0", "description": "A cli-demo", "main": "index.js", "author": "xmanlin", "license": "MIT" } 里面一些用不上都已经去掉。 自定义命令 相信很多小伙伴在使用 npm 初始化项目的时候,或者在用 Vue CLI 搭建项目的时候,会发现它们有一个共同点——都有自己个性化的命令,感觉有点酷炫。那么我们怎么才能实现自己的命令呢,很简单,在 package.json 添加bin: { "name": "mycli", "version": "1.0.0", "description": "A cli-demo", "main": "index.js", "bin": { "mycli": "./index.js" }, "author": "xmanlin", "license": "MIT" } package.json中的bin的作用就是可以让设置的mycli成为一个可执行命令,而命令所执行的文件就是后面的index.js,这些都可以根据自己的想法来定。接着我们要继续对index.js进行修改: index.js #!/usr/bin/env node console.log("执行成功") 这里的第一行很重要,这里表明index.js是 node 可执行文件。 设置完成后,就可以全局安装我们的CLI工具了: npminstall-g + mycli@1.0.0 added 1 package from 1 contributor in 0.12s 可以看见全局安装成功,最后我们试试我们的命令: mycli 输出: 执行成功 交互式命令行 刚刚小伙伴们在进行命令行操作的时候,使用了不同选项的命令,例如npm init、npm install -g,其中包含一些交互式的,比如在初始化项目时,npm init提供一些输入问答形式的命令。接下来,我们就来为自己的CLI工具添加这些类似的命令。 我们可以依赖两个库进行我们的开发:commander.js和Inquirer.js commander.js:完整的 node.js 命令行解决方案。我们可以利用它,快速的编写我们的命令行,自定义化操作。 Inquirer.js:是常规交互式命令行用户接口的集合,提供给 Node.js 一个方便嵌入,漂亮的命令行接口。我们可以用来快速进行交互式命令行的编写。 这两个库的具体用法,这里就过多的介绍,小伙伴们可以点击上面名字的链接去熟悉一下,花不了太多时间,实际用起来也不难,后面那个没有中文Readme,但是不妨碍大家会搜索呀~对不对。 定义选项 我们首先来实现类似于npm -v、node -v这样类似的命令选项。 首先安装commander.js: npminstallcommander 然后引入 commander.js 并对index.js进行修改 #!/usr/bin/env node const { program } = require('commander'); // 字符串分割为数组的方法 function strToArr(value, preValue){ return value.split(',') } // cli版本 program.version(require('./package').version, '-v, --version', 'cli的最新版本'); // 设置选项 program .option('-d, --debug', '调试一下') .option('-l, --list <value>', '把字符串分割为数组', strToArr) .action((options, command) => { // 进行逻辑处理 if(options.debug) { console.log("调试成功") } if(options.list !== undefined) { console.log(options.list) } }); // 处理命令行输入的参数 program.parse(process.argv); 我们来试试刚刚设置的命令选项: mycli -d 输出: 调试成功 输入: mycli-l1,2,3 输出: ['1','2','3' ] 在commander.js里面已经给我们定义好了--help选项: mycli -h 输出: Usage: index [options] Options: -v, --version cli的最新版本 -d, --debug 调试一下 -l, --list <value> 把字符串分割为数组 -h, --help display help for command 利用--help选项,我们可以很清楚的了解到 mycli 中已有多少命令。 设置子命令 在项目开发中,我们有时会用到类似于npm run xxx这样的命令,其中run就相当于npm的子命令。这里我们也可以给mycli设置类似的子命令: const { program } = require('commander'); ... // 创建文件命令行 program .command('create <filename>') .description('创建一个文件') .action((filename) => { console.log(filename) }) ... // 处理命令行输入的参数 program.parse(process.argv); command('create <filename>')就是创建了一个 mycli 的create子命令,后面跟了一个必填参数。 输入: myclicreatefile 输出 file 子命令创建成功。 利用命令行创建项目文件 我们现在能够定义选项,设置命令了。接下来我们就可以实际做点东西,利用命令行来创建项目的文件。 简单的设计一个流程: 创建模板文件 我们先来创建一个templates文件夹,然后在里面写几个常见的模板文件,这里的模板文件运用到了模板字符串,结构如下: reactClass.js module.exports = function (className) { return ` import * as React from 'react'; export class ${className} extends React.Component{ constructor(props){ super(props); this.state = {} } componentDidMount(){ } render() { return ( <div></div> ) } } ` } vueTemplate.js module.exports = function () { return ` <template> <div></div> </template> <script> export default { data() { return {} } methods: { } } </sctipt> <style lang="scss" scoped> </style> ` } index.js const reactClass = require('./reactClass'); const vueTemplate = require('./vueTemplate'); module.exports = [ { name: 'reactClass', src: reactClass }, { name: 'vueTemplate', src: vueTemplate } ] 模板文件创建完成后,我们先把它放在一边,待会儿才用的上。 创建交互命令行以及调用模板 当我们输入mycli create file命令后,需要得到下图中的效果,我们可以手动上下进行选择,也就是可以被称为交互式的命令。 这里就要用到我们上文中提到的另外一个库——Inquirer.js 首先肯定需要进行安装 npminstallinquirer 引入并修改我们根目录下的index.js: #!/usr/bin/env node const { program } = require('commander'); const inquirer = require('inquirer'); // 引入模板文件 const templates = require('./templates/index'); // 命令行选择列表 let prompList = [ { type:'list', name: 'template', message: '请选择你想要生成的模板?', choices: templates, default: templates[0] } ] ... // 创建文件命令行 program .command('create <filename>') .description('创建一个文件') .action(async (filename) => { const res = await inquirer.prompt(prompList) console.log(res) }) // 处理命令行输入的参数 program.parse(process.argv); 接下来我们在命令行中输入: myclicreatefile 就可以得到上面所展示的效果 然后选择第一个后回车: 可以看到输出了我们所选择模板的名字。接下来就是进行实际文件的创建。 创建项目文件 创建文件则需要调用node.js的fs相关api,然后修改index.js: // 处理文件 const fs = require("fs"); ... // 创建文件命令行 program .command('create <filename>') .description('创建一个文件') .action(async (filename) => { const res = await inquirer.prompt(prompList) if(res.template === 'reactClass') { templates.forEach((item) => { if(item.name === 'reactClass') { fs.writeFile(`./${filename}.jsx`, item.src(filename), function(err) { if(err) { console.log('创建失败:', err) } else { console.log(`创建文件成功!${filename}.jsx`); } }) } }) } if(res.template === 'vueTemplate') { templates.forEach((item) => { if(item.name === 'vueTemplate') { fs.writeFile(`./${filename}.vue`, item.src(), function(err) { if(err) { console.log('创建失败:', err) } else { console.log(`文件创建成功!${filename}`); } }) } }) } }) ... 我们再次在命令行中输入mycli create file,然后选择一个模板。 输出: 创建文件成功!file.jsx 同时我们可以看见项目根目录下面新增了一个file.jsx文件: 打开file.jsx可以看见文件的类名也进行了相应的填写。不仅如此,因为我们的mycli是全局安装的,所以可以说在电脑中任何位置,只要我们输入mycli create file,我们在当前目录下都能获取到file.jsx文件。也就意味着,我们在开发一些项目的时候,不用在复制粘贴,删删改改。 直接一行命令就能搞定了~ 更多功能 既然能创建文件,那么能否创建文件夹?答案是肯定的,只需要继续添加命令就行: ... // 创建文件夹命令行 program .command('create-f <folder>') .description('创建一个文件夹') .action((folder) => { if(fs.existsSync(folder)) { console.log('文件夹已存在') } else { fs.mkdirSync(folder); console.log('文件夹创建成功') } }); ... 然后在命令行中输入mycli create-f xxx,同样可以创建想要命名文件夹。 最后 到此我们的 mycli 已经能进行创建文件,创建文件夹等等操作了。跟着敲一遍,一定会有收获的。后续我们可以根据项目的实际情况编写模板文件,自定义想要的命令,进行必要的拓展。这个就留给大家尽情的发挥啦~