从0-1逐步搭建一个前端脚手架工具并发布到npm

前言

本文介绍的案例已同步到github,github地址。

vue-cli 和 create-react-app 等 cli 脚手架工具用于快速搭建应用,无需手动配置复杂的构建环境。本文介绍如何使用 rollup 搭建一个脚手架工具。

脚手架工具的工作流程简言为:提供远端仓库各种模版 => 用户通过命令选择模版 => 拉取仓库代码

分别对应如下几个重要模块:

  • 配置 打包命令
  • 配置 命令行交互,如 create、-v 等,其中 create 是核心逻辑,用于根据用户的选择拉取远程仓库中相应的初始代码。
  • 发布至 npm

1. 初始化项目

初始化项目

复制代码
npm init -y

生成:typescript 配置文件 tsconfig.json,在此之前需要确保全局安装了 TypeScript

复制代码
npm install -g typescript // 如果已经安装,无需理会
npx tsc --init

package.json 中添加依赖

复制代码
"devDependencies": {
  "@inquirer/prompts": "^3.2.0",
  "@rollup/plugin-commonjs": "^25.0.3", 
  "@rollup/plugin-json": "^6.0.1", 
  "@rollup/plugin-node-resolve": "^15.1.0", 
  "@rollup/plugin-terser": "^0.4.3", 
  "@types/fs-extra": "^11.0.2",
  "@types/lodash": "^4.14.199",
  "@types/node": "^16.18.40",
  "axios": "^1.5.0",
  "chalk": "^4.1.2",
  "commander": "^11.0.0",
  "figlet": "^1.8.0",
  "fs-extra": "^11.1.1",
  "lodash": "^4.17.21",
  "log-symbols": "^4.1.0",
  "ora": "5",
  "progress-estimator": "^0.3.1",
  "pure-thin-cli": "^0.1.8",
  "rollup": "^4.6.1",
  "rollup-plugin-dts": "^5.3.0", 
  "rollup-plugin-esbuild": "^5.0.0",
  "rollup-plugin-node-externals": "^5.1.2", 
  "rollup-plugin-typescript2": "^0.36.0", 
  "simple-git": "^3.19.1",
  "tslib": "^2.6.1",
  "typescript": "^5.2.2"
}

本文用到的所有依赖说明:下文中也会一一介绍依赖的用处与用法

复制代码
"devDependencies": {
  // 用于命令行交互。
  "@inquirer/prompts": "^3.2.0",
  // Rollup 相关的插件,用于模块打包
  "@rollup/plugin-commonjs": "^25.0.3", // 支持rollup打包commonjs模块
  "@rollup/plugin-json": "^6.0.1", // 支持rollup打包json文件
  "@rollup/plugin-node-resolve": "^15.1.0", // 用于帮助 Rollup 解析和处理 Node.js 模块(Node.js 的 CommonJS 模块规范)
  "@rollup/plugin-terser": "^0.4.3", // Rollup 构建过程中对生成的 JavaScript 代码进行压缩和混淆,以减小最终输出文件的体积。
  // TypeScript 的类型定义文件
  "@types/fs-extra": "^11.0.2",
  "@types/lodash": "^4.14.199",
  "@types/node": "^16.18.40",
   // 用于发起 HTTP 请求。 
  "axios": "^1.5.0",
  // 在命令行中输出彩色文本。
  "chalk": "^4.1.2",
  // 命令行界面的解决方案  
  "commander": "^11.0.0",
  // 优化打印效果
  "figlet": "^1.8.0",
  // 扩展了标准 fs 模块的文件系统操作
  "fs-extra": "^11.1.1",
  // 一个提供实用函数的 JavaScript 库。  
  "lodash": "^4.17.21",
  // 在命令行中显示日志符号。  
  "log-symbols": "^4.1.0",
  // 创建可旋转的加载器  
  "ora": "5",
   // 估算操作进度。 
  "progress-estimator": "^0.3.1",
  // 一个特定于项目或定制的 CLI 工具  
  "pure-thin-cli": "^0.1.8",
    
  "rollup": "^4.6.1",
  "rollup-plugin-dts": "^5.3.0", // 是一个 Rollup 插件,它的主要作用是处理 TypeScript 的声明文件(.d.ts 文件)
  "rollup-plugin-esbuild": "^5.0.0",
  "rollup-plugin-node-externals": "^5.1.2", // 使rollup自动识别外部依赖
  "rollup-plugin-typescript2": "^0.36.0", // 支持rollup打包ts文件
    
  // 用于 Git 命令的 Node.js 封装。  
  "simple-git": "^3.19.1",
  // TypeScript 运行时库。  
  "tslib": "^2.6.1",
  "typescript": "^5.2.2"
},

