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

从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地址。

相关文章:

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

前言 本文介绍的案例已同步到github&#xff0c;github地址。 vue-cli 和 create-react-app 等 cli 脚手架工具用于快速搭建应用&#xff0c;无需手动配置复杂的构建环境。本文介绍如何使用 rollup 搭建一个脚手架工具。 脚手架工具的工作流程简言为&#xff1a;提供远端仓库…...

开发者视角下的鸿蒙

鸿蒙操作系统&#xff08;HarmonyOS&#xff09;是华为公司自主研发的一款面向未来、面向全场景的分布式操作系统。它旨在为用户提供一个无缝的智能生活体验&#xff0c;支持多种终端设备&#xff0c;如智能手机、平板电脑、智能穿戴设备、智能家居等。鸿蒙操作系统的出现&…...

docker基础命令

目录 1、docker拉取镜像 2、查看镜像 3、运行镜像 4、查看容器 5、停止、启动、容器和删除容器 6、进入容器 7、删除镜像 8、保存镜像 9、加载镜像 10、镜像标签 11、制作镜像 ​12、镜像上传 1、docker拉取镜像 docker pull 用户名/镜像名:tag不加tag(版本号) 即…...

订单日记为“惠采科技”提供全方位的进销存管理支持

感谢温州惠采科技有限责任公司选择使用订单日记&#xff01; 温州惠采科技有限责任公司&#xff0c;成立于2024年&#xff0c;位于浙江省温州市&#xff0c;是一家以从事销售电气辅材为主的企业。 在业务不断壮大的过程中&#xff0c;想使用一种既能提升运营效率又能节省成本…...

C++共享智能指针

C中没有垃圾回收机制&#xff0c;必须自己释放分配的内存&#xff0c;否则就会造成内存泄漏。解决这个问题最有效的方式是使用智能指针。 智能指针是存储指向动态分配(堆)对象指针的类&#xff0c;用于生存期的控制&#xff0c;能够确保在离开指针所在作用域时&#xff0c;自动…...

数学建模学习(138):基于 Python 的 AdaBoost 分类模型

1. AdaBoost算法简介 AdaBoost(Adaptive Boosting)是一种经典的集成学习算法,由Yoav Freund和Robert Schapire提出。它通过迭代训练一系列的弱分类器,并将这些弱分类器组合成一个强分类器。算法的核心思想是:对于被错误分类的样本,在下一轮训练中增加其权重;对于正确分类…...

sqlite-vec一个SQLite3高效向量搜索扩展--JDBC环境使用

最近要用SQLite3&#xff0c;之前放出来了SQLiteUtile工具&#xff0c;方便操作。今天发现AIGC方面&#xff0c;RAG知识库需要使用向量数据库&#xff0c;来存储知识信息。一般呢都是用mysql&#xff0c;但无奈的是mysql就是不让用。突然又发现SQLite3有向量库扩展组件&#xf…...

Spark SQL操作

Spark SQL操作 文章目录 Spark SQL操作一、DataFrame的创建与保存1.前提操作2.数据准备3.创建4.保存DataFrame 二、DataFrame的操作1.printSchema2.show3.select4.filter5.groupBy(filed)6.sort(field) 三、临时表操作1.创建临时表2.通过临时表及SQL语句进行查询 四、从RDD转换…...

【大模型】LLaMA: Open and Efficient Foundation Language Models

链接&#xff1a;https://arxiv.org/pdf/2302.13971 论文&#xff1a;LLaMA: Open and Efficient Foundation Language Models Introduction 规模和效果 7B to 65B&#xff0c;LLaMA-13B 超过 GPT-3 (175B)Motivation 如何最好地缩放特定训练计算预算的数据集和模型大小&…...

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

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

ansible常用模块

一.ansible常用模块 ansible [主机or组列表] -m 模块 -a "参数"1.shell:类似于在终端上直接输入命令,支持bash特性2.command(默认模块):使用的变量需要事先定义好,不支持bash特性&#xff0c;如管道、重定向3.script: 执行脚本,支持python,shell脚本4.file:用于在被控…...

