从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.人…

macOS 的目录结构

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

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

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

aws凭证(一)凭证存储

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

力扣面试经典 150(上)

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

聚焦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 是…

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

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

树莓派搭建NextCloud:给数据一个安全的家

前言 NAS有很多方案&#xff0c;常见的有 Nextcloud、Seafile、iStoreOS、Synology、ownCloud 和 OpenMediaVault &#xff0c;以下是他们的特点&#xff1a; 1. Nextcloud 优势&#xff1a; 功能全面&#xff1a;支持文件同步、共享、在线文档编辑、视频会议、日历、联系人…

数据集-目标检测系列- 花卉 鸡蛋花 检测数据集 frangipani >> DataBall

数据集-目标检测系列- 花卉 鸡蛋花 检测数据集 frangipani >> DataBall DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 贵在坚持&#xff01; 数据样例项目地址&#xff1a; * 相关项目 1&#xff09;数据集…

初次体验加猜测信息安全管理与评估国赛阶段训练习

[第一部分] 网络安全事件响应 window操作系统服务器应急响应流程_windows 服务器应急响应靶场_云无迹的博客-CSDN博客 0、请提交攻击者攻击成功的第一时间&#xff0c;格式&#xff1a;YY:MM:DD hh:mm:ss1、请提交攻击者的浏览器版本2、请提交攻击者目录扫描所使用的工具名称…

Python Matplotlib 安装指南:使用 Miniconda 实现跨 Linux、macOS 和 Windows 平台安装

Python Matplotlib 安装指南&#xff1a;使用 Miniconda 实现跨 Linux、macOS 和 Windows 平台安装 Matplotlib是Python最常用的数据可视化工具之一&#xff0c;结合Miniconda可以轻松管理安装和依赖项。在这篇文章中&#xff0c;我们将详细介绍如何使用Miniconda在Linux、mac…

opencv-python 分离边缘粘连的物体(距离变换)

import cv2 import numpy as np# 读取图像&#xff0c;这里添加了判断图像是否读取成功的逻辑 img cv2.imread("./640.png") # 灰度图 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯模糊 gray cv2.GaussianBlur(gray, (5, 5), 0) # 二值化 ret, binary cv2…

KubeSphere内网环境实践GO项目流水线

KubeSphere内网环境实践GO项目流水线 kubesphere官方给出的流水线都是在公网环境下&#xff0c;并对接github、dockerhub等环境。本文在内网实践部署&#xff0c;代码库使用内网部署的gitlab&#xff0c;镜像仓库使用harbor。 1. 环境准备 1.1 部署kubesphere环境 参考官方…

UE5材质篇5 简易水面

不得不说&#xff0c;UE5里搞一个水面实在是相比要自己写各种反射来说太友好了&#xff0c;就主要是开启一堆开关&#xff0c;lumen相关的&#xff0c;然后稍微连一些蓝图就几乎有了 这里要改一个shading model&#xff0c;要这个 然后要增加一个这个node 并且不需要连接base …

浦语提示词工程实践(LangGPT版,服务器上部署internlm2-chat-1_8b,踩坑很多才完成的详细教程,)

首先&#xff0c;在InternStudio平台上创建开发机。 创建成功后点击进入开发机打开WebIDE。进入后在WebIDE的左上角有三个logo&#xff0c;依次表示JupyterLab、Terminal和Code Server&#xff0c;我们使用Terminal就行。&#xff08;JupyterLab可以直接看文件夹&#xff09;…

小白学多线程(持续更新中)

1.JDK中的线程池 JDK中创建线程池有一个最全的构造方法&#xff0c;里面七个参数如上所示。 执行流程分析&#xff1a; 模拟条件&#xff1a;10个核心线程数&#xff0c;200个最大线程数&#xff0c;阻塞队列大小为100。 当有小于十个任务要处理时&#xff0c;因为小于核心线…