[搭建CLI效率工具] Rollup + TypeScript 搭建CLI工程

环境搭建

工欲善其事必先利其器,使用Rollup搭建Typescript开发环境。毕竟Typescript是大势所趋并且Rollup相比较webpack对于node模块来说比较优化。

创建项目目录并初始化

mkdir <projectName> && cd <projectName> && yarn init -y

创建基础目录结构

├── bin           #可执行文件目录
│   └── index.js
├── lib           #编译后产物
└──  package.json

bin/index.js

#!/usr/bin/env node
console.log('hello cli')

package.json

{
  "name": "wz-jenkins",
  "version": "1.0.0",
  "description": "terminal call jenkins",
  "main": "lib/index.js",
  "bin": "bin/index.js",
  "repository": "git@github.com:a20070322/wz-jenkins.git",
  "author": "ZhaoZhongYang <z1031839775@gmail.com>",
  "license": "MIT"
}

测试

# 将npm模块链接至全局
npm link 

# 执行命令 package.json中的name
wz-jenkins

# 输出 hello cli

[搭建CLI效率工具] Rollup + TypeScript 搭建CLI工程

搭建rollup环境

为了方便解释包的作用,此处分开下载依赖

# rollup打包工具
yarn add rollup -D

# rollup文件夹清除插件
yarn add rollup-plugin-cleandir -D

# rollup解析及编译TS插件
yarn add @rollup/plugin-typescript -D

# 将json转换为ES6模块
yarn add @rollup/plugin-json -D

# 解析代码中依赖的node_modules
yarn add @rollup/plugin-node-resolve -D

# 
yarn add @rollup/plugin-commonjs 

# 下载ts模块
yarn add typescript -D

# 下载eslist 
yarn add eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser -D

.eslintrc.js
module.exports = {
    parser: '@typescript-eslint/parser',
    extends: [
        'plugin:@typescript-eslint/recommended'
    ],
    parserOptions: {
        project: './tsconfig.json',
        tsconfigRootDir: __dirname,
        ecmaVersion: 2020,
        sourceType: 'module',
        createDefaultProgram: true,
    },
    rules: {

    }
}
tsconfig.json
{
  "compilerOptions": {
    "rootDir": "./",
    "types": ["node"],
    "target": "ESNext",
    "module": "CommonJS",
    "declaration": true,
    "outDir": "lib",
    "strict": true,
    "esModuleInterop": true,
    "pretty": true,
    "resolveJsonModule": true,
    "typeRoots": ["./node_modules/@types/", "./src/typings/"]
  },
  "include": ["src", "types.d.ts"],
  "exclude": ["lib"]
}

rollup.config.js
/** 将json转换为ES6模块 */
const json = require("@rollup/plugin-json");

/** rollup解析及编译TS插件 */
const typescript = require("@rollup/plugin-typescript");

/** 解析代码中依赖的node_modules */
const resolve = require("@rollup/plugin-node-resolve");

/** 将 CommonJS 模块转换为 ES6 的 Rollup 插件 */
const commonjs = require("@rollup/plugin-commonjs");

/** rollup文件夹清除插件 */
const { cleandir } = require("rollup-plugin-cleandir");

module.exports = {
  /** 打包入口文件 */
  input: ["./src/index.ts", "./src/cli.ts"],
  /** 输出配置 */
  output: {
    /** 输出目录 */
    dir: "./lib",
    /** 输出文件为 CommonJS格式 */
    format: "cjs",
  },
  plugins: [
    /** 配置插件 - 每次打包清除目标文件 */
    cleandir("./lib"),
    /** 配置插件 - 将json转换为ES6模块 */
    json(),
    /** 配置插件 - 将json转换为ES6模块 */
    typescript({
      module: "esnext",
      exclude: ["./node_modules/**"],
    }),
    resolve.default({
      extensions: [".js", ".ts", ".json"],
      modulesOnly: true,
      preferredBuiltins: false,
    }),
    commonjs({ extensions: [".js", ".ts", ".json"] }),
  ],
};
package.json

增加了scripts命令

