当前位置: 首页 > news >正文

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

前言

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))
}

在这里插入图片描述
在这里插入图片描述

相关文章:

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

前言 vue-cli 和 create-react-app 等 cli 脚手架工具用于快速搭建应用&#xff0c;无需手动配置复杂的构建环境。本文介绍如何使用 rollup 搭建一个脚手架工具。 脚手架工具的工作流程简言为&#xff1a;提供远端仓库各种模版 > 用户通过命令选择模版 > 拉取仓库代码 …...

河道水位流量一体化自动监测系统:航运安全的护航使者

在广袤的水域世界中&#xff0c;航运安全始终是至关重要的课题。而河道水位流量一体化自动监测系统的出现&#xff0c;如同一位强大的护航使者&#xff0c;为航运事业的稳定发展提供了坚实的保障。 水位传感器&#xff1a;负责实时监测河道的水位变化。这些传感器通常采用先进的…...

维护在线重做日志

学习目标 解释在线重做日志文件的目的概述在线重做日志文件的结构控制日志开关和检查点多路复用和维护在线重做日志文件使用OMF管理在线重做日志文件获取在线重做日志文件信息 在线重做日志文件提供了在数据库发生故障时重做事务的方法。 每个事务都同步写入重做日志缓冲区&a…...

ASCB1系列APP操控末端回路智能微断 物联网断路器 远程控制开关 学校、工厂、农场、商业大楼等可用

安科瑞戴婷 Acrel-Fanny ASCB1系列智能微型断路器是安科瑞电气股份有限公司全新推出的智慧用电产品&#xff0c;产品由智能微型断路器与智能网关两部分组成&#xff0c;可用于对用电线路的关键电气因素&#xff0c;如电压、电流、功率、温度、漏电、能耗等进行实时监测&#x…...

Python入门(10)--面向对象进阶

Python面向对象进阶 &#x1f680; 1. 继承与多态 &#x1f504; 1.1 继承基础 class Animal:def __init__(self, name, age):self.name nameself.age agedef speak(self):passdef describe(self):return f"{self.name} is {self.age} years old"class Dog(Anim…...

Makefile 之 自动化变量

作用范围只在这条规则以及连带规则中&#xff0c;所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。 "$" 表示目标的集合&#xff0c;就像一个数组&#xff0c;"$"依次取出目标&#xff0c;并执于命令。 "$<"和"$&qu…...

鸿蒙开发:ForEach中为什么键值生成函数很重要

前言 在列表组件使用的时候&#xff0c;如List、Grid、WaterFlow等&#xff0c;循环渲染时都会使用到ForEach或者LazyForEach&#xff0c;当然了&#xff0c;也有单独使用的场景&#xff0c;如下&#xff0c;一个很简单的列表组件使用&#xff0c;这种使用方式&#xff0c;在官…...

沃丰科技智能外呼机器人:超越人工,重塑外呼体验

随着科技的不断发展&#xff0c;人工智能已经逐渐渗透到各行各业&#xff0c;其中智能外呼机器人的出现&#xff0c;更是给企业带来了全新的客户体验。与传统的人工外呼相比&#xff0c;智能外呼机器人具有更高的效率、更低的成本以及更好的用户体验等优势。 优势一&#xff1…...

百度飞浆:paddle 线性回归模型

学习引用 参考视频&#xff1a; https://www.bilibili.com/video/BV1oRtkeVEVx?spm_id_from333.788.player.switch&vd_sourcec7739de98d044e74cdc74d6e772bed5f&p2 这段代码使用PaddlePaddle深度学习框架来实现一个简单的线性回归模型&#xff0c;旨在从给定的出租车…...

【JavaSE】【网络编程】UDP数据报套接字编程

目录 一、网络编程简介二、Socket套接字三、TCP/UDP简介3.1 有连接 vs 无连接3.2 可靠传输 vs 不可靠传输3.3 面向字节流 vs 面向数据报3.4 双向工 vs 单行工 四、UDP数据报套接字编程4.1 API介绍4.1.1 DatagramSocket类4.1.1.1 构造方法4.1.1.2 主要方法 4.1.2 DatagramPocket…...

45.坑王驾到第九期:Mac安装typescript后tsc命令无效的问题

点赞收藏加关注&#xff0c;你也能主打别墅&#xff01; 一、问题描述 Mac上终端运行如下命令&#xff1a; sudo npm install typescript -g //全局安装ts提示成功安装后&#xff0c;我测试tsc -v这个命令时出现如下错误&#xff1a; 也就是说找不到 tsc 命令。 二、解决方…...

20241120-Milvus向量数据库快速体验

