Advertisement

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

阅读量:

前言

本项目的相关案例现已发布至GitHub平台,并已提供对应的GitHub地址供参考。

采用 vue-cli 以及 create-react-app 这类命令行界面脚手架工具时可实现便捷的应用构建。这些工具能够帮助开发者轻松完成项目部署与管理任务,并且能够自动处理依赖项的安装与更新等操作。这些脚手架工具基于命令行界面设计,在使用过程中不需要编写额外的代码即可完成核心功能。本文将介绍如何利用 rollup 构建一个高效的脚手架工具,并展示其在实际开发中的应用场景与优势。

脚手架工具的主要功能包括向远程仓库提供多种模板选项;用户可通过命令行指令来选择所需模板;执行拉取远程代码的操作。

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

  • 设置打包指令
  • 设置命令行交互功能,并列举常用命令如create-v等选项;其中create是核心功能块。
  • 发布软件到npm平台

1. 初始化项目

初始化项目

复制代码
    npm init -y
    
    
      
    
    AI写代码

在安装完成后,在配置生成之前,请确保全局安装了 TypeScript。

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

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"
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

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

复制代码
    "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"
    },
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

目录结构如下,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
    
    
      
    
    AI写代码

依赖说明:

  • RollUp 是一个广泛使用的打包工具。
  • @RollUp/Plugin-Node-Resolve 支持使用 RollUp 对 Node.js 模块进行打包。
  • @RollUp/Plugin-CommonJS 支持滚包 CommonJS 模块。
  • @RollUp/Plugin-Json 该插件可支持滚包 Json 文件。
  • @RollUP/Plugin-TypeScript2 该插件可支持滚包 TypeScript 文件。
  • @RollUP/Plugin-Terser 该插件可压缩打包代码。
  • rolls up 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(),
    ],
      },
    ]);
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

在 package.json 文件中,请您设置打包指令,并请指定 rollup 配置文件。同时,请确保使用 --bundleConfigAsCjs 选项将其转换为 CommonJS 执行方式。

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

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

遇到如下错误的原因在于,默认配置中将 module 设置为 CommonJS;而我们需要将其修改为 ES2015、ES2020、ES2022 或 Next.js 版本

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

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

在这里插入图片描述

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

访问以下代码仓库:https://github.com/facebook/create-react-app/blob/main/packages/create-react-app/package.json,我们可以识别其依赖关系,并决定仅安装其中的一部分。

在这里插入图片描述