{
  "name": "wz-jenkins",
  "version": "1.0.0",
  "description": "terminal call jenkins",
  "main": "lib/index.js",
  "bin": "bin/index.js",
  "scripts": {
    "lint": "eslint 'src/**/*.{js,ts}' --fix",
    "dev": "rollup -w -c",
    "build": "rollup -c"
  },
  "repository": "git@github.com:a20070322/wz-jenkins.git",
  "author": "ZhaoZhongYang <z1031839775@gmail.com>",
  "license": "MIT",
  "devDependencies": {
    "@rollup/plugin-commonjs": "^21.0.1",
    "@rollup/plugin-json": "^4.1.0",
    "@rollup/plugin-node-resolve": "^13.1.3",
    "@rollup/plugin-typescript": "^8.3.0",
    "@typescript-eslint/eslint-plugin": "^5.10.0",
    "@typescript-eslint/parser": "^5.10.0",
    "eslint": "^8.7.0",
    "rollup": "^2.64.0",
    "rollup-plugin-cleandir": "^2.0.0",
    "typescript": "^4.5.4"
  },
  "dependencies": {
    "cac": "^6.7.12"
  }
}

目前工程目录如下

├── LICENSE
├── README.md
├── bin            # 可执行文件目录
│   └── index.js
├── lib            # 编译后产物
│   ├── cli.js
│   └── index.js
├── package.json   
├── rollup.config.js # rollup配置文件
├── src            # 源代码
│   ├── cli.ts     # cli相关函数通过此文件暴露
│   └── index.ts   # 暴露方法,其他项目通过包引用
├── tsconfig.json  # typescript配置文件
└── yarn.lock

代码示例01

命令行工具

下载 cac

yarn add cac

cac 基础使用

src/cli.ts


import cac from "cac";
import { name, version } from "../package.json";

const cli = cac(name);

/** cli命令数组 */
cli.commands = [
  /** 命令行 命令name , 命令描述 , 命令配置 */
  cli.command("", "执行jenkins脚本").action(() => {
    console.log("hello cli");
  }),
];

/** cli-帮助 */
cli.help();

/** cli-版本*/
cli.version(version);

/** 解析命令行参数 */
cli.parse();

/** 异常处理函数 */
const one rror = (err: Error): void => {
  console.error(`错误异常: ${err.message}`);
  console.log(err.stack);
  process.exit(1);
};

/** 监听 uncaughtException 异常 */
process.on("uncaughtException", one rror);

/** 监听 unhandledRejection 异常 */
process.on("unhandledRejection", one rror);

本地监听编译输出 yarn dev

检验成果(终端执行如下命令)

wz-jenkins 
# 输出 hello cli

wz-jenkins -v
# 输出 wz-jenkins/1.0.0 darwin-x64 node-v14.15.1

wz-jenkins -h 

# 输出
# wz-jenkins/1.0.0
# Usage:
# $ wz-jenkins 
# Commands:
#    执行jenkins脚本
# For more info, run any command with the `--help` flag:
#   $ wz-jenkins --help
# Options:
#   -h, --help     Display this message 
#   -v, --version  Display version number

chalk (命令行色彩)

# 下载 chalk
yarn add chalk@4.*

目前只能用4.x版本,更高的版本使用的时候会报错。有更好的解决办法可以评论或私信

src/cli.ts

// 伪代码

// 引入chalk
import chalk from "chalk";

/** cli命令数组 */
cli.commands = [
  /** 增加一个 command */
  cli.command("chalk", "test console color").action(() => {
    console.log(chalk.blue("Hello cli!"));
    console.log(chalk.blue("Hello") + " World" + chalk.red("!"));
    console.log(chalk.blue.bgRed.bold("Hello world!"));
    console.log(chalk.blue("Hello", "World!", "Foo", "bar", "biz", "baz"));
    console.log(chalk.red("Hello", chalk.underline.bgBlue("world") + "!"));
    console.log(
      chalk.green(
        "I am a green line " +
          chalk.blue.underline.bold("https://github.com/") +
          " that becomes green again!"
      )
    );
    console.log(`
      CPU: ${chalk.red("90%")}
      RAM: ${chalk.green("40%")}
      DISK: ${chalk.yellow("70%")}
    `);
    console.log(chalk.rgb(123, 45, 67).underline("Underlined reddish color"));
    console.log(chalk.hex("#DEADED").bold("Bold gray!"));

    const error = chalk.bold.red;
    const warning = chalk.hex("#FFA500");
    console.log(error("Error!"));
    console.log(warning("Warning!"));
    const name = "Sindre";
    console.log(chalk.green("Hello %s"), name);
  }),
];

