——————————☆☆☆——————————

Node 系列相应地址:

  • 代码仓库:https://github.com/LiangJunro...
  • 文章仓库:https://github.com/LiangJunro...系列-前端材料/Node

——————————☆☆☆——————————

一 前言

通过后面 TypeScript 环境的搭建和 commander.js 的配合,咱们当初能够在 .ts 文件中编写对应指令,而后通过 npm run xxx 来运行我的项目了,然而这种形式有个 Bug:

  • 当指令过多的时候,咱们压根记不住那么多的指令!

所以,就须要一个智能提醒,将指令简化并可视化。

二 集成 Inquirer.js

这边 jsliang 想的一个法子就是通过终端那种问答模式的来解决这个问题(后续可能安顿页面或者 Chrome 插件等)

那么,废话少说,Here we go~

首先,装置必须的包:

  • 装置 Inquirer.jsnpm i inquirer
  • 装置 @types/inquirer(可选,TS 必装):npm i @types/inquirer -D

而后。咱们就能够开始耍起来了,接入后面的 TypeScript 和 commander.js,拿起 index.tspackage.json 就是一顿批改:

src/index.ts
import program from 'commander';import inquirer from 'inquirer';import { sortCatalog } from './sortCatalog';program  .version('0.0.1')  .description('工具库')program  .command('jsliang')  .description('jsliang 帮忙指令')  .action(() => {    inquirer    .prompt([      {         type: 'rawlist',        name: 'question1',        message: '请问须要什么服务?',        choices: ['公共服务', '其余']      },    ])    .then((answers) => {      if (answers.question1 === '公共服务') {        inquirer.prompt([          {            type: 'rawlist',            name: 'question',            message: '以后公共服务有:',            choices: ['文件排序']          }        ]).then((answers) => {          if (answers.question === '文件排序') {            inquirer.prompt([              {                type: 'input',                name: 'question',                message: '须要排序的文件夹为?(绝对路径)',                default: 'D:/xx',              }            ]).then(async (answers) => {              const result = await sortCatalog(answers.question);              if (result) {                console.log('排序胜利!');              }            }).catch((error) => {              console.error('出错啦!', error);            });          }        }).catch((error) => {          console.error('出错啦!', error);        });      } else if (answers === '其余') {        // 做其余事件      }    }).catch((error) => {      console.error('出错啦!', error);    });  });program.parse(process.argv);

留神这里 sort 改成 jsliang 了(人不要脸天下无敌)。

package.json
{  "name": "jsliang",  "version": "1.0.0",  "description": "Fe-util, Node 工具库",  "main": "index.js",  "scripts": {    "jsliang": "ts-node ./src/index.ts jsliang"  },  "keywords": [    "jsliang",    "Node 工具库",    "Node"  ],  "author": "jsliang",  "license": "ISC",  "devDependencies": {    "@types/inquirer": "^7.3.1",    "@types/node": "^15.12.2",    "@typescript-eslint/eslint-plugin": "^4.26.1",    "@typescript-eslint/parser": "^4.26.1",    "eslint": "^7.28.0",    "ts-node": "^10.0.0",    "typescript": "^4.3.2"  },  "dependencies": {    "commander": "^7.2.0",    "inquirer": "^8.1.0"  }}

于是就有了成果:

一样的丝滑好用,还能够管制文件夹门路了~

然而!小伙伴们看到下面代码,是不是有种想吐的感觉。

  • 问题 1:呀,这是啥,这些代码你写了什么性能?
  • 问题 2:太恶心了吧,竟然不反对 async/await

OK,一一解决问题,咱们先解说下 Inquirer.js 外面的一些操作。

三 Inquirer.js 应用技巧

在下面的代码中,通过 .prompt(Array<Object>) 能够传递多个问题信息,而后通过回调获取答案,举例一个输入框:

inquirer.prompt([  {     type: 'input',    name: 'question',    message: '请问须要什么服务?',  }]).then((res) => {  console.log('胜利!', res);}).catch((err) => {  console.error('报错!', err);});

其中 Object 外面能够塞:

  • type:【String】提醒的类型,默认 input,蕴含 inputnumberconfirmlistrawlistexpandcheckboxpasswordeditor
  • name:【String】存储以后问题答复的变量
  • message:【String|Function】发问的问题内容
  • default:【String|Number|Boolean|Array|Function】默认值
  • choices:【Array|Function】列表选项
  • validate:【Function】验证办法,校验输出值是否可行,无效返回 true,否则返回字符串示意错误信息(返回 false 则为默认的错误信息)
  • filter:【Function】对答案进行过滤解决,返回解决后的值
  • transformer:【Function】操作答案的显示成果
  • when:【Function|Boolean】承受答案,依据后面的内容判断是否须要展现该问题
  • pageSize:【Number】在 listrawlistexpandcheckbox 这种多选项中,进行分页拆分
  • prefix:【String】批改默认前缀
  • suffix:【String】批改默认后缀
  • askAnswered:【Boolean】已有答案是否强制发问
  • loop:【Boolean】list 是否能循环滚动抉择,默认 true

置信你也看不懂,咱们将一些可能用到的写一写用例吧。

后续代码为简写,全写大略为上面代码所示,前面就不颤抖了
import program from 'commander';import inquirer from 'inquirer';program  .version('0.0.1')  .description('工具库')program  .command('jsliang')  .description('jsliang 帮忙指令')  .action(() => {    inquirer    .prompt([      {         type: 'rawlist',        name: 'question',        message: '请问须要什么服务?',        choices: ['公共服务', '其余']      },    ])    .then((answers) => {      console.log('答案:', answers);    }).catch((error) => {      console.error('出错啦!', error);    });  });program.parse(process.argv);
留神:
① 上面这些举例,你也能够在 Inquires.js 中找到,然而 jsliang 心愿搬运到本人这篇文章中不便后续检索。
② 如果有评论没看到这个正文就吐槽 jsliang 缮写人家 README,那 jsliang 也无话可说,只是被吐槽了几次,略微写点正文

3.1 输入框

输出文本

可配合参数:type, name, message[, default, filter, validate, transformer]

inquirer.prompt([  {     type: 'input',    name: 'question',    message: '问题?',    default: 'liangjunrong',  }]);

输出数字

可配合参数:type, name, message[, default, filter, validate, transformer]

inquirer.prompt([  {     type: 'number',    name: 'question',    message: '问题?',    default: '1',  }]);

输出明码

可配合参数:type, name, message, mask,[, default, filter, validate]

inquirer.prompt([  {     type: 'password',    name: 'question',    message: '问题?',  }]);

3.2 单选项

没下标的单选项

可配合参数:type, name, message, choices[, default, filter, loop]

inquirer.prompt([  {     type: 'list',    name: 'question',    message: '问题?',    default: 'jsliang',    choices: ['liangjunrong', 'jsliang']  }]);

增加分隔符

inquirer.prompt([  {     type: 'list',    name: 'question',    message: '问题?',    default: 'jsliang',    choices: [      'liangjunrong',      new inquirer.Separator(), // 增加分隔符      'jsliang',    ]  }]);

有下标的单选项

可配合参数:type, name, message, choices[, default, filter, loop]

inquirer.prompt([  {     type: 'rawlist',    name: 'question',    message: '问题?',    default: 'jsliang',    choices: ['liangjunrong', 'jsliang']  }]);

3.3 多选项

可配合参数:type, name, message, choices[, filter, validate, default, loop]

inquirer.prompt([  {     type: 'checkbox',    name: 'question',    message: '问题?',    choices: ['liangjunrong', 'jsliang']  }]);

3.4 确认框

可配合参数:type, name, message, [default]

inquirer.prompt([  {     type: 'confirm',    name: 'question',    message: '问题?',  }]);

3.5 校验输出

inquirer.prompt([  {     type: 'input',    name: 'phone',    message: '请输出手机号',    validate: (val) => {      if (val.match(/\d{11}/g)) {        return true;      }      return '请输出 11 位数字';    },  }]);

四 动静发问

下面咱们说了 2 个问题:

  • 问题 1:呀,这是啥,这些代码你写了什么性能?
  • 问题 2:太恶心了吧,竟然不反对 async/await

方才曾经将问题 1 解决了(就是这个 Inquires.js 性能反对),上面咱们看看问题 2 怎么操作。

其实为了解决这个问题,咱们须要依照 Inquires.js 中的举荐装置 Rx.jsRx.js 参考文献:

  • GitHub:rxjs
  • RxJS 中文文档

开始装置:

  • 装置 rxjsnpm i rxjs@5

以后版本为 v7.1.0,然而看了下 Inquirer.js 举例的是 v5.x 版本,找了一会找不到新版本的用法,只能出此下举

其次 jsliang 是真的懒,不想理解 Rx.js 做啥子的,我只心愿我的项目能依照 async/await 形式跑起来

import program from 'commander';import Rx from 'rxjs/Rx';import inquirer from 'inquirer';const prompts = new Rx.Subject();// 有情的信息处理器inquirer.prompt(prompts).ui.process.subscribe((result) => {  console.log('胜利:', result);}, (error: unknown) => {  console.error('失败', error);}, () => {  console.log('实现');});program  .version('0.0.1')  .description('工具库')program  .command('jsliang')  .description('jsliang 帮忙指令')  .action(() => {    prompts.next({      type: 'confirm',      name: 'question',      message: '问题?',    });    prompts.complete();  });program.parse(process.argv);

这样就实现了封装,更不便解决信息了。(能够设想前面会有一堆 switch...case... 判断)

然而,料想不到的是,在多个模块接入 Inquire.js 后,出问题了。

多个模块示例
+ src  - index.ts  + base    - config.ts  + common    - inquirer.ts  + jsliang    - inquirer.ts

暂不须要依照这个目录更改接口,以下一个目录为准

集体狐疑 Rx.js 是单实例缘故

运行时报错提醒:

npm ERR! code ELIFECYCLEnpm ERR! errno 1npm ERR! jsliang@1.0.0 test: `ts-node ./src/index.ts test`npm ERR! Exit status 1npm ERR!npm ERR! Failed at the jsliang@1.0.0 test script.npm ERR! This is probably not a problem with npm. There is likely additional logging output above.npm ERR! A complete log of this run can be found in:npm ERR!     C:\Users\wps\AppData\Roaming\npm-cache\_logs\2021-06-08T11_46_58_005Z-debug.log

排查了老久,应该跟我不相熟 RX.js 无关,所以就想着能不能更新一波:

【准】依照这个目录更改文件夹/文件
+ src —————————————————————— src 文件夹  - index.ts ——————————————— 主入口  + base ——————————————————— 根底文件夹,例如 config/math 等    - config.ts ———————————— 罕用配置项    - inquirer.ts —————————— inquirer 总解决口,对立封装 async/await    - interface.ts ————————— 临时将所有通用的 interface.ts 放到这里  + common ————————————————— 通用性能    - index.ts ————————————— common 解决问题的入口    - sortCatalog.ts —————— inquirer 调用具体的性能文件  + jsliang ———————————————— 业务性能    - xx.ts ———————————————— 业务性能文件

顺带给个目录图吧:

src/base/inquirer.ts
import * as myInquirer from 'inquirer';import Rx from 'rxjs/Rx';import { Question } from './interface';export const inquirer = (questions: Question[], answers: any): void => {  const prompts = new Rx.Subject();  // 长度判断  if (questions.length !== answers.length) {    console.error('问题和答案长度不统一!');  }  // 问题列表  const questionList = questions.map((item, index) => {    return () => {      prompts.next(Object.assign({}, item, {        name: String(index),      }));    };  });  // 问题处理器  myInquirer.prompt(prompts).ui.process.subscribe(async (res) => {    console.log('执行胜利,输出信息为:', res);    const index = Number(res.name);        // 回调函数:后果、问题列表、prompts(管制是否须要进行)    answers[index](res, questionList, prompts);    // 默认最初一个问题就主动终止    if (index === answers.length - 1) {      prompts.complete(); // 回调函数能够手动管制终止询问机会    }  }, (error: unknown) => {    console.error('执行失败,报错信息为:', error);  }, () => {    // console.log('实现'); // 必定会执行的代码  });  // 执行第一个问题  questionList[0]();};
src/base/interface.ts
export interface Question {  type: string,  name?: string,  message: string,  default?: string,  choices?: string[],  validate?(): boolean,}export interface Result {  name: string,  answer: string,}

依照这样子设置后,就能够在其余中央欢快游玩了:

src/common/index.ts
import { inquirer } from '../base/inquirer';import { Result } from '../base/interface';import { sortCatalog } from './sortCatalog';const common = (): void => {  // 测试新个性  const questionList = [    {      type: 'list',      message: '请问须要什么服务?',      choices: ['公共服务', '其余']    },    {      type: 'list',      message: '以后公共服务有:',      choices: ['文件排序']    },    {      type: 'input',      message: '须要排序的文件夹为?(绝对路径)',      default: 'D:/xx',    },  ];  const answerList = [    async (result: Result, questions: any) => {      if (result.answer === '公共服务') {        questions[1]();      } else if (result.answer === '其余') {        // 做其余事件        console.log('暂未开明该服务');      }    },    async (result: Result, questions: any) => {      console.log(result);      if (result.answer === '文件排序') {        questions[2]();      }    },    async (result: Result) => {      const sortResult = await sortCatalog(result.answer);      if (sortResult) {        console.log('排序胜利!');      }    },  ];  inquirer(questionList, answerList);};export default common;

传递问题数组,而后回调函数解决内容,满足我以后的需要,咱就不再革新了。

其余具体文件内容如下:

src/index.ts
import program from 'commander';import common from './common';program  .version('0.0.1')  .description('工具库')program  .command('jsliang')  .description('jsliang 帮忙指令')  .action(() => {    common();  });program.parse(process.argv);
src/base/config.ts
/** * @name 默认的全局配置 * @time 2021-05-22 16:12:21 */import path from 'path';// 根底目录export const BASE_PATH = path.join(__dirname, './docs');// 疏忽目录export const IGNORE_PATH = [  '.vscode',  'node_modules',];
src/common/sortCatalog.ts
/** * @name 文件排序功能 * @time 2021-05-22 16:08:06 * @description 规定   1. 零碎程序 1/10/2/21/3,心愿排序 1/2/3/10/21   2. 插入文件 1/2/1-1,心愿排序 1/2/3(将 1-1 变成 2,2 变成 3)*/import fs from 'fs';import path from 'path';import { IGNORE_PATH } from '../base/config';const recursion = (filePath: string, level = 0) => {  const files = fs.readdirSync(filePath);  files    .filter((item => !IGNORE_PATH.includes(item))) // 过滤疏忽文件/文件夹    .sort((a, b) =>      Number((a.split('.')[0]).replace('-', '.'))      - Number((b.split('.')[0]).replace('-', '.'))    ) // 排序文件夹    .forEach((item, index) => { // 遍历文件夹      // 设置旧文件名称和新文件名称      const oldFileName = item;      const newFileName = `${index + 1}.${oldFileName.slice(oldFileName.indexOf('.') + 1)}`;      // 设置旧文件门路和新文件门路      const oldPath = `${filePath}/${oldFileName}`;      const newPath = `${filePath}/${newFileName}`;      // 判断文件格式      const stat = fs.statSync(oldPath);      // 判断是文件夹还是文件      if (stat.isFile()) {        fs.renameSync(oldPath, newPath); // 重命名文件      } else if (stat.isDirectory()) {        fs.renameSync(oldPath, newPath); // 重命名文件夹        recursion(newPath, level + 1); // 递归文件夹      }    });};export const sortCatalog = (filePath: string): boolean => {  // 绝对路径  if (path.isAbsolute(filePath)) {    recursion(filePath);  } else { // 相对路径    recursion(path.join(__dirname, filePath));  }  return true;};

那么,Inquirer.js 接入就搞定了,试试咱们的 npm run jsliang,能够失常应用!

前面能够欢快写性能啦~

五 参考文献

  • GitHub:SBoudrias/Inquirer.js
  • GitHub:rxjs
  • RxJS 中文文档
  • CSDN:inquirer.js —— 一个用户与命令行交互的工具

jsliang 的文档库由 梁峻荣 采纳 常识共享 署名-非商业性应用-雷同形式共享 4.0 国内 许可协定 进行许可。<br/>基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/>本许可协定受权之外的应用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处取得。