本文使用了如下依赖:

  • 命令解析器:解析命令行指令
  • 启动动画:终端加载动画
  • 估算器用于显示进度条动画:终端加载条动画
  • 符号输出模块:终端输出符号
  • 图形渲染引擎( chalk):终端字体美化
  • @inquirer/prompts标签或提示名称( @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()
    
    
      
      
      
      
      
      
      
    
    AI写代码

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

在这里插入图片描述

5. create 指令配置

我们定义了一个创建操作。当在 vue-cli 中输入 vue create 时,请问您希望下载预设模板吗?如果选择这样做,则将该操作命名为 create 指令。

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

src/command/create.ts 文件中实现create命令的核心功能。
提供一个能够接收项目名称的方法;当用户直接传递项目名称时,请其选择合适的模板;若无输入,则要求用户提供项目的名称。

后执行create操作后可以跟上一个参数name(代表项目名称),该参数为可选参数,在后续步骤中将要求用户提供项目名。

复制代码
    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()
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

完成打包操作,在运行 npm run build 之后,请查看生成的命令文件。现在可以看到生成的命令是 create,并且正确获取了 dirName。

在这里插入图片描述

针对create的核心逻辑进行处理,在src/index.ts文件中传递dirName参数给create函数

在这里插入图片描述

在命令行中运行命令的.ts文件

  • 核心模块:基于 Vite + TypeScript + Vue 3 + axios + Pinia 开发
  • 组件库:Vite + TypeScript + Vue 3 + axios + Pinia + element + tailwind
  • 布局构建:完成所有组件布局

@inquirer/prompts 的作用是帮助终端用户执行输入或选择操作。本文采用了 Inquire-RAM 组件中的 input 和 select 功能。如需了解更多信息,请访问官方文档:inquirer.js

select 要求数据格式如下:

在这里插入图片描述
复制代码
    import { select, input } from "@inquirer/prompts"
    export interface TemplateInfo {
      name: string // 模板名称
      downloadUrl: string // 模板下载地址
      description: string // 模板描述
      branch: string // 模板分支
    }
    
    export const templates: Map<string, TemplateInfo> = new Map([
      [
    "Vite-Vue3-TypeScript-template",
    {
      name: "Vite-Vue3-TypeScript-template",
      downloadUrl: "git@gitee.com:tian__shuai/template-vite5--vue3.git",
      description: "vite + vue3 + ts初始模版",
      branch: "master",
    },
      ],
      [
    "Vite-Vue3-TypeScript-ElementUI-template",
    {
      name: "Vite-Vue3-TypeScript-ElementUI-template",
      downloadUrl: "git@gitee.com:tian__shuai/template-vite5--vue3.git",
      description: "vite + vue3 + ts + elementplus 初始模版",
      branch: "element",
    },
      ],
      [
    "Vite-Vue3-TypeScript-ElementUI-layout-template",
    {
      name: "Vite-Vue3-TypeScript-ElementUI-layout-template",
      downloadUrl: "git@gitee.com:tian__shuai/template-vite5--vue3.git",
      description: "vite + vue3 + ts + elementplus + layout 初始模版",
      branch: "element_layout",
    },
      ],
    ])
    
    export async function create(projectName?: string) {
      if (!projectName) {
    projectName = await input({ message: "请输入项目名称" })
      }
      
      // 初始化模版列表
      const templateList = Array.from(templates).map(
    (item: [string, TemplateInfo]) => {
      const [name, info] = item
      return {
        name,
        value: name,
        description: info.description,
      }
    }
      )
      // 选了哪个模版
      const templateName = await select({
    message: "请选择模版",
    choices: templateList,
      })
      // 选中模版的详情
      const info = templates.get(templateName)
      console.log(info)
      console.log("create", projectName)
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

检查是否能够获取用户所选的模版详情,并运行相应的命令

在这里插入图片描述

当输入项目名称时,请使用上/下箭头键来切换模板。描述字段位于下面。

在这里插入图片描述

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

在这里插入图片描述

当create时传入name参数,则不触发input事件,并直接跳转到模版选择页面

在这里插入图片描述

5.2 下载选择的模版

在项目目录下新建 src/utils/clone.ts 文件,并命名为克隆模板工具。该文件采用 simple-git 工具来拉取 Git 仓库,并通过 progress-estimator 应用程序来估算 Git 复制所需的时间,并在操作过程中显示进度条。

参考 simple-git官方文档 ,必须提供三个参数:

  • url:仓库地址
  • localPath:目标路径
  • options:分支信息
在这里插入图片描述

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

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

将来自 src/utils/create.ts 的模版信息通过 select 传递给 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)
      }
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码
在这里插入图片描述

提升下载界面的美观度,并通过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)
      }
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

效果:

在这里插入图片描述

进一步提升样式表现,并借助 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)
      }
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

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])
      }
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

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

在这里插入图片描述

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

6. 发布至npm

发布前需要调整src/project目录,在测试阶段以便检验create效果;但实际使用中无需设置process.cwd()。

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

6.1 README.md

建议增加README文件的说明文档,并提供一个在线生成图表的网站,用于创建npm版本标识。

在这里插入图片描述

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

在这里插入图片描述

README.md

在这里插入图片描述

6.2 完善package.json

6.2.1 bin

完善包文件的 bin 配置,在 bin 文件中配置了 benchu 命令。该命令将指向项目目录下的 bin 程序。例如运行 benchu 即等同于启动 bin 程序。在测试阶段打包后运行 node 跑 dist/index.js 或者直接使用 npx 命令运行 benchu

在这里插入图片描述

当我们运行 benchu 命令时,具体来说执行的是位于 bin/index.js 这个文件中。必须确保该文件拥有正确的可执行权限,并在该文件的头部配置 Node.js 环境。

bin/index.js

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

该字段在 package.json 中标识包含于 npm 包注册表中的文件或目录。如果没有设置该字段,默认情况下 npm 会包含除 .gitignore 和 .npmignore 文件外的所有文件。

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

在这里插入图片描述

6.3 发包

确认打包完成

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

查看npm镜像源地址

复制代码
    npm config get registry
    
    
      
    
    AI写代码

我这里是淘宝镜像

在这里插入图片描述

切换到 npm 源

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

登录 npm

复制代码
    npm login
    
    
      
    
    AI写代码

发布

复制代码
    npm publish
    
    
      
    
    AI写代码

注意: 每当进行 publish 操作时, 系统会自动更新 package.json 文件中的 version 参数. 这项功能可以通过以下两种方式实现: 一是手动调整版本号, 另一种是使用命令 npm version patch 来执行. 在运行该命令之前, 请确保您的 Git 工作区中没有任何未提交的变化或未被跟踪的文件.

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