终端执行命令 wz-jenkins chalk

[搭建CLI效率工具] Rollup + TypeScript 搭建CLI工程

ora (命令行loading)

# 下载 ora
yarn add ora@5.* 

# 下载 logSymbols
yarn add logSymbols

目前只能用5.x版本,更高的版本编译时候会报错,有更好的解决办法可以评论或私信

src/cli.ts

// 伪代码

// 引入ora
import ora from "ora";
// 引入logSymbols
import logSymbols from "log-symbols";
/** cli命令数组 */
cli.commands = [
  cli.command("ora", "test console color").action(async () => {
    /**
     * 模拟耗时任务
     * @param timer 定时器参数
     * @returns
     */
    const sleep = (timer = 1000) =>
      new Promise<void>((resolve) => {
        setTimeout(() => resolve(), timer);
      });
    /** 开启任务1 loading状态 */
    const spinnerTask1 = ora("run mock task one").start();
    await sleep();
    /** 结束任务1 并且保留内容且改为成功标识 */
    spinnerTask1.stopAndPersist({
      prefixText: logSymbols.success,
    });
    /** 开启任务2 loading状态 */
    const spinnerTask2 = ora("run mock task two").start();
    await sleep();
    /** 结束任务2 并且保留内容且改为错误标识 */
    spinnerTask2.stopAndPersist({
      prefixText: logSymbols.error,
    });
    /** 开启任务3 loading状态 */
    const spinnerTask3 = ora("run mock task three").start();
    await sleep();
    /** 结束任务3 清除浏览器输出 */
    spinnerTask3.stop();
  }),
];

终端执行命令 wz-jenkins ora

[搭建CLI效率工具] Rollup + TypeScript 搭建CLI工程

cac传送门 | chalk传送门 |
ora传送门 |
logSymbols传送门 | 代码传送门

控制台输出函数封装

src/utils/log.ts

import chalk from "chalk";
import logSymbols from "log-symbols";
import ora from "ora";

/** 获取堆栈信息 */
const getStackTrace = function () {
  const obj: Error = { name: "", message: "" };
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  Error.captureStackTrace(obj, getStackTrace);
  return obj.stack || "";
};

/** 控制台输出封装 */
export const Log = {
  /** info */
  info(...args: unknown[]) {
    console.log(chalk.cyan("Info:"), chalk.cyan(...args));
  },
  /** warn */
  warn(...args: unknown[]) {
    console.log(chalk.yellow("Warn:"), chalk.yellow(...args));
  },
  /** error */
  error(...args: unknown[]) {
    console.log(chalk.red("Error:"), chalk.red(...args));
    const stack = getStackTrace() || "";
    const matchResult = stack.match(/\(.*?\)/g) || [];
    const line = matchResult[1] || "";
    console.log(`${chalk.gray("Error stack:")} ${chalk.gray(line)}`);
  },
  /**
   * loadingPromise
   * @param msg 值
   * @param fn 异步函数
   * @returns
   */
  async loadingPromise<T>(msg: string, fn: () => Promise<T>) {
    const spinner = ora(chalk.cyan(`Loading ${msg}`)).start();
    try {
      const result = await fn();
      spinner.stopAndPersist({
        prefixText: logSymbols.success,
        text: chalk.green(`Success ${msg}`),
      });
      return result;
    } catch (error) {
      spinner.color = "red";
      spinner.stopAndPersist({
        prefixText: logSymbols.error,
        text: chalk.red(`Error ${msg}`),
      });
      throw error;
    }
  },
};

效果如下

[搭建CLI效率工具] Rollup + TypeScript 搭建CLI工程

ps 后续持续更新

上一篇:TypeScript学习: 十二、TS中的装饰器


下一篇:关于TypeScript的声明文件的总结