window11编译pycdc.exe

一、代码库和参考链接 在对python打包的exe文件进行反编译时&#xff0c;会使用到uncompyle6工具&#xff0c;但是这个工具只支持python3.8及以下&#xff0c;针对更高的版本的python则不能反编译。 关于反编译参考几个文章&#xff1a; Python3.9及以上Pyinstaller 反编译教…...

C语言——break、continue、goto

目录 一、break 二、continue 1、在while循环中 2、在for循环中 三、go to 一、break 作用是终止循环&#xff0c;在循环内遇到break直接就跳出循环。 注&#xff1a; 一个break语句只能跳出一层循环。 代码演示&#xff1a; #include<stdio.h>void test01() {for (…...

实战OpenCV之人脸识别

基础入门 随着计算机视觉技术和深度学习的发展,人脸识别已经成为一项广泛应用的技术,涵盖了从安全监控、身份验证、智能家居到大型公共安全项目等多个领域。 人脸识别技术通常包括以下几个主要步骤。 图像采集:通过摄像头或其他图像采集设备,捕获包含人脸的图像或视频帧。 …...

记录第一次安装laravel项目

window系统 Laravel中文文档&#xff1a;https://laravel-docs.catchadmin.com/docs/11/getting-started/installation 1.使用composer安装全局laravel composer global require laravel/installer2.安装完成后在命令行输入laravel&#xff0c;如果报错&#xff1a;laravel不是…...

AWTK-WEB 快速入门(1) - C 语言应用程序

先安装 AWTK Designer 用 AWTK Designer 新建一个应用程序 2.1. 新建应用程序 这里假设应用程序的名称为 AwtkApplicationC&#xff0c;后面会用到&#xff0c;如果使用其它名称&#xff0c;后面要做相应修改。 在窗口上放置一个按钮将按钮的名称改为 “close”将按钮的文本改…...

《操作系统 - 清华大学》4 -5:非连续内存分配:页表一反向页表

文章目录 1. 大地址空间的问题2. 页寄存器&#xff08; Page Registers &#xff09;方案3. 基于关联内存(associative memory )的反向页表&#xff08;inverted page table&#xff09;4. 基于哈希&#xff08;hashed&#xff09;查找的反向页表5. 小结 1. 大地址空间的问题 …...

数据可视化复习1-Matplotlib简介属性和创建子图

1.Matplotlib简介 Matplotlib是一个Python的2D绘图库&#xff0c;它可以在各种平台上以各种硬拷贝格式和交互环境生成具有出版品质的图形。通过Matplotlib&#xff0c;开发者可以仅需要几行代码&#xff0c;便可以生成绘图、直方图、功率谱、条形图、错误图、散点图等。 以下…...

98. 验证二叉搜索树【 力扣(LeetCode) 】

文章目录 零、原题链接一、题目描述二、测试用例三、解题思路四、参考代码 零、原题链接 98. 验证二叉搜索树 一、题目描述 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当…...

github中banch和tag的应用

GitHub 中的 Branch 和 Tag 之间的关系 在 GitHub 和 Git 中&#xff0c;**Branch&#xff08;分支&#xff09;**和**Tag&#xff08;标签&#xff09;**都是用来管理和标记代码的概念&#xff0c;但它们在版本控制中扮演不同的角色和有不同的用途。 --- 名词解释 1. 分支…...

鸿蒙HarmonyOS开发:一次开发,多端部署(工程级)三层工程架构

文章目录 一、工程创建1、先创建出最基本的项目工程。2、新建common、features、 products 目录 二、工程结构三、依赖关系1、oh-package.json52、配置ohpm包依赖 四、引用ohpm包中的代码1、定义共享资源2、在common模块index文件中导出3、在phone模块oh-package.json5文件中引…...

无插件H5播放器EasyPlayer.js视频流媒体播放器如何开启electron硬解码Hevc(H265)