目录 20241120-Milvus向量数据库快速体验Milvus 向量数据库pymilvus内嵌向量数据库模式设置向量数据库创建 Collections准备数据用向量表示文本插入数据 语义搜索向量搜索带元数据过滤的向量搜索查询通过主键搜索 删除实体加载现有数据删除 Collections了解更多 个人主页: 【⭐…...

【Golang】——Gin 框架中间件详解:从基础到实战

中间件是 Web 应用开发中常见的功能模块&#xff0c;Gin 框架支持自定义和使用内置的中间件&#xff0c;让你在请求到达路由处理函数前进行一系列预处理操作。这篇博客将涵盖中间件的概念、内置中间件的用法、如何编写自定义中间件&#xff0c;以及在实际应用中的一些最佳实践。…...

量子计算来袭:如何保护未来的数字世界

目录 前言 一、量子计算安全的学习方向 1. 量子物理学基础 2. 量子计算原理与技术 3. 传统网络安全知识 4. 量子密码学 5. 量子计算安全政策与法规 二、量子计算的漏洞风险 1. 加密算法被破解风险 2. 区块链安全风险 3. 量子密钥分发风险 4. 量子计算系统自身风险 …...

VMware虚拟机(Ubuntu或centOS)共享宿主机网络资源

VMware虚拟机(Ubuntu或centOS)共享宿主机网络资源 由于需要在 Linux 环境下进行一些测试工作&#xff0c;于是决定使用 VMware 虚拟化软件来安装 Ubuntu 24.04 .1操作系统。考虑到测试过程中需要访问 Github &#xff0c;要使用Docker拉去镜像等外部网络资源&#xff0c;因此产…...

光伏电站仿真系统的作用

光伏仿真系统有多方面的重要作用&#xff0c;不仅对前期的项目设计评估还是后期的运维效验都有非常重要的作用。 1、优化系统设计 通过输入不同的光伏组件参数、布局方案以及气象条件等&#xff0c;模拟各种设计场景下光伏电站的性能表现。例如&#xff0c;可以比较不同类型光…...

Golang文件操作

写文件 &emsp&#xff1b; os模块可以创建文件&#xff0c;使用fmt可以写入文件。如以下例子&#xff1a; package mainimport ("fmt""os" )func main() {// 学习 golang的文件操作file, err : os.Create("test.txt")if err ! nil {fmt.P…...

爬虫开发工具与环境搭建——使用Postman和浏览器开发者工具

第三节&#xff1a;使用Postman和浏览器开发者工具 在网络爬虫开发过程中&#xff0c;我们经常需要对HTTP请求进行测试、分析和调试。Postman和浏览器开发者工具&#xff08;特别是Network面板和Console面板&#xff09;是两种最常用的工具&#xff0c;能够帮助开发者有效地捕…...

React(二)

文章目录 项目地址七、数据流7.1 子组件传递数据给父组件7.1.1 方式一:給父设置回调函数,传递给子7.1.2 方式二:直接将父的setState传递给子7.2 给props传递jsx7.2.1 方式一:直接传递组件给子类7.2.2 方式二:传递函数给子组件7.3 props类型验证7.4 props的多层传递7.5 cla…...

同步原语(Synchronization Primitives)

同步原语&#xff08;Synchronization Primitives&#xff09;是用于控制并发编程中多个线程或进程之间的访问顺序&#xff0c;确保共享资源的安全访问的一组机制或工具。它们解决了竞争条件&#xff08;Race Condition&#xff09;、死锁&#xff08;Deadlock&#xff09;等并…...

SpringBoot服务多环境配置

一个项目的的环境一般有三个&#xff1a;开发(dev)、测试(test)、生产(proc)&#xff0c;一般对应三套环境&#xff0c;三套配置文件。 像下面这样直接写两个配置文件是不行的。 application.ymlserver:port: 8080application-dev.ymlspring:datasource:driver-class-name: co…...

STM32单片机CAN总线汽车线路通断检测-分享

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着汽车电子技术的不断发展&#xff0c;车辆通信接口在汽车电子控…...

【环境搭建】使用IDEA远程调试Docker中的Java Web

有时候要对Docker的Java Web远程调试其功能&#xff0c;于是就需要使用IDEA的远程调试功能&#xff0c;记录一下简单配置方法。 以Kylin4.0.0为例&#xff0c;首先拉取镜像并启动容器&#xff1a; $ docker pull apachekylin/apache-kylin-standalone:4.0.0$ docker run -d \-…...

贴代码框架PasteForm特性介绍之select,selects,lselect和reload

简介 PasteForm是贴代码推出的 “新一代CRUD” &#xff0c;基于ABPvNext&#xff0c;目的是通过对Dto的特性的标注&#xff0c;从而实现管理端的统一UI&#xff0c;借助于配套的PasteBuilder代码生成器&#xff0c;你可以快速的为自己的项目构建后台管理端&#xff01;目前管…...