目录结构如下,index.js:命令入口文件;command:命令逻辑;utils:公共方法

2. 配置打包命令

下载依赖

复制代码
pnpm add -D rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-json rollup-plugin-typescript2 @rollup/plugin-terser rollup-plugin-node-externals

依赖说明:

  • rollup:打包工具,有很多选择,如webpack
  • @rollup/plugin-node-resolve:支持rollup打包node.js模块
  • @rollup/plugin-commonjs:支持rollup打包commonjs模块
  • @rollup/plugin-json:支持rollup打包json文件
  • rollup-plugin-typescript2:支持rollup打包ts文件
  • @rollup/plugin-terser:压缩打包代码
  • rollup-plugin-node-externals:使rollup自动识别外部依赖

根目录下新建 rollup.config.js

复制代码
import { defineConfig } from 'rollup';
import nodeResolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import externals from "rollup-plugin-node-externals";
import json from "@rollup/plugin-json";
import terser from "@rollup/plugin-terser";
import typescript from 'rollup-plugin-typescript2';

export default defineConfig([
  {
    input: {
      index: 'src/index.ts', // 打包入口文件
    },
    output: [
      {
        dir: 'dist', // 输出目标文件夹
        format: 'cjs', // 输出 commonjs 文件
      }
    ],
    // 这些依赖的作用上文提到过
    plugins: [
      nodeResolve(),
      externals({
        devDeps: false, // 可以识别我们 package.json 中的依赖当作外部依赖处理 不会直接将其中引用的方法打包出来
      }),
      typescript(),
      json(),
      commonjs(),
      terser(),
    ],
  },
]);

在 package.json 中配置打包命令,-c 指定 rollup 配置文件,--bundleConfigAsCjs 将配置转为 commonjs 执行。

复制代码
{
  ......
  "scripts": {
	......
    "build": "rollup -c rollup.config.js --bundleConfigAsCjs"
  },
}

index.ts 中加入一定代码 后,执行 npm run build 测试打包结果

发现如下报错,这是因为默认生成的 tsconfig.json 中 moudle 为 commonjs,改为 'module: "ES2015"', 'module: "ES2020"', 'module: "ES2022"', or 'module: "ESNext"' 后即可。

再次执行 npm run build,打包配置完毕

3. 命令行交互配置依赖介绍

查看 这里是代码015 cli 的源码,发现其使用了如下依赖,我们也选择一部分安装

本文使用了如下依赖:

  • commander:解析命令行指令
  • ora:终端加载动画
  • progress-estimator:终端加载条动画
  • log-symbols:终端输出符号
  • chalk:终端字体美化
  • @inquirer/prompts:终端输入交互

其中最重要的是 commander,下载:pnpm install commander -D,用于解析用户在命令行输入的指令,可以到官方文档查看基本使用,并应用到 src/index.ts 中。

4. -v --version指令配置

src/index.ts 中尝试导入 Command 和 version 遇到如下报错

根据提示,将 tsconfig.json 中默认注释的 moduleResolution 放开即可,发现导入 package.json 仍报错,将 resolveJsonModule 取消注释。