在数字化时代&#xff0c;流媒体播放器技术正经历着前所未有的变革。随着人工智能、大数据、云计算等技术的融合&#xff0c;流媒体播放器的核心技术不断演进&#xff0c;为用户提供了更加丰富和个性化的观看体验。 EasyPlayer.js H5播放器&#xff0c;是一款能够同时支持HTTP、…...

关于vue生命周期理解示例代码

在业务运作时&#xff0c;特定的逻辑代码&#xff0c;需要在特定的阶段去执行&#xff0c;所以需要理解Vue的生命周期&#xff0c;以及各个周期内的方法&#xff0c;才能明确业务代码的编写 概述&#xff1a;Vue生命周期&#xff0c;指一个vue实例从创建到销毁的过程。 分为四…...

【MySQL数据库】C#实现MySQL数据库最简单的查询和执行函数

文章目录 前言一、查询方法二、执行方法 前言 C#和MySQL数据库是常见的数据交互&#xff0c;标准的查询和执行方法如下&#xff0c;做个记录。 一、查询方法 private static int QueryTable(string tableName, DateTime today, string stepName){int result 0; // 返回数据…...

深度学习笔记之BERT(二)BERT精简变体:ALBERT

深度学习笔记之BERT——BERT精简变体:ALBERT 引言回顾&#xff1a;ResNet对于反向传播的作用BERT的配置BERT的问题/缺陷ALBERTALBERT的策略BERT VS ALBERT 引言 上一节从 Word2vec \text{Word2vec} Word2vec上下文信息的局限性角度出发&#xff0c;介绍了 BERT \text{BERT} BE…...

Easyexcel(5-自定义列宽)

相关文章链接 Easyexcel&#xff08;1-注解使用&#xff09;Easyexcel&#xff08;2-文件读取&#xff09;Easyexcel&#xff08;3-文件导出&#xff09;Easyexcel&#xff08;4-模板文件&#xff09;Easyexcel&#xff08;5-自定义列宽&#xff09; 注解 ColumnWidth Data…...

Linux 安装 Git 服务器

一、安装 Git 1. 在 CentOS/RHEL 中使用以下命令&#xff1a; sudo yum update -y # 或者 sudo dnf update -y (在较新的系统中) sudo yum install git -y验证安装&#xff1a;git --version 2. 配置 Git 用户 git config --global user.name "Your Name" git co…...

C#学习笔记——窗口停靠控件WeifenLuo.WinFormsUI.Docking使用-腾讯云开发者社区-腾讯云

C#学习笔记——窗口停靠控件WeifenLuo.WinFormsUI.Docking使用-腾讯云开发者社区-腾讯云 C#学习笔记——窗口停靠控件WeifenLuo.WinFormsUI.Docking使用 发布于 2021-06-10 00:10:59 7.1K0 举报 文章被收录于专栏&#xff1a;c#学习笔记 一、介绍 DockPanelSuite是托管在…...

c++-有关输出、信息输入、趣味输入应用、运算符、变量、浮点数数据类型的基础知识

C是一种功能强大且广泛使用的编程语言&#xff0c;它可以用于开发各种类型的应用程序。在这篇文章中&#xff0c;我们将介绍C程序的输出、信息输入、趣味输入应用、运算符、变量和浮点数数据类型的基础知识。 目录 输出 信息输入 趣味输入应用 运算符 变量 浮点数数据类…...

STM32-- keil 的option for target使用

keil版本号 1.device界面 如&#xff1a;stm32f103c8t6的工程&#xff0c;可以直接在device这里修改成stm32f103vct6&#xff0c;虽然引脚不一样&#xff0c;但是很多一样的地方&#xff0c;可以直接使用&#xff0c;有些不修改也可以下载程序。 2.target xtal的设置不起作用了…...

Python 使用 Token 认证方案连接 Kubernetes (k8s) 的详细过程