STM32G4的数模转换器(DAC)的应用

目录 概述 1 DAC模块介绍 2 STM32Cube配置参数 2.1 参数配置 2.2 项目架构 3 代码实现 3.1 接口函数 3.2 功能函数 3.3 波形源代码 4 DAC功能测试 4.1 测试方法介绍 4.2 波形测试 概述 本文主要介绍如何使用STM32G4的DAC模块功能&#xff0c;笔者使用STM32Cube工具…...

SpringMVC跨线程获取requests请求对象(子线程共享servletRequestAttributes)和跨线程获取token信息

文章目录 引言I 跨线程共享数据跨线程获取requests请求对象基于org.slf4j.MDC存储共享数据InheritableThreadLocal解决异步线程,无法获取token信息问题II Feign 传递请求属性feign 模块处理被调用方处理请求头III 异步调用的方式CompletableFutureAsync注解Executors引言 本文…...

提取repo的仓库和工作树(无效)

问题 从供应商处获取的.repo的git仓库裸(project-object)仓库和工作树(projects)是分开的。 解决方案 根据工作树的软链接路劲&#xff0c;将工作树合并到project-object下。 import os import shutil import argparse import logging# 设置日志配置 logging.basicConfig(l…...

力扣整理版七:二叉树(待更新)

满二叉树&#xff1a;如果一棵二叉树只有度为0的结点和度为2的结点&#xff0c;并且度为0的结点在同一层上&#xff0c;则这棵二叉树为满二叉树。深度为k&#xff0c;有2^k-1个节点的二叉树。 完全二叉树&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&am…...

基于单片机的多功能环保宠物窝设计

本设计基于单片机设计的多功能环保宠物窝&#xff0c;利用温湿度传感器、压力传感模块、气味传感模块、红外测温传感器、通信模块、显示模块、清扫部件等&#xff0c;使其能够实现自动检测并调节温湿度、补充宠物食物、检测宠物体温健康并出现异常时进行报警、自动清扫消毒宠物…...

HBase 基础操作

一、启动HBase 首先&#xff0c;确保Hadoop和HBase服务已经启动。如果尚未启动&#xff0c;可以使用以下命令启动&#xff1a; # 启动Hadoop start-all.sh# 启动HBase start-hbase.sh二、HBase Shell操作 创建表 在HBase Shell中&#xff0c;使用create命令创建表。以下是一…...

泰安中文网站建设电话/沈阳关键词优化报价

登录功能是我在湖畔做的第一个需求。 当时PD给我的草图和下图类似&#xff1a; &#xff08;图片来自知乎iOS客户端登录界面&#xff09; 不过需求中要求用户名或者密码错误时&#xff0c;输入框要抖动&#xff08;类似Mac登录密码错误的抖动效果&#xff09;。 如果实现上图…...

化州网站建设/网络整合营销公司

PL/SQL(Procedural Language extensions to SQL)是Oracle 对标准 SQL 语言的过程化扩展&#xff0c;是专门用于各种环境下对 Oracle 数据库进行访问和开发的语言。 一、概述 Oracle数据库对标准的SQL 语言进行了扩展&#xff0c;将 SQL 语言的非过程化与第三代开发语言的过程化…...

java做购物网站/什么是搜索引擎营销

文章目录 1. 网页的相关概念2. 常用的游览器及内核3. web标准4. HTML语法规范4.1 基本语法概述4.2 标签关系5. HTML基本结构标签6. DOCTYPE和lang以及字符集的作用6.1 文档类型声音标签6. 2 lang语言种类6.3 字符集7. 标签语义8. 标题标签9. 段落标签和换行标签10. HTML格式化1…...

深圳企业建站高性价比的选择/优化大师下载

随着flv格式的日益流行&#xff0c;越来越多的地方都在使用这个格式&#xff0c;体积小&#xff0c;是最大的优势&#xff0c;但是问题是 如果你手头是其他的视频格式 要转换为flv格式的话&#xff0c;是不是很头疼啊 。 当你看到这篇文章是&#xff0c;想必一定也在为 怎么把s…...

广州站图片/无锡网站seo顾问

根据端口查进程 netstat -ano|findstr 80查看进程 tasklist|findstr 11111...

专门做网站制作的公司/广告seo是什么意思

Android的应用组件 应用组件是 Android 应用的基本构建基块。 共有四种不同的应用组件类型。每种类型都服务于不同的目的&#xff0c;并且具有定义组件的创建和销毁方式的不同生命周期。 活动Activity服务Service广播接收器(Broadcast Receiver)内容提供程序(Content Provide…...