在这里插入图片描述

可以看到 package.json 中 files 字段

在这里插入图片描述

7. 测试发布的包

全局下载 benchu-cli

复制代码
    npm install benchu-cli -g
    
    
      
    
    AI写代码

执行命令

复制代码
    benchu create
    
    
      
    
    AI写代码

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

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

command/create.ts文件中审阅之后进行覆盖操作,在确认是否有必要时审查版本升级

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

复制代码
    pnpm install axios -D
    
    
    // ... 忽略其余包
    import { name, version } from "../../package.json"
    import axios from 'axios'
    
    // 获取 npm 包的最新版本
    const getNpmInfo = async (name: string) => {
      const npmUrl = `https://registry.npmjs.org/${name}`
      let res = {}
      try {
    res = await axios.get(npmUrl)
      } catch (e) {
    console.log(e)
      }
      return res
    }
    const getNpmLatestVersion = async (name: string) => {
      const { data } = (await getNpmInfo(name)) as AxiosResponse
      console.info(data)
    }
    
    // 检查版本
    const checkVersion = async (name: string, version: string) => {
      const lastestVersion = await getNpmLatestVersion(name)
    }
    
    
    export async function create(projectName?: string) {
      // 项目名是否为空、是否需要覆盖项目 的逻辑...
    
      // 检查版本更新
      await checkVersion(name, version)
    
      // 提供模版选择、克隆仓库 的逻辑...
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

npm run build生成新的打包文件并运行脚本node dist/index.js create以生成新的包文件可以看到npm包的详细信息其中dist-tags字段存储了最新的标签信息

在这里插入图片描述

进一步优化系统性能,在 lodash 库中采用 lodash/gt 方法进行版本号比较,并提供版本更新提醒。同时我们也可以定义一个名为 update 的新命令来实现 benchesu-cli 的版本更新功能。

复制代码
    // 用于检查 npm 包的版本,是否需要更新 npm 包
    import { name, version } from "../../package.json"
    import { gt } from "lodash"
    import axios, { AxiosResponse } from "axios"
    import chalk from "chalk"
    
    // 获取 npm 包的最新版本
    const getNpmInfo = async (cliName: string) => {
      const npmUrl = `https://registry.npmjs.org/${cliName}`
      let res = {}
      try {
    res = await axios.get(npmUrl)
      } catch (e) {
    console.log(e)
      }
      return res
    }
    const getNpmLatestVersion = async (cliName: string) => {
      const { data } = (await getNpmInfo(cliName)) as AxiosResponse
      return data["dist-tags"].latest
    }
    
    // 检查版本
    export const checkVersion = async () => {
      const lastestVersion = await getNpmLatestVersion(name)
      const needUpdate = gt(lastestVersion, version)
      if (needUpdate) {
    console.warn(
      `${chalk.greenBright(name)} 版本需要更新,当前版本:${chalk.blueBright(
        version
      )}, 最新版本:${chalk.blueBright(lastestVersion)}`
    )
    console.log(
      `可使用${chalk.yellow(
        "npm install benchu-cli@latest -g"
      )} 或 ${chalk.yellow("benchu update")}更新`
    )
      }
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

手动修改 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()
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

command/update.ts文件中通过process获取最新的包,并利用child_process这一核心组件来创建与主进程隔离的子进程。该组件不仅能够执行系统命令、脚本或其他可执行文件的操作,还可以通过隔离的方式确保操作的安全性和稳定性。

复制代码
    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("更新成功"))
    }
      })
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

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
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

原先的 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("代码下载失败!"))
      }
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码
在这里插入图片描述

持续改进,并借助 figlet 来生成具有打印效果的文字以提升显示效果。参考 figlet官方文档 获取详细信息。例如:

在这里插入图片描述

安装 figlet 及其类型声明文件

复制代码
    pnpm install figlet @types/figlet
    
    
      
    
    AI写代码

注意:要添加到生产依赖

在这里插入图片描述

clone.ts

复制代码
    const goodPrinter = async (message: string) => {
      const data = await figlet(message)
      console.log(chalk.rgb(40, 156, 193).visible(data))
    }
    
    
      
      
      
      
    
    AI写代码
在这里插入图片描述
在这里插入图片描述

结语

该文所展示的案例已同步至GitHub平台,并可访问地址为:github地址

全部评论 (0)

还没有任何评论哟~