在 Kubernetes 中&#xff0c;使用 Token 认证是一种常见的客户端身份验证方式&#xff0c;尤其适用于 ServiceAccount。以下是详细的步骤&#xff0c;包括如何查看 Token、获取 API 服务地址、配置远程连接&#xff0c;以及如何在 Python 中连接 k8s。 1. 获取 Token 首先&a…...

神经网络(系统性学习二):单层神经网络(感知机)

此前篇章&#xff1a; 神经网络中常用的激活函数 神经网络&#xff08;系统性学习一&#xff09;&#xff1a;入门篇 单层神经网络&#xff08;又叫感知机&#xff09; 单层网络是最简单的全连接神经网络&#xff0c;它仅有输入层和输出层&#xff0c;没有隐藏层。即&#x…...

3D Gaussian Splatting在鱼眼相机中的应用与投影变换

paper:Fisheye-GS 1.概述 3D 高斯泼溅 (3DGS) 因其高保真度和实时渲染而备受关注。然而,由于独特的 3D 到 2D 投影计算,将 3DGS 适配到不同的相机型号(尤其是鱼眼镜头)带来了挑战。此外,基于图块的泼溅效率低下,尤其是对于鱼眼镜头的极端曲率和宽视野,这对于其更广泛…...

MATLAB的语音信号采集与处理分析

1、基本描述 本文描述的系统是一个全面而精细的语音信号处理平台&#xff0c;核心组件由MATLAB的高级功能模块构建而成。系统的核心交互界面&#xff0c;借助于MATLAB的uifigure函数搭建&#xff0c;为用户提供了一个直观且响应迅速的操作环境。通过设计的GUI按钮&#xff0c;如…...

H.265流媒体播放器EasyPlayer.js H5流媒体播放器如何验证视频播放是否走硬解

随着技术的不断进步和5G网络的推广&#xff0c;中国流媒体播放器行业市场规模以及未来发展趋势都将持续保持稳定的增长&#xff0c;并将在未来几年迎来新的发展机遇。流媒体播放器将继续作为连接内容创作者和观众的重要桥梁&#xff0c;推动数字媒体产业的创新和发展。 EasyPla…...

深度学习:ResNet每一层的输出形状