继续完善 index.ts,自定义指令名称为 benchu ,和 vite、vue-cli 一样就是一个命令名,添加 -v 或 --version 返回版本号,版本号为 package.json 中的 version 字段,该字段会在每次提交上传至 npm 时强制更新。

复制代码
import { Command } from "commander"
import { version } from "../package.json"

const program = new Command("benchu")
program.version(version, "-v, --version", "获取版本号")

program.parse()

打包后测试自定义命令 benchu,尝试查看版本与帮助说明

5. create 指令配置

声明创建命令,如使用 vue-cli 创建项目时输入的 vue create,可以让用户选择下载预设的模板 ,我们也命名一个 create 指令。

5.1 让用户输入项目名称 并 选择初始模版

  • 在 src/command/create.ts 文件下编写 create 命令核心代码
  • 导出一个可以传入项目名称的方法,如果用户直接传入了项目名称则让用户选择模板,否则需要先让用户输入项目名称

create 后可以紧跟参数 name,代表项目名称,该参数为可选参数,可以只执行 create,后续步骤中要求用户输入项目名。

复制代码
import { Command } from "commander"
import { version } from "../package.json"

const program = new Command("benchu")
program.version(version, "-v, --version", "版本号")

// create 指令
program
  .command("create")
  .description("初始化新项目")
  .argument("[name]", "项目名称") // "[name]"表示可选参数,"name"表示必填参数
  .action((dirName) => {
    console.log("init", dirName)
  })

program.parse()

重新打包,执行 npm run build 后,运行 dist/index.js 查看 command 此时已经可以看到 create 命令且正确拿到了 dirName

接下来处理 create 核心逻辑部分,首先在 src/index.ts 中将 dirName 交予 create

command/create.ts,首先需要确认预设模版 与对应远端仓库的关系,如本文连接到本人 gitee 的某个仓库,其中不同分支对应不同模版,可以供用户选择:

  • master:vite+ts+vue3+axios+pinia
  • element:vite+ts+vue3+axios+pinia+element+tailwind
  • element_layout:vite+ts+vue3+axios+pinia+element+tailwind+搭建完毕layout

@inquirer/prompts ,可以帮助我们让用户在终端进行输入或选择的操作,本文使用到了 input 和 select,更多使用方法可查阅官方文档:inquirer.js。

select 要求数据格式如下:

复制代码
import { select, input } from "@inquirer/prompts"
export interface TemplateInfo {
  name: string // 模板名称
  downloadUrl: string // 模板下载地址
  description: string // 模板描述
  branch: string // 模板分支
}

export const templates: Map<string, TemplateInfo> = new Map([
  [
    "Vite-Vue3-TypeScript-template",
    {
      name: "Vite-Vue3-TypeScript-template",
      downloadUrl: "git@gitee.com:tian__shuai/template-vite5--vue3.git",
      description: "vite + vue3 + ts初始模版",
      branch: "master",
    },
  ],
  [
    "Vite-Vue3-TypeScript-ElementUI-template",
    {
      name: "Vite-Vue3-TypeScript-ElementUI-template",
      downloadUrl: "git@gitee.com:tian__shuai/template-vite5--vue3.git",
      description: "vite + vue3 + ts + elementplus 初始模版",
      branch: "element",
    },
  ],
  [
    "Vite-Vue3-TypeScript-ElementUI-layout-template",
    {
      name: "Vite-Vue3-TypeScript-ElementUI-layout-template",
      downloadUrl: "git@gitee.com:tian__shuai/template-vite5--vue3.git",
      description: "vite + vue3 + ts + elementplus + layout 初始模版",
      branch: "element_layout",
    },
  ],
])

export async function create(projectName?: string) {
  if (!projectName) {
    projectName = await input({ message: "请输入项目名称" })
  }
  
  // 初始化模版列表
  const templateList = Array.from(templates).map(
    (item: [string, TemplateInfo]) => {
      const [name, info] = item
      return {
        name,
        value: name,
        description: info.description,
      }
    }
  )
  // 选了哪个模版
  const templateName = await select({
    message: "请选择模版",
    choices: templateList,
  })
  // 选中模版的详情
  const info = templates.get(templateName)
  console.log(info)
  console.log("create", projectName)
}

