Skip to content

chalk和ora源码重点知识

chalk

知识点1:自定义 imports 引用

在 package.json 中定义:

json
{
  "imports": {
    "#ansi-styles": "./source/vendor/ansi-styles/index.js",
    "#supports-color": {
      "node": "./source/vendor/supports-color/index.js",
      "default": "./source/vendor/supports-color/browser.js"
    }
  }
}

应用:

js
import ansiStyles from '#ansi-styles';
import supportsColor from '#supports-color';

知识点2:批量生成方法和构造者模式应用

根据 ansiStyles 配置批量生成构造者方法:

js
for (const [styleName, style] of Object.entries(ansiStyles)) {
  styles[styleName] = {
    get() {
      const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
      Object.defineProperty(this, styleName, { value: builder });
      return builder;
    },
  };
}

通过 createBuilder 生成构造者对象:

js
const createBuilder = (self, _styler, _isEmpty) => {
  const builder = (...arguments_) => applyStyle(builder, (arguments_.length === 1) ? ('' + arguments_[0]) : arguments_.join(' '));

  Object.setPrototypeOf(builder, proto);

  builder[GENERATOR] = self;
  builder[STYLER] = _styler;
  builder[IS_EMPTY] = _isEmpty;

  return builder;
};

知识点3:在对象的原型(prototype)上新增属性

js
Object.defineProperties(createChalk.prototype, styles);

知识点4:使用工厂模块快速生成 chalk 实例

定义:

js
function createChalk(options) {
  return chalkFactory(options);
}

应用:

js
const chalk = createChalk();

export default chalk;

知识点5:替换对象的原型

js
const chalkFactory = options => {
  const chalk = (...strings) => strings.join(' ');
  applyOptions(chalk, options);

  Object.setPrototypeOf(chalk, createChalk.prototype);

  return chalk;
};

知识点6:向\n两侧注入转义字符

js
export function stringEncaseCRLFWithFirstIndex(
  string, // 带\n的初始字符串
  prefix, // 闭合字符串,\n左侧注入
  postfix, // 开启字符串,\n右侧注入
  index // 第一个\n位置序号
) {
  let endIndex = 0;
  let returnValue = '';
  do {
    // 是否存在\r
    const gotCR = string[index - 1] === '\r';
    // 向\n两侧注入转移字符
    returnValue += string.substr(endIndex, (gotCR ? index - 1 : index) - endIndex) + prefix + (gotCR ? '\r\n' : '\n') + postfix;
    // 获取\n后面一个字符的位置
    endIndex = index + 1;
    // 获取下一个\n的序号
    index = string.indexOf('\n', endIndex);
  } while (index !== -1); // 如果存在\n则继续循环
  // 获取\n后面的字符串进行拼接
  returnValue += string.slice(endIndex);
  return returnValue;
}

代码演示

js
import chalk, { Chalk } from "chalk";

// 简单调用
console.log('chalk--->', chalk.red('hello chalk'))

// 混合调用
console.log('chalk--->', `${chalk.red('hello chalk')}!${chalk.green('chalk learn')}`)

// 链式调用
console.log('chalk--->', chalk.red.bgBlue.bold('hello chalk'))

// 多个参数
console.log('chalk--->', chalk.red('hello chalk', 'learn'))

// 嵌套的调用
console.log('chalk--->', chalk.red('hello chalk', chalk.underline('learn')))

// 超越256的色值进行定义
console.log('chalk--->', chalk.rgb(255, 255, 0).underline('hello chalk'))
console.log('chalk--->', chalk.hex('#ff0000').bold('hello chalk'))
console.log('chalk--->', chalk.hex('#ff0000')('hello chalk'))

// 场景一:日志的打印
const error = (...text) => console.log(chalk.bold.hex('ff0000')(text))
const warning = (...text) => console.log(chalk.bold.hex('ffa500')(text))

error('Error')
warning('Warning')

// 自定义Chalk类
const customChalk = new Chalk({ level: 0 })
console.log(customChalk.red('hello chalk'))

// 链式调用
class Test {
    one() {
        console.log(1)
        return this;
    }
    two() {
        console.log(2)
        return this;
    }
}

function createBuilder () {
    return new Test()
}

const builder = createBuilder()

builder.one().two()

ora

知识点7:Class私有属性

js
class Test {
  #name = 'sam';

  #getName() {
    return this.#name;
  }
}

const t = new Test();
console.log(t.#getName()); // error
console.log(t.#name); // error

知识点8:输入流缓冲

js
import readline from 'node:readline';
import { BufferListStream } from 'bl';

#mutedStream = new BufferListStream();
this.#mutedStream.pipe(process.stdout);

this.#rl = readline.createInterface({
  input: process.stdin,
  output: this.#mutedStream,
});

知识点9:命令行光标隐藏和显示

js
console.log('\u001B[?25l'); // 光标隐藏
console.log('\u001B[?25h'); // 光标显示

import cliCursor from 'cli-cursor';
cliCursor.show(stream);
cliCursor.hide(stream);

知识点10:命令行清屏操作

js
this.#stream.cursorTo(0); // 光标移动到初始位置

for (let index = 0; index < this.#linesToClear; index++) {
  if (index > 0) {
    this.#stream.moveCursor(0, -1); // 如果清除行数大于1,光标上移一行
  }

  this.#stream.clearLine(1); // 清除一行
}

知识点11:打印成功字符

使用log-symbols获取成功字符

js
import logSymbols from 'log-symbols';

console.log(logSymbols.success); // ✔

代码演示

js
import ora, { oraPromise } from 'ora'

const spinner = ora().start();

spinner.color = 'red'
spinner.text = 'Reading...';

setTimeout(() => {
    spinner.stop()
}, 3000);

// oraPromise 支持异步任务
(async function () {
    const promise = new Promise(resolve => {
        console.log('doing something...');
        setTimeout(() => {
            resolve();
        }, 3000)
    });
    await oraPromise(promise, {
        successText: 'success',
        failText: 'failed',
        prefixText: 'Download chalk'
    })
})()

手动实现ora

js
import cliSpinners from 'cli-spinners'  // https://www.npmjs.com/package/cli-spinners
import cliCursor from 'cli-cursor'; // https://www.npmjs.com/package/cli-cursor
import { BufferListStream } from 'bl'; // https://www.npmjs.com/package/bl
import * as readline from 'node:readline'; // https://nodejs.org/api/readline.html#readline

const spinners = cliSpinners.dots2; // 组件库
const text = 'loading' // 默认文本
const stream = process.stderr; // 输出流
let frameIndex = 0; // 当前帧
const frames = spinners.frames; // 每一帧的内容
const interval = spinners.interval; // 每一帧的间隔
const mutedStream = new BufferListStream();
mutedStream.pipe(process.stdout);
const rl = readline.createInterface({
    input: process.stdin,
    output: mutedStream
});

function clear() {
    stream.cursorTo(0);
    stream.clearLine(1);
}

function render() {
    clear()
    const readerText = frames[frameIndex] + ' ' + text;
    stream.write(readerText);
    frameIndex = ++frameIndex % frames.length;
}

let i = setInterval(render, interval)

function stop() {
    clearInterval(i);
    i = undefined;
    clear();
    frameIndex = 0;
    rl.close()
}
cliCursor.hide(stream);

setTimeout(() => {
    stop();
}, 3000)

Released under the MIT License.