其中 /**在输出通道数为64、步幅为2的7 7卷积层后&#xff0c;接步幅为2的3 3的最大汇聚层,与GoogLeNet区别是每个卷积层后增加了批量规范层**/ b1 nn.Sequential(nn.Conv2d(1, 64, kernel_size7, stride2, padding3),nn.BatchNorm2d(64), nn.ReLU(),nn.MaxPool2d(kernel_s…...

牛客题库 21738 牛牛与数组

牛牛与数组题目链接 题目大意 牛牛喜欢这样的数组: 1:长度为n 2:每一个数都在1到k之间 3:对于任意连续的两个数A,B,A<=B 与(A % B != 0) 两个条件至少成立一个请问一共有多少满足条件的数组,对 1 e 9 + 7 1e^9+7 1e9+7 取模 输入格式 输入两个整数 n , k n,k n,…...

学会Lambda,让程序Pythonic一点

Lambda是Python里的高阶用法&#xff0c;要把代码写得Pythonic&#xff0c;就需要了解这些高阶用法&#xff0c;想说自己是一名真正的Python程序员&#xff0c;先要把代码写得Pythonic。 今天聊下Lambda的用法&#xff0c;写篇简短的用法说明。 Lambda是匿名函数的意思&#…...

旋转向量v和旋转矩阵R

旋转向量v和旋转矩阵R 旋转向量 v 和旋转矩阵 R 是三维空间中描述旋转的两种数学表示方式。两者的关系通过 Rodrigues 公式 建立。 1. 旋转向量v 2. 旋转矩阵R 3. 旋转向量v和旋转矩阵R的关系 两者通过 Rodrigues 公式 和特殊的矩阵运算互相转换&#xff1a; 4. 代码示例 1…...

CSS浮动:概念、特性与应用

CSS浮动是网页设计和开发中常见的布局技术之一&#xff0c;以下是CSS浮动相关的所有重要知识点&#xff1a; 一、浮动的定义与语法 浮动&#xff08;float&#xff09;属性可以指定一个元素应沿其容器的左侧或右侧放置&#xff0c;允许文本和内联元素环绕它。浮动属性最初只用…...

类和对象(下)

1.取地址运算符重载 1.1 const成员函数 • 将const修饰的成员函数称之为const成员函数&#xff0c;const修饰成员函数放到成员函数参数列表的后 ⾯。 • const实际修饰该成员函数隐含的this指针&#xff0c;表明在该成员函数中不能对类的任何成员进行修改。 const 修饰D…...

后端接受大写参数(亲测能用)

重要点引入包别引用错了 import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data;JsonSerialize Data public class Item {JsonProperty(value "Token")private String token…...

Android仿前端分页组件pagination

仿前端pagination Android仿前端分页组件pagination 最近Android原生有个需求就是做个分页组件&#xff0c;不用上拉加载&#xff0c;因为数据量太大用户喜欢前端的方式&#xff0c;UI主要是拼凑比较简单&#xff0c;主要补充了一些判断越界和数据不全的细节&#xff0c;记录方…...

网络安全中常用浏览器插件、拓展

引言 现在的火狐、Edge&#xff08; Chromium内核&#xff09;、Chrome等浏览器带有插件、拓展&#xff08;Plugin&#xff09;的功能。这些插件中有的可以过滤广告&#xff0c;有的提供便捷的翻译&#xff0c;有的提供JavaScript脚本支持&#xff0c;方便用户的使用也大大的增…...

Vue 使用 Cropper.js 实现图片裁剪功能

前言 图片裁剪功能无论是用户头像的裁剪&#xff0c;还是图片内容的精确调整&#xff0c;都成为了提升用户体验的关键一环。Vue.js 结合 Cropper.js 这一功能丰富的图片裁剪库&#xff0c;可以轻松实现高效、直观的图片裁剪功能。本文将详细介绍如何在 Vue.js 项目中集成并使用…...

Python 3 和 JSON 数据格式

Python 3 和 JSON 数据格式 Python 3 是一种广泛使用的编程语言,以其简洁明了的语法和强大的功能而闻名。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。Python 3 提供了内置的 json 模块,使得在 Python 程序…...

Halo 正式开源: 使用可穿戴设备进行开源健康追踪

在飞速发展的可穿戴技术领域&#xff0c;我们正处于一个十字路口——市场上充斥着各式时尚、功能丰富的设备&#xff0c;声称能够彻底改变我们对健康和健身的方式。 然而&#xff0c;在这些光鲜的外观和营销宣传背后&#xff0c;隐藏着一个令人担忧的现实&#xff1a;大多数这些…...

第一个autogen与docker项目

前提条件&#xff1a;在windows上安装docker 代码如下&#xff1a; import os import autogen from autogen import AssistantAgent, UserProxyAgentllm_config {"config_list": [{"model": "GLM-4-Plus","api_key": "your api…...

React第四节 组件的三大属性之state

前言 状态 state适用于类式组件中&#xff0c;而再函数式组件中需要使用 useState HOOK 模拟状态; React的组件就是一个状态机&#xff0c;通过与用户的交互&#xff0c;实现不同的状态&#xff0c;根据不同的状态展现出不一样的UI视图 并不是组件中所有的属性 都是组件的状态…...

在 CentOS 系统上直接安装 MongoDB 4.0.25

文章目录 步骤 1&#xff1a;配置 MongoDB 官方源步骤 2&#xff1a;安装 MongoDB步骤 3&#xff1a;启动 MongoDB 服务步骤 4&#xff1a;验证安装步骤 5&#xff1a;可选配置注意事项 以下是在 CentOS 系统上直接安装 MongoDB 4.0.25 的详细步骤&#xff1a; 步骤 1&#x…...