测试是否可以获取用户选择的模版详情,执行 npm run build => node dist/index.js create

输入项目名称后,通过上下键选择需要的模版,最下方就是定义的 description

选择后,获取到该模版的 info

create 时如果传递参数 name,则不会触发 input,而是直接进入 select 选择模版

5.2 下载选择的模版

新建 src/utils/clone.ts,用于处理下载模版,使用 simple-git 拉取 git 仓库,progress-estimator 设置预估 git clone 的时间并展示进度条。

参考 simple-git官方文档 ,git clone 需要传入三个参数:

  • url:仓库地址
  • localPath:目标路径
  • options:分支信息


src/utils/clone.ts,接收这三个参数:

复制代码
import simpleGit from "simple-git"
export const clone = (url: string, projectName: string, options: string[]) => {}

将上一步 src/utils/create.ts 通过 select 拿到的模版 info 传入 src/utils/clone.ts 处理。

在项目根目录下创建 project 目录,用于存储下载的项目模版

完善 command/clone.ts

复制代码
import { simpleGit, SimpleGit, SimpleGitOptions } from "simple-git"

const getOptions: Partial<SimpleGitOptions> = {
  baseDir: `${process.cwd()}/project`, // 指定 simple-git 操作的目录,默认为 process.cwd() 表示当前目录,我这里设置为根目录下的 project 目录,方便查看克隆多个项目的效果
  binary: "git", // 指定 git 的二进制文件位置
  maxConcurrentProcesses: 6, // 最大并发进程数
  trimmed: false, // git 输出的结果是否自动去除前后多余的空白字符
}

export const clone = async (
  url: string,
  projectName: string,
  branchOptions: string[]
) => {
  const git: SimpleGit = simpleGit(getOptions)
  try {
    await git.clone(url, projectName, branchOptions)
    console.log()
    console.log("代码下载完成!")
    console.log("=====================================================")
    console.log("================= 欢迎使用 benchu-cli ===============")
    console.log("=====================================================")
    console.log()
    console.log(
      "======== pnpm install 安装依赖, pnpm run dev 运行项目 ======="
    )
  } catch (e) {
    console.log("clone error", e)
  }
}

优化下载时的样式,使用 progress-estimator 添加进度条,progress-estimator官方文档地址。

复制代码
import { simpleGit, SimpleGit, SimpleGitOptions } from "simple-git"
import createLogger from "progress-estimator"

// 初始化进度条
const logger = createLogger({
  spinner: {
    interval: 100, // 100毫秒刷新一次
    frames: ["?", "?", "?", "?"], // 进度条的样式,
  },
})

const getOptions: Partial<SimpleGitOptions> = {
  baseDir: `${process.cwd()}/project`, // 指定 simple-git 操作的目录,默认为 process.cwd() 表示当前目录
  binary: "git", // 指定 git 的二进制文件位置
  maxConcurrentProcesses: 6, // 最大并发进程数
  trimmed: false, // git 输出的结果是否自动去除前后多余的空白字符
}

export const clone = async (
  url: string,
  projectName: string,
  branchOptions: string[]
) => {
  const git: SimpleGit = simpleGit(getOptions)
  try {
    await logger(git.clone(url, projectName, branchOptions), "代码下载中...", {
      estimate: 5000, // 预计下载时间
    })
    console.log()
    console.log("代码下载完成!")
    console.log("=====================================================")
    console.log("================= 欢迎使用 benchu-cli ===============")
    console.log("=====================================================")
    console.log()
    console.log(
      "======== pnpm install 安装依赖, pnpm run dev 运行项目 ======="
    )
  } catch (e) {
    console.log("clone error", e)
  }
}

效果:

继续优化样式,使用 chalk 添加颜色,chalk官方文档。


