从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.jsonmoudlecommonjs,改为 'module: "ES2015"', 'module: "ES2020"', 'module: "ES2022"', or 'module: "ESNext"' 后即可。

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

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

查看 create-react-app 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] = itemreturn {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.jsnpx 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 AxiosResponseconsole.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 字段记录了最新版本。

在这里插入图片描述

继续优化,使用 lodashgt 比较版本号,并给出更新提示,我们也可以再定义一个命令 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 AxiosResponsereturn 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地址。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/61725.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

摄影:相机控色

摄影&#xff1a;相机控色 白平衡&#xff08;White Balance&#xff09;白平衡的作用&#xff1a; 白平衡的使用环境色温下相机色温下总结 白平衡偏移与包围白平衡包围 影调 白平衡&#xff08;White Balance&#xff09; 人眼看到的白色&#xff1a;会自动适应环境光线。 相…

键盘党福音!自定义指令实现回车快捷删除

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热爱技术和分享&#xff0c;欢迎大家交流&#xff0c;一起学习进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 目录 确认对话框 回车键快捷确认 自定义指令实现回车删除 实现思路 实现代码 …

AG32既可以做MCU,也可以仅当CPLD使用

Question: AHB总线上的所有外设都需要像ADC一样&#xff0c;通过cpld处理之后才能使用? Reply: 不用。 除了ADC外&#xff0c;其他都是 mcu可以直接配置使用的。 Question: DMA和CMP也不用? Reply: DMA不用。 ADC/DAC/CMP 用。 CMP 其实配置好后&#xff0c;可以直…

深度学习实战人脸识别

文章目录 前言一、人脸识别一般过程二、人脸检测主流算法1. MTCNN2. RetinaFace3. CenterFace4. BlazeFace5. YOLO6. SSD7. CascadeCNN 三、人脸识别主流算法1.deepface2.FaceNet3.ArcFace4.VGGFace5.DeepID 四、人脸识别系统实现0.安装教程与资源说明1. 界面采用PyQt5框架2.人…

【STL】12.unordered_set与unordered_map的模拟实现

一、源码及框架分析 SGI-STL30版本源代码中没有unordered_map和unordered_set&#xff0c;SGI-STL30版本是C11之前的STL版本&#xff0c;这两个容器是C11之后才更新的。但是SGI-STL30实现了哈希表&#xff0c;只容器的名字是hash_map和hash_set&#xff0c;他是作为非标准的容…

macOS 的目录结构

文章目录 根目录 (/)常见目录及其用途示例目录结构注意事项根目录 (/)主要目录及其含义其他目录总结 macOS 的目录结构无论是在 Intel 架构还是 ARM 架构的 Mac 电脑上都是相同的。macOS 的目录结构遵循 Unix 和 BSD 的传统&#xff0c;具有许多标准目录。以下是一些主要目录及…

003 STM32基础、架构以及资料介绍——常识

注&#xff1a; 本笔记参考学习B站官方视频教程&#xff0c;免费公开交流&#xff0c;切莫商用。内容可能有误&#xff0c;具体以官方为准&#xff0c;也欢迎大家指出问题所在。 01什么是STM32&#xff08;宏观&#xff09; STM32属于一个微控制器&#xff0c;自带了各种常用通…

AWS CLI

一、AWS CLI介绍 1、简介 AWS CLI&#xff08;Amazon Web Services Command Line Interface&#xff09;是一个命令行工具&#xff0c;它允许用户通过命令行与 Amazon Web Services&#xff08;AWS&#xff09;的各种云服务进行交互和管理。使用 AWS CLI&#xff0c;用户可以…

aws凭证(一)凭证存储

AWS 凭证用于验证身份&#xff0c;并授权对 DynamoDB 等等 AWS 服务的访问。配置了aws凭证后&#xff0c;才可以通过编程方式或从AWS CLI连接访问AWS资源。凭证存储在哪里呢&#xff1f;有以下几个方法&#xff1a; 一、使用文件存储 1、介绍 文件存储适用于长期和多账户配置…

Linux系统性能优化技巧

系统性能优化 在当今的信息技术领域&#xff0c;Linux系统的性能优化变得越来越重要。随着Linux操作系统的广泛应用&#xff0c;从桌面环境到大型服务器集群&#xff0c;性能优化不仅可以提升系统的响应速度和吞吐量&#xff0c;还能降低资源消耗&#xff0c;从而延长硬件使用…

力扣面试经典 150(上)

文章目录 数组/字符串1. 合并两个有序数组2. 移除元素3. 删除有序数组中的重复项4. 删除有序数组的重复项II5. 多数元素6. 轮转数组7. 买卖股票的最佳时机8. 买卖股票的最佳时机II9. 跳跃游戏10. 跳跃游戏II11. H 指数12. O(1)时间插入、删除和获取随机元素13. 除自身以外数组的…

二分排序

二分问题之前遇到很多次了&#xff0c;不过一直是手写完整二分&#xff0c;现在转变一下想法&#xff0c;直接使用函数lower_bound和upper_bound更方便 lower_bound 有序数组中 查找第一个不小于指定值的位置。 本质二分代码&#xff1a; int lower_bound_custom(int* arr, i…

apache、iis屏蔽限制ip访问(适用虚拟主机)

Linux下规则文件.htaccess(手工创建.htaccess文件到站点根目录) <IfModule mod_rewrite.c> RewriteEngine On #Block ip RewriteCond %{http:X-Forwarded-For} ^(8.8.4.4|8.8.8\.) [OR] RewriteCond %{REMOTE_ADDR} ^(8.8.4.4|8.8.8\.) [OR] RewriteCond %{http:X-Real-…

李春葆《数据结构》——图相关代码

邻接矩阵结构体&#xff1a; #define MAX<最大结点个数> #define INF 32765 //定义无穷 typedef struct{int no;//顶点的编号&#xff1b;InfoType info;//顶点的其他信息 }vertexType;//顶点的类型 typedef struct{int edges[MAX][Max];//邻接矩阵数组 int vertexTy…

经验笔记:Git 中的远程仓库链接及上下游关系管理

Git 中的远程仓库链接及上下游关系管理 1. 远程仓库的链接信息 当你克隆一个远程仓库时&#xff0c;Git 会在本地仓库中记录远程仓库的信息。这些信息包括远程仓库的 URL、默认的远程名称&#xff08;通常是 origin&#xff09;&#xff0c;以及远程仓库中的所有分支和标签。…

聚焦AI存储,联想凌拓全力奔赴

【全球存储观察 &#xff5c; 科技热点关注】 每一个时代&#xff0c;都有每一个时代的骄傲。 在信息化时代&#xff0c;NAS文件存储肩负着非结构化数据管理与存储的重任&#xff0c;NetApp以其创新实力&#xff0c;赢得了全球存储市场的极高声誉。 在数智化时代&#xff0c;…

JavaWeb后端开发知识储备2

目录 1.HttpClient 2.微信小程序开发 3.Spring Cache 1.HttpClient 简单来说&#xff0c;HttpClient可以通过编码的方式在Java中发送Http请求 2.微信小程序开发 微信小程序的开发本质上是前端开发&#xff0c;对于后端程序员来说了解即可 3.Spring Cache Spring Cache 是…

MySQL和ADSDB

ADSDB&#xff08;Aliyun Distributed SQL Database&#xff09; 和 MySQL 都是数据库系统&#xff0c;但它们在架构、用途和特点上有所不同。下面是这两者的主要区别&#xff1a; 1、数据库类型与设计架构 MySQL&#xff1a; 是一个 关系型数据库管理系统&#xff08;RDBMS&a…

基于CNN+RNNs(LSTM, GRU)的红点位置检测(pytorch)

1 项目背景 需要在图片精确识别三跟红线所在的位置&#xff0c;并输出这三个像素的位置。 其中&#xff0c;每跟红线占据不止一个像素&#xff0c;并且像素颜色也并不是饱和度和亮度极高的红黑配色&#xff0c;每个红线放大后可能是这样的。 而我们的目标是精确输出每个红点的…

【java】常用命令记录

1.java 2.jar 2.1 介绍 JAR包是Java中所特有一种压缩文档,其实大家就可以把它理解为.zip包。(也可以用war包. jar cvf aa.war)当然也是有区别的,JAR包中有一个META-INF\MANIFEST.MF文件,当你找成JAR包时,它会自动生成。JAR包是由JDK安装目录\bin\jar.exe命令生成的&#xff0…