脚手架开发之终端显示及状态

136 阅读7分钟

之前我们了解了脚手架的基本实现步骤,以及如何解析命令行参数,下面来看看如何让你的终端更丰富。

以下库都是实现了ANSI转义序列 - 维基百科,自由的百科全书提供的标准,用于控制文本在终端上的光标位置、颜色和其他选项。

  • chalk 用于让终端文字显示各种样式(字体样式,颜色,背景等等)
  • ora 实现终端loading效果。

首先我们先来初体验原生ansi

//  \x1b[图形在线码值\x1b[图形在线码值 + 操作代码 + 字符串内容

console.log("\x1b[4m\x1b[47m%s\x1b[0m", "ansi") // 输出带下划线白色背景的文字
console.log("\x1b[4F%s", "ansi2") // 光标上移一行显示

image.png

chalk

  • ANSI转义码封装:自动处理终端兼容性
  • 链式调用chalk.red.bold.underline('警告')
  • 16/256色支持chalk.rgb(255,0,0)('自定义红')
// 颜色组合
console.log(chalk.hex('#FF8800').bgBlack('黑色背景'));

// 样式叠加
const errorMsg = chalk.red.inverse.bold('[ERROR]');

// 多种类型颜色,只有最后一个生效
`chalk.red.yellow.green` 等价于 `chalk.green`

控制台精度条

import chalk from 'chalk';

function createProgressBar(total) {
  let current = 0;
  
  return {
    update() {
      current++;
      const percent = (current / total * 100).toFixed(1);
      if(current > total) return
      const progress = chalk.green('■'.repeat(current)) 
        + chalk.gray('□'.repeat(total - current));
      process.stdout.write(`\r${progress} ${chalk.yellow(percent)}%`);
    }
  };
}


const bar = createProgressBar(20);
const timer = setInterval(() => {
  bar.update();
  if(bar.current >= 20) clearInterval(timer);
}, 100);

image.png

如果想要使用原生ansi开发控制台文件样式,我们可以查看chalk提供的样式函数对应的ansi码值,快速实现我们的效果。 image.png

ora

在终端进行loading加载动画。

const spinner = ora('正在加载数据...').start();
setTimeout(() => spinner.stop(), 2000); //  正在加载数据直接消失
setTimeout(() => spinner.succeed('加载完成'), 2000); // 覆盖正在加载数据展示

// spinner.color 设置加载图标颜色
// spinner.text 修改加载文本
// spinner.prefixText 添加前置文本
// spinner.suffixText 添加后置文本
// spinner.suffixText 添加后置文本

image.png

还可以配置异步任务来进行处理,不需要我们处理ora的开始和结束,直接根据异步任务的结果即可更新loading状态。

async function init() {
  const promise = new Promise((resolve, reject) => {
    setTimeout(resolve, 2000);
  });
  await oraPromise(promise, {
    failText: "失败了...",
    successText: "成功了...",
    text: "正在执行异步任务中..."
  })
}

init()

我们还可以通过spinner调整loading的样式,提供的值可以在这里进行查找。 我们也可以自己去指定。

spinner:{
    interval: 80, // 毫秒
    frames: ["z", "h", "a", "n", "g"] // 指定interval时间段循环展示给定的字符
}

进度反馈机制

let progress = 0;
const spinner = ora({
  text: '下载进度: 0%',
  color: 'magenta'
}).start();

const updateInterval = setInterval(() => {
  progress += 10;
  spinner.text = `下载进度: ${chalk.green(progress)}%`;
  
  if(progress >= 100) {
    clearInterval(updateInterval);
    spinner.succeed(chalk.bold('下载完成!'));
  }
}, 500);

inquirer

node命令行交互工具。  由于一些命令,参数过程,用户难以记忆和配置。所以我们可以使用问答的方式,让用户选择,然后进行生成配置文件。例如vite等。

image.png

  • 灵活的cli交互方式(提供很多的api)。
  • 抹平平台间的差异。

现在我们来看下大致的使用方式

  • type 提供了inputnumberconfirmlistrawlistexpandcheckboxpasswordeditor(windows直接打开记事本)问答方式。
  • name 当前问答保存的键名。
  • message 问答内容。
  • default 问答默认值,如果是list, rawlist, expand, checkbox 其值应该是条目索引。
  • choices 列表问答的选项
  • validate 验证用户输入的内容,如果不通过将会给出提示。可直接返回提示信息。 image.png
  • filter 过滤用户输入的内容。会直接修改answers中对应的属性的。
  • transformer 修改用户输入的内容(起美化输出的作用),仅影响编辑时显示的内容。它不会修改 answers 哈希
  • when 当前问题是否被询问。
  • pageSize 使用 listrawListexpand 或 checkbox 时将呈现的行数。
  • prefix 更改问答前缀
  • suffix 更改问答后缀
  • loop 列表选择是否可以通过上下键循环切换。首尾项是否可以通过上下键切换。
  • askAnswered 如果答案已存在,则强制提示问题。
  • waitUserInput 启用/禁用在打开系统编辑器之前等待用户输入的标志。默认值为true
inquirer.prompt([
    {
      type: 'input',
      name: 'name',
      message: '姓名:',
      validate: input =>  input ? true : "姓名不能为空"
    },
    {
      type: 'number',
      name: 'age',
      message: '年龄:',
      min: 18,
      max: 120,
      default: 25
    },
    {
      type: 'confirm',
      name: 'sex',
      message: '是否男生?',
    },
    {
      type: 'list',
      name: 'framework',
      message: '选择框架:',
      choices: [
        { name: 'React', value: 'react' },
        { name: 'Vue', value: 'vue' },
        { name: 'Angular', value: 'angular' },
        new inquirer.Separator(), // 分隔线
        { name: '其他', value: 'other' }
      ],
      loop: false,
      default: 'react' // 可以是value值,也可以是选项条目索引
    },
    // 与 list 类似,但用户输入数字选择
    {
      type: 'rawlist',
      name: 'theme',
      message: '选择主题:',
      choices: ['明亮', '暗黑', '系统默认']
    },
    {
      type: 'expand',
      name: 'action',
      message: '请选择操作:',
      choices: [
        { key: 'a', name: '添加用户', value: 'add' },
        { key: 'd', name: '删除用户', value: 'delete' },
        { key: 'e', name: '编辑用户', value: 'edit' },
        new inquirer.Separator(),
        { key: 'x', name: '退出', value: 'exit' }
      ]
    },
    {
      type: 'checkbox',
      name: 'tools',
      message: '选择开发工具:',
      choices: [
        { name: 'ESLint', value: 'eslint', checked: true },
        { name: 'Prettier', value: 'prettier' },
        { name: 'TypeScript', value: 'ts', disabled: '已集成' },
        { name: 'Jest', value: 'jest' }
      ],
      validate: choices => choices.length > 0 || '至少选择一项'
    },
    {
      type: 'password',
      name: 'password',
      message: '设置密码:',
      mask: '🔒',
      validate: input => input.length >= 8 || '密码至少8位'
    },
    {
      type: 'editor',
      name: 'description',
      message: '项目描述:',
      postfix: '.md',
      default: '# 项目描述\n\n请在此输入...'
    }
]).then(answers => {
  console.log('你的选择:', answers);
});

image.png

往期年度总结

往期文章

专栏文章

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏✍️评论,    支持一下博主~

公众号:全栈追逐者,不定期的更新内容,关注不错过哦!