src/command/clone.ts 完整代码

复制代码
import { simpleGit, SimpleGit, SimpleGitOptions } from "simple-git"
import createLogger from "progress-estimator"
import chalk from "chalk"

// 初始化进度条
const logger = createLogger({
  spinner: {
    interval: 100, // 100毫秒刷新一次
    frames: ["?", "?", "?", "?"].map((item) => chalk.blue(item)), // 进度条的样式,
  },
})

const getOptions: Partial<SimpleGitOptions> = {
  baseDir: `${process.cwd()}/project`, // 指定 simple-git 操作的目录,默认为 process.cwd() 表示当前目录
  binary: "git", // 指定 git 的二进制文件位置
  maxConcurrentProcesses: 6, // 最大并发进程数
  trimmed: false, // git 输出的结果是否自动去除前后多余的空白字符
}

export const clone = async (
  url: string,
  projectName: string,
  branchOptions: string[]
) => {
  const git: SimpleGit = simpleGit(getOptions)
  try {
    await logger(git.clone(url, projectName, branchOptions), "代码下载中...", {
      estimate: 5000, // 预计下载时间
    })
    console.log()
    console.log(chalk.green("代码下载完成!"))
    console.log("=====================================================")
    console.log("================= 欢迎使用 benchu-cli ===============")
    console.log("=====================================================")
    console.log()
    console.log(
      "======== pnpm install 安装依赖, pnpm run dev 运行项目 ======="
    )
  } catch (e) {
    console.log("clone error", e)
  }
}

5.3 项目名相同检查是否需要覆盖更新

command/create.ts 中加入如下代码,判断项目名是否重复,并询问用户是否需要覆盖,如果覆盖,删除原有项目并让用户重新选择模版下载。

复制代码
import path from "path"
import fs from "fs-extra"

// 省略其余代码 ...

// 是否覆盖同名项目
export function isOverwrite(projectName: string) {
  return select({
    message: "项目已存在,是否覆盖?",
    choices: [
      { name: "覆盖", value: true },
      { name: "不覆盖", value: false },
    ],
  })
}

export async function create(projectName?: string) {
  if (!projectName) {
    projectName = await input({ message: "请输入项目名称" })
  }
  
  // 判断是否覆盖同名项目
  const projectPath = path.resolve(`${process.cwd()}/project`, projectName) // 这里的路径保持和 clone.ts 中 simple-git 的 dirName 一致
  if (fs.existsSync(projectPath)) {
    const isRun = await isOverwrite(projectName)
    if (isRun) {
      await fs.remove(projectPath)
    } else {
      return
    }
  }

  const templateName = await select({
    message: "请选择模版",
    choices: templateList,
  })
  const info = templates.get(templateName)
	
  // 下载模版
  if (info) {
    clone(info.downloadUrl, projectName, ["-b", info.branch])
  }
}

选择覆盖后,原有的项目删除,选择新模版后重新下载

至此,一个功能极简的 脚手架工具 已经完成,下面需要考虑发布到 npm。

6. 发布至npm

发布前需要修改测试阶段创建的 src/project 目录,当时为了方便观察 create 效果,然而给用户使用时采用默认的 process.cwd() 即可。


6.1 README.md

需要添加 README.md 说明文档,分享一个在线生成图标网站,用于生成 npm 版本图标

也可以添加一些 icon,icon库地址

README.md :

6.2 完善package.json

6.2.1 bin

补全 package.json,其中 bin 配置了命令 benchu,这个命令会映射到项目中的 bin/index.js ,即输入 benchu 就等于执行 bin/index.js,相当于测试阶段打包后执行 node dist/index.js 或 npx benchu 。

在运行 benchu 命令时,实际上执行的脚本是 bin/index.js,需要确保这个文件具有正确的可执行权限,并在文件头部指定 Node.js 环境。

bin/index.js:

复制代码
#!/usr/bin/env node
require("../dist") // 执行编译后的文件 dist/index.js
6.2.2 files

files 字段在 package.json 中用于指定哪些文件或目录应包含在发布到 npm 注册表的包中,如果未设置 files 字段,npm 默认会包括除了 .gitignore 和 .npmignore 文件中忽略的内容外的所有文件。

我这里只将 bin、dist、README.md 发布到npm。

6.3 发包

确认打包完成

检查 npm 源,如果是 淘宝源 则需要改回 npm 源。

查看npm镜像源地址

复制代码
npm config get registry

我这里是淘宝镜像

切换到 npm 源

复制代码
npm config set registry https://registry.npmjs.org/

登录 npm

复制代码
npm login

发布

复制代码
npm publish

注意: 每次 publish 都会强制要求更新 package.json 中的 version,可以手动修改,也可以通过命令 npm version patch,执行此命令前需要保证 Git 工作目录中没有未提交的更改或未跟踪的文件。

发布完毕后,即可在 npm 看到该包

可以看到 package.json 中 files 字段

7. 测试发布的包

全局下载 benchu-cli

复制代码
npm install benchu-cli -g

执行命令

复制代码
benchu create

8. 检查 npm 包的版本,并提示更新

当 benchu-cli 更新后需要给用户提示

command/create.ts 中,在检查是否需要覆盖项目后 ,检查是否需要版本更新

安装 axios,get 获取 npm 包的详情

复制代码
pnpm install axios -D


// ... 忽略其余包
import { name, version } from "../../package.json"
import axios from 'axios'

// 获取 npm 包的最新版本
const getNpmInfo = async (name: string) => {
  const npmUrl = `https://registry.npmjs.org/${name}`
  let res = {}
  try {
    res = await axios.get(npmUrl)
  } catch (e) {
    console.log(e)
  }
  return res
}
const getNpmLatestVersion = async (name: string) => {
  const { data } = (await getNpmInfo(name)) as AxiosResponse
  console.info(data)
}

// 检查版本
const checkVersion = async (name: string, version: string) => {
  const lastestVersion = await getNpmLatestVersion(name)
}


export async function create(projectName?: string) {
  // 项目名是否为空、是否需要覆盖项目 的逻辑...

  // 检查版本更新
  await checkVersion(name, version)

  // 提供模版选择、克隆仓库 的逻辑...
}

npm run build 重新打包并执行 node dist/index.js create,可以看到 npm 包的详情,其中 dist-tags 字段记录了最新版本。

继续优化,使用 lodash 的 gt 比较版本号,并给出更新提示,我们也可以再定义一个命令 update 用于更新 benchu-cli。

复制代码
// 用于检查 npm 包的版本,是否需要更新 npm 包
import { name, version } from "../../package.json"
import { gt } from "lodash"
import axios, { AxiosResponse } from "axios"
import chalk from "chalk"

// 获取 npm 包的最新版本
const getNpmInfo = async (cliName: string) => {
  const npmUrl = `https://registry.npmjs.org/${cliName}`
  let res = {}
  try {
    res = await axios.get(npmUrl)
  } catch (e) {
    console.log(e)
  }
  return res
}
const getNpmLatestVersion = async (cliName: string) => {
  const { data } = (await getNpmInfo(cliName)) as AxiosResponse
  return data["dist-tags"].latest
}

// 检查版本
export const checkVersion = async () => {
  const lastestVersion = await getNpmLatestVersion(name)
  const needUpdate = gt(lastestVersion, version)
  if (needUpdate) {
    console.warn(
      `${chalk.greenBright(name)} 版本需要更新,当前版本:${chalk.blueBright(
        version
      )}, 最新版本:${chalk.blueBright(lastestVersion)}`
    )
    console.log(
      `可使用${chalk.yellow(
        "npm install benchu-cli@latest -g"
      )} 或 ${chalk.yellow("benchu update")}更新`
    )
  }
}

手动修改 package.json 中的版本,再次执行 create 测试

9. update命令配置

上一步中通过比较 本地 与 npm 中 benchu-cli 的最新版本,给出用户提示,让其更新,我们也可以新增 update 命令用于更新包。

src/index.ts 新增 update 命令

复制代码
import { Command } from "commander"
import { version } from "../package.json"
import { create } from "./command/create"
import { update } from "./command/update"

const program = new Command("benchu")
program.version(version, "-v, --version", "版本号")

// create 指令
program
  .command("create")
  .description("初始化新项目")
  .argument("[name]", "项目名称") // "[name]"表示可选参数,"name"表示必填参数
  .action((dirName) => {
    create(dirName) // 不考虑不传参的情况,统一交予 create 函数处理
  })

// update 指令
program
  .command("update")
  .description("更新 benchu-cli 工具")
  .action(async () => {
    await update()
  })

program.parse()

command/update.ts,通过 process 下载最新的包,child_process 是 Node.js 的核心模块之一,用于创建和管理子进程,以便运行系统命令、脚本或其他可执行文件。

复制代码
import process from "child_process"
import chalk from "chalk"
import ora from "ora"

// 进度条
const spinner = ora({
  text: "benchu-cli 正在更新",
  spinner: {
    interval: 100, // 100毫秒刷新一次
    frames: ["?", "?", "?", "?"].map((item) => chalk.blue(item)), // 进度条的样式
  },
})

export const update = () => {
  spinner.start() // 开始动画
  process.exec("npm install benchu-cli@latest -g", (error, stdout) => {
    if (error) {
      spinner.fail()
      console.log(chalk.red(error))
    } else {
      spinner.succeed()
      console.log(chalk.green("更新成功"))
    }
  })
}

npm run build => node dist/index.js update

10. 优化终端打印样式

至此功能已经完全实现,但是控制台打印样式太丑了

utils/log.ts,使用 log-symbols 封装 log,log-symbols官方地址。

下载

复制代码
// 优化终端打印样式
import logSymbol from "log-symbols"

const log = {
  success: (message: string) => {
    console.log(logSymbol.success, message)
  },
  error: (message: string) => {
    console.log(logSymbol.error, message)
  },
  info: (message: string) => {
    console.log(logSymbol.info, message)
  },
  warn: (message: string) => {
    console.log(logSymbol.warning, message)
  },
}

export default log

原先的 clone.ts

使用 log 优化 clone.ts 打印

复制代码
export const clone = async (
  url: string,
  projectName: string,
  branchOptions: string[]
) => {
  const git: SimpleGit = simpleGit(getOptions)
  try {
    await logger(git.clone(url, projectName, branchOptions), "代码下载中...", {
      estimate: 5000, // 预计下载时间
    })

    console.log()
    console.log(chalk.blackBright("======================================="))
    console.log(chalk.blackBright("========= 欢迎使用 benchu-cli ========="))
    console.log(chalk.blackBright("======================================="))
    console.log()

    log.success(`项目创建成功 ${chalk.blueBright(projectName)}`)
    log.success("执行以下命令启动项目")
    log.info(`cd ${chalk.blueBright(projectName)}`)
    log.info(`${chalk.yellow("pnpm")} install`)
    log.info(`${chalk.yellow("pnpm")} run dev`)
  } catch (e) {
    log.error(chalk.red("代码下载失败!"))
  }
}

继续优化,使用 figlet 添加打印效果,figlet官方文档。如这种效果:

安装 figlet 及其类型声明文件

复制代码
pnpm install figlet @types/figlet

注意:要添加到生产依赖

clone.ts

复制代码
const goodPrinter = async (message: string) => {
  const data = await figlet(message)
  console.log(chalk.rgb(40, 156, 193).visible(data))
}


结语

本文介绍的案例已同步到github,github地址。

相关推荐
福兮说11 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理11 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆66612 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
Dovis(誓平步青云)12 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan13 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_13 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒14 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技14 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu15 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub
niyongsheng15 小时前
后端零改动,给若依换一套现代化前端
vue.js·开源·node.js