普通视图

发现新文章,点击刷新页面。
今天 — 2025年11月28日首页

Monorepo 架构以及工具选型、搭建

作者 颜酱
2025年11月28日 19:56

Monorepo(Monolithic Repository,单体仓库)是一种代码管理策略,核心是将一个项目的所有相关代码(包括多个应用、库、工具链等)集中存储在单个代码仓库中,而非按模块拆分到多个独立仓库(Multirepo)。

📑 目录


快速参考

工具选型速查表

工具类型 推荐工具 适用场景 备选方案
包管理器 pnpm workspace 磁盘效率高、安装速度快 npm workspace、yarn workspace
任务调度 Turbo 增量构建、并行任务、缓存 Nx(企业级)、Rush(大型项目)
版本管理 Changeset monorepo 友好的版本管理 release-it(单包)、Lerna(传统)

快速开始

# 1. 初始化项目
mkdir your-project && cd your-project
pnpm init -y

# 2. 安装 Turbo
pnpm add turbo -D -w

# 3. 配置工作区
# 创建 pnpm-workspace.yaml

# 4. 配置 Turbo
# 创建 turbo.json

# 5. 创建子包
mkdir -p packages/core docs examples/basic

什么是 Monorepo

简单类比

  • Multirepo:像多个独立的文件夹,每个项目 / 库单独存放(比如 reactreact-domreact-router 各一个仓库)
  • Monorepo:像一个大文件夹,里面按功能分类存放所有相关项目(比如 Facebook 的 facebook/react 仓库,包含 React 核心、文档、示例、相关工具等所有代码)

常用结构

monorepo-root/
├── packages/          # 所有可复用包(库、工具)
│   ├── utils/         # 通用工具库
│   ├── components/     # UI 组件库
│   └── cli/           # 命令行工具
├── apps/              # 可部署应用
│   ├── web/           # 网页应用
│   └── admin/         # 管理后台
├── scripts/           # 全局构建/测试脚本
├── package.json       # 根项目配置(依赖、脚本)
└── pnpm-workspace.yaml # 工作区配置(pnpm 为例)

Monorepo vs Multirepo

对比维度 Multirepo(多仓库) Monorepo(单仓库)
依赖管理 重复安装,版本不一致,易冲突 共享依赖,版本统一,减少冗余
跨项目引用 需发布 npm / 用相对路径,同步修改繁琐 本地直接引用,修改实时生效,无需发布
工程化规范 各仓库独立配置,维护成本高 根目录统一配置,所有子项目继承
代码复用 复制粘贴或发布私有包,复用成本高 仓库内直接复用,抽离库更便捷
版本管理与发布 手动协调多包版本(如 A 依赖 B,B 升级后 A 需手动更新) 工具自动管理版本依赖(如 Changeset),批量发布
协作效率 跨仓库 PR 联动复杂,代码审查分散 所有代码在一个仓库,PR 集中,协作更高效

Monorepo 的优缺点

优点

  • 高效协作:所有代码集中管理,跨项目修改无需切换仓库,PR 集中审查
  • 规范统一:工程化配置(lint、测试、构建)全局统一,降低维护成本
  • 依赖优化:共享依赖减少安装体积,版本统一避免冲突
  • 代码复用:子包间直接引用,无需发布,迭代速度快

缺点

  • 仓库体积增大:随着项目增多,仓库体积会变大,但现代 Git(如 Git LFS)可缓解
  • 构建速度:大型 Monorepo 全量构建较慢,需借助 Turborepo 等工具实现增量构建和缓存
  • 权限控制:难以对单个子包进行精细化权限控制(如需控制,可结合 Git 子模块或企业级工具如 GitLab Enterprise)

经典案例

前端 Monorepo 经典案例:

  • Reactfacebook/react 仓库包含 React 核心、React DOM、React Server Components 等所有相关代码
  • Vuevuejs/core 仓库包含 Vue 3 核心、编译器、运行时等
  • Vitevitejs/vite 仓库包含 Vite 核心、官方插件(如 @vitejs/plugin-react)等
  • Tailwind CSStailwindlabs/tailwindcss 仓库包含核心库、CLI、插件等

何时使用 Monorepo

当你的项目满足以下 2 个及以上条件时,优先选择 Monorepo:

  • ✅ 需拆分独立模块(核心包 + 文档 + 示例是典型场景)
  • ✅ 模块间有依赖关系(如示例依赖核心包、文档引用核心包 API)
  • ✅ 需统一构建、测试、发布流程
  • ✅ 追求高效开发(增量构建、并行任务)

何时不使用 Monorepo

  • ❌ 单一核心包 + 简单 README 文档(单包架构足够)
  • ❌ 子包之间完全独立,无依赖关系
  • ❌ 团队规模小,维护成本高

工具选型

工作区管理工具

负责管理多包的依赖安装、路径映射、脚本执行,主流选择:

工具 磁盘效率 安装速度 monorepo 支持 适用场景
pnpm workspace ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 推荐,生态最优
npm workspace ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ npm 7+ 原生支持
yarn workspace ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐ Yarn 1.x 传统方案
Lerna ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐ 早期方案,已过时

选型建议

  • 推荐:pnpm workspace(磁盘效率高、安装速度快、生态完善)
  • 备选:npm workspace(npm 7+ 原生支持,无需额外配置)

核心特性

  • pnpm workspace:轻量、快速,原生支持 Monorepo,通过 pnpm-workspace.yaml 配置子项目路径,自动处理包之间的软链接,安装依赖时复用缓存,效率极高
  • Yarn Workspaces:与 pnpm 功能类似,支持 workspace:* 语法声明内部依赖
  • Lerna:早期流行的 Monorepo 工具,可搭配 npm/yarn 使用,擅长版本管理和发布,但依赖安装效率不如 pnpm

任务调度工具

需支持「多包构建」「增量构建」「依赖顺序构建」,避免每次全量构建:

工具 增量构建 并行任务 缓存机制 配置复杂度 适用场景
Turbo ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ 中小型项目(推荐)
Nx ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐ 大型企业级项目
Rush ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐ 超大型项目

选型建议

  • 中小型项目:Turbo(推荐,配置简单,性能优秀)
  • 大型企业级项目:Nx(功能强大,但配置复杂)
  • 超大型项目:Rush(微软开源,适合超大型 monorepo)

核心特性

  • Turbo:高性能构建系统,可缓存构建结果,并行执行任务(构建、测试、lint),大幅提升大型 Monorepo 的构建速度
  • tsup:支持多入口、增量构建,适配 TypeScript 项目,可快速构建多个子包
  • Rollup/Vite:适合构建库或应用,支持 Tree-shaking,Vite 还能提供开发时热更新

版本管理工具

解决多包版本联动、CHANGELOG 自动生成、npm 发布等问题:

工具 monorepo 支持 多包版本同步 自动化程度 配置复杂度 适用场景
Changeset ⭐⭐⭐⭐⭐ ✅ 自动同步 ⭐⭐⭐ ⭐⭐⭐ 多包项目(推荐)
release-it ⭐⭐⭐ ❌ 需手动 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ 单包项目、简单场景
Lerna ⭐⭐⭐ ✅ 支持 ⭐⭐⭐ ⭐⭐ 传统方案

选型建议

  • 多包项目、需要版本同步:Changeset(推荐)
  • 单包项目、追求自动化:release-it
  • 简单场景、快速发布:release-it

核心特性

  • Changeset:轻量、易用,支持按子包提交变更记录,自动计算版本号(语义化版本),生成 CHANGELOG,批量发布子包
  • release-it:可搭配 Changeset 使用,提供交互式发布流程,支持 GitHub 标签、发布说明等

代码质量工具

统一管理 lint、测试、格式化:

工具类型 推荐工具 核心功能
代码规范 ESLint + Prettier 根目录配置,所有子项目共享规则,可通过 eslint-config-xxx 抽离自定义规则
测试框架 Vitest / Jest 统一测试框架,支持跨包测试,可在根目录运行所有子项目的测试用例
Git Hooks Husky + lint-staged 提交代码前自动执行 lint 和测试,保障代码质量

实战搭建

以下是最简搭建流程,基于 pnpm(生态最优)+ Turbo + tsup(核心包打包)+ VitePress(文档)+ Vitest(测试)

1. 初始化基础环境

# 创建项目根目录
mkdir your-project && cd your-project

# 初始化根目录 package.json
pnpm init -y

# 安装 Turbo(任务调度)
pnpm add turbo -D -w  # -w 表示安装到根目录(workspace-root)

2. 配置 pnpm 工作区

创建 pnpm-workspace.yaml

# pnpm-workspace.yaml
packages:
  - 'packages/*' # 核心包(可发布)
  - 'docs' # 文档站点(不发布)
  - 'examples/*' # 示例项目(不发布)

3. 配置 Turbo

创建 turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["package.json", "turbo.json"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**", "build/**", ".vitepress/dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    },
    "lint": {
      "outputs": []
    },
    "clean": {
      "cache": false
    }
  }
}

配置说明

  • dependsOn: ["^build"]:构建前先构建依赖的子包
  • outputs:指定构建输出目录,用于缓存判断
  • cache: false:开发模式不缓存,避免热更新问题
  • persistent: true:开发模式持续运行(如 watch 模式)

4. 根目录 package.json 配置

{
  "name": "your-project-monorepo",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "test": "turbo run test",
    "lint": "turbo run lint",
    "clean": "turbo run clean && rm -rf node_modules",
    "format": "prettier --write \"**/*.{ts,tsx,md,json}\""
  },
  "devDependencies": {
    "turbo": "^2.0.0",
    "prettier": "^3.0.0"
  },
  "packageManager": "pnpm@9.0.0",
  "engines": {
    "node": ">=18.0.0",
    "pnpm": ">=9.0.0"
  }
}

5. 搭建核心包

# 创建核心包目录并初始化
mkdir -p packages/core && cd packages/core
pnpm init -y

# 安装核心依赖(共享依赖安装到根目录)
pnpm add -D tsup typescript vitest @types/node -w

核心包 package.json

{
  "name": "@your-org/core",
  "version": "1.0.0",
  "description": "核心功能包",
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    }
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch",
    "test": "vitest run",
    "test:watch": "vitest",
    "lint": "eslint src/**/*.ts",
    "clean": "rm -rf dist coverage"
  },
  "keywords": ["core", "utils"],
  "license": "MIT"
}

核心包 tsup.config.ts

import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm', 'cjs'],
  dts: true,
  clean: true,
  sourcemap: true,
  minify: true,
  splitting: false,
});

核心包 src/index.ts

export const greet = (name: string) => {
  return `Hello, ${name}!`;
};

export const add = (a: number, b: number) => {
  return a + b;
};

6. 搭建文档站点

# 创建文档目录并初始化
mkdir docs && cd docs
pnpm init -y

# 安装文档依赖
pnpm add -D vitepress @vitepress/theme-default -w

# 初始化 VitePress 文档
npx vitepress init

文档 package.json

{
  "name": "@your-org/docs",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "vitepress dev",
    "build": "vitepress build",
    "preview": "vitepress preview",
    "lint": "eslint . --ext .md,.ts",
    "clean": "rm -rf .vitepress/dist"
  },
  "dependencies": {
    "@your-org/core": "workspace:*"
  }
}

在 Markdown 中可直接导入核心包,用于示例演示:

# 快速使用

```ts
import { greet } from '@your-org/core';

console.log(greet('World')); // Hello, World!
```

7. 搭建示例项目

# 创建示例目录并初始化
mkdir -p examples/basic && cd examples/basic
pnpm init -y

# 安装依赖(使用工作区协议引用核心包)
pnpm add @your-org/core@workspace:* -w
pnpm add -D vite @vitejs/plugin-react -w

示例 package.json

{
  "name": "@your-org/example-basic",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "clean": "rm -rf dist"
  },
  "dependencies": {
    "@your-org/core": "workspace:*"
  }
}

8. 核心命令使用

命令 作用
pnpm run dev 同时启动核心包监听、文档热更新、示例热更新
pnpm run build 一键构建核心包(ESM/CJS + 类型)、文档静态资源、示例
pnpm run test 运行所有子包的测试(核心包单元测试、示例冒烟测试)
pnpm run lint 统一校验所有子包的代码规范
pnpm run clean 清理所有子包的构建产物和缓存

增量构建效果示例

  • 首次执行 pnpm run build:构建所有子包(core + docs + examples)
  • 修改核心包代码后再次执行 pnpm run build:仅重建 core 和依赖它的 examples,docs 未变更则直接复用缓存,构建速度提升 50%+

过滤特定子包执行任务

# 只构建核心包
pnpm run build --filter=@your-org/core

# 只构建文档和示例
pnpm run build --filter=@your-org/docs --filter=@your-org/example-basic

# 构建核心包及其依赖者
pnpm run build --filter=@your-org/core...

9. 版本管理与发布

使用 Changeset(推荐,适合多包版本同步)

Changeset 完全支持 Turbo monorepo,可仅发布核心包(packages/core),文档和示例不发布,并支持多包版本同步:

优势

  • ✅ 专为 monorepo 设计,支持多包版本同步
  • ✅ 自动更新依赖包的版本号
  • ✅ 变更记录清晰,便于追溯

劣势

  • ❌ 需要手动记录变更(npx changeset
  • ❌ 流程相对复杂

安装与配置

# 安装 Changeset 并初始化
pnpm add @changesets/cli -D -w
npx changeset init

修改 Changeset 配置(.changeset/config.json)

{
  "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": ["@your-org/docs", "@your-org/example-basic"]
}

发布流程

# 1. 记录核心包变更(仅选择 @your-org/core)
npx changeset

# 2. 升级版本 + 生成 CHANGELOG
npx changeset version

# 3. 构建核心包
pnpm run build --filter=@your-org/core

# 4. 发布核心包
pnpm publish --filter=@your-org/core --access public

使用 release-it(适合单包发布或简单场景)

release-it 也可以用于 monorepo,但主要用于单包发布场景。

优势

  • ✅ 自动化程度高,一条命令完成发布
  • ✅ 配置灵活,支持自定义发布流程
  • ✅ 支持 GitHub Release、npm 发布等

劣势

  • ❌ 不支持多包版本同步(需要手动处理)
  • ❌ 需要为每个包单独配置或使用脚本

方式一:在子包中单独配置(推荐)

在需要发布的子包中配置 release-it:

# 在核心包目录下
cd packages/core
pnpm add release-it @release-it/conventional-changelog -D

核心包 release-it.config.js

module.exports = {
  git: {
    commitMessage: 'chore: release @your-org/core@${version}',
    tagName: '@your-org/core@${version}',
    requireCleanWorkingDir: false,
    requireBranch: 'main',
    requireCommits: true,
  },
  github: {
    release: true,
    releaseName: '@your-org/core@${version}',
  },
  npm: {
    publish: true,
    publishPath: './',
  },
  hooks: {
    'before:init': ['pnpm run test'],
    'after:bump': ['pnpm run build'],
    'after:release': 'echo "Release @your-org/core@${version} completed!"',
  },
  plugins: {
    '@release-it/conventional-changelog': {
      preset: 'angular',
      infile: 'CHANGELOG.md',
    },
  },
};

核心包 package.json scripts

{
  "scripts": {
    "release": "release-it",
    "release:patch": "release-it patch",
    "release:minor": "release-it minor",
    "release:major": "release-it major"
  }
}

发布流程

# 在核心包目录下
cd packages/core
pnpm run release

方式二:在根目录统一配置(适合单包发布)

在根目录配置,配合 pnpm filter 使用:

# 在根目录安装
pnpm add release-it @release-it/conventional-changelog -D -w

根目录 release-it.config.js

module.exports = {
  git: {
    commitMessage: 'chore: release v${version}',
    tagName: 'v${version}',
    requireCleanWorkingDir: false,
    requireBranch: 'main',
  },
  hooks: {
    'before:init': ['pnpm run test --filter=@your-org/core'],
    'after:bump': ['pnpm run build --filter=@your-org/core'],
  },
  plugins: {
    '@release-it/conventional-changelog': {
      preset: 'angular',
      infile: 'CHANGELOG.md',
    },
  },
};

根目录 package.json scripts

{
  "scripts": {
    "release": "release-it",
    "release:core": "cd packages/core && pnpm run release"
  }
}

Changeset vs release-it 对比

特性 Changeset release-it
monorepo 支持 ⭐⭐⭐⭐⭐(专为 monorepo 设计) ⭐⭐⭐(需手动配置)
多包版本同步 ✅ 自动同步 ❌ 需手动处理
自动化程度 ⭐⭐⭐(需手动记录变更) ⭐⭐⭐⭐⭐(一条命令)
配置复杂度 ⭐⭐⭐ ⭐⭐⭐⭐
适用场景 多包项目、版本同步需求 单包项目、简单场景

选型建议

  • 多包项目、需要版本同步:Changeset(推荐)
  • 单包项目、追求自动化:release-it
  • 简单场景、快速发布:release-it

高级配置

Turbo 缓存优化

1. 配置远程缓存(可选)

Turbo 支持远程缓存,团队共享构建缓存:

# 安装 Turbo 远程缓存客户端
pnpm add turbo -D -w

# 登录 Vercel(免费提供远程缓存)
npx turbo login

# 链接项目
npx turbo link

2. 优化缓存配置

{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"],
      "env": ["NODE_ENV"], // 环境变量变化时重新构建
      "inputs": ["src/**/*.ts", "tsup.config.ts"] // 指定输入文件
    }
  }
}

CI/CD 集成

GitHub Actions 示例

创建 .github/workflows/ci.yml

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with:
          version: 9

      - uses: actions/setup-node@v4
        with:
          node-version: 18
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install

      - name: Build
        run: pnpm run build

      - name: Test
        run: pnpm run test

      - name: Lint
        run: pnpm run lint

依赖管理策略

1. 公共依赖提升到根目录

# 安装公共依赖到根目录
pnpm add -D typescript eslint prettier -w

# 子包无需重复安装,直接使用

2. 子包间依赖使用工作区协议

{
  "dependencies": {
    "@your-org/core": "workspace:*" // ✅ 正确
    // "@your-org/core": "1.0.0"     // ❌ 错误,无法实时同步
  }
}

3. 版本统一管理

使用 .npmrc 统一配置:

# .npmrc
shamefully-hoist=true
strict-peer-dependencies=false

最佳实践

  1. 子包命名规范:使用 scope(如 @your-org/core),避免命名冲突
  2. 依赖管理:公共依赖提升到根目录,子包间使用 workspace:* 协议
  3. 任务配置:在 turbo.json 中准确配置 outputs,确保缓存生效
  4. 版本管理:使用 Changeset 管理版本,仅发布需要发布的包
  5. 目录结构:按功能拆分(packages、docs、examples),保持清晰
  6. 避免过度拆分:子包数量控制在 5 个以内,过多会增加配置复杂度
  7. 开发体验:使用 pnpm run dev 同时启动所有子包的开发模式

常见问题

工作区相关问题

Q: 子包间依赖如何引用?

A: 使用工作区协议 workspace:*

{
  "dependencies": {
    "@your-org/core": "workspace:*"
  }
}

Q: 如何安装依赖到特定子包?

A:

# 安装到根目录
pnpm add -D typescript -w

# 安装到特定子包
pnpm add -D vite --filter @your-org/example-basic

# 安装到所有子包
pnpm add -D eslint --filter "./packages/*"

Q: 如何查看所有子包?

A:

pnpm list -r --depth=0

Turbo 相关问题

Q: Turbo 缓存不生效怎么办?

A:

  1. 检查 turbo.json 中的 outputs 配置是否正确
  2. 检查构建输出目录是否在 outputs 中声明
  3. 清理缓存:pnpm run clean && rm -rf .turbo

Q: 如何只构建变更的子包?

A: Turbo 默认就是增量构建,只需执行 pnpm run build,Turbo 会自动判断哪些子包需要重新构建。

Q: 如何跳过缓存强制构建?

A:

pnpm run build --force

Q: 如何查看构建依赖关系?

A:

# 查看任务依赖图
npx turbo run build --graph

版本管理相关问题

Q: Changeset 如何只发布特定包?

A: 在 .changeset/config.json 中配置 ignore 字段,或在执行 npx changeset 时只选择需要发布的包。

Q: 如何自动化发布流程?

A: 使用 GitHub Actions + Changeset,参考 Changeset 文档

Q: release-it 能否用于 monorepo?

A: 可以,但主要用于单包发布场景。如果项目只有一个包需要发布,release-it 更简单;如果需要多包版本同步,建议使用 Changeset。

Q: release-it 如何发布 monorepo 中的特定包?

A:

# 方式一:在子包目录下执行
cd packages/core
pnpm run release

# 方式二:使用 pnpm filter
pnpm --filter @your-org/core run release

性能优化相关问题

Q: 如何提升构建速度?

A:

  1. 配置 Turbo 远程缓存(团队共享)
  2. 优化 turbo.jsonoutputs 配置
  3. 使用 dependsOn 合理配置任务依赖
  4. 避免不必要的任务依赖

Q: 如何减少 node_modules 体积?

A:

  1. 使用 pnpm(默认使用符号链接,节省磁盘空间)
  2. 公共依赖提升到根目录
  3. 使用 .npmrc 配置 shamefully-hoist=false

参考资源


文档版本:v2.0
最后更新:2024 年

CLI 工具开发的常用包对比和介绍

作者 颜酱
2025年11月28日 14:39

04-CLI 工具开发

CLI 工具开发涉及命令行交互、终端美化、文件操作和模板生成等核心功能。

📑 目录


快速参考

工具选型速查表

工具类型 推荐工具 适用场景 备选方案
命令行解析 commander 功能丰富、API 友好 yargs(灵活)、meow(轻量)
交互式输入 inquirer 功能全面、生态丰富 prompts(轻量)、enquirer
终端美化 chalk 功能丰富、API 友好 picocolors(极轻量)、kleur
加载动画 ora 简单易用 -
进度条 cli-progress 文件上传/下载进度 -
文件操作 fs-extra Promise API、功能增强 -
文件匹配 glob 通配符匹配 -
模板引擎 handlebars 轻量、逻辑少 ejs、mustache

快速开始

# 1. 安装核心工具
pnpm add commander inquirer chalk ora fs-extra glob handlebars

# 2. 创建 CLI 入口文件
# src/cli.ts

# 3. 配置 package.json bin 字段

命令行交互

commander(推荐)

commander 是一款 Node.js 命令行解析工具,核心用途是解析命令行参数,让 CLI 工具的命令行交互更友好、专业。

优势

  • ✅ API 友好,链式调用
  • ✅ 功能丰富,支持子命令、选项、帮助信息
  • ✅ 生态完善,文档详细
  • ✅ 自动生成帮助信息

劣势

  • ❌ 体积较大(相比 meow)
安装
pnpm add commander
pnpm add @types/commander -D
基础用法
// src/cli.ts
import { program } from 'commander';

program
  .version('1.0.0', '-v, --version')
  .description('一个基于 commander + inquirer 的 CLI 工具示例');

// 定义无参数命令
program
  .command('init')
  .description('初始化项目')
  .action(() => {
    console.log('开始初始化项目...');
  });

// 定义带选项的命令
program
  .command('build')
  .description('打包项目')
  .option('-e, --env <env>', '打包环境', 'development')
  .option('-o, --outDir <dir>', '输出目录', 'dist')
  .action((options) => {
    console.log('开始打包...');
    console.log('打包环境:', options.env);
    console.log('输出目录:', options.outDir);
  });

program.parse(process.argv);
选项配置
选项格式 说明 示例
.option('-s, --single') 布尔型选项(无参数,存在即 true) your-cli --single{ single: true }
.option('-n, --name <name>') 必填参数选项 your-cli --name test{ name: 'test' }
.option('-a, --age [age]') 可选参数选项 your-cli --age 25{ age: 25 };不传则为 undefined
.option('--env <env>', '描述', 'dev') 带默认值的选项 不传 --env 时,默认 { env: 'dev' }
高级用法
// 子命令
program
  .command('create <name>')
  .description('创建新项目')
  .option('-t, --template <template>', '模板类型', 'default')
  .action((name, options) => {
    console.log(`创建项目 ${name},使用模板 ${options.template}`);
  });

// 必需选项
program.requiredOption('-c, --config <path>', '配置文件路径').parse();

// 自定义帮助信息
program.addHelpText('after', '\n示例:\n  $ my-cli init\n  $ my-cli build --env production');

yargs

yargs 是功能强大的命令行解析工具,支持位置参数、命令补全等高级功能。

优势

  • ✅ 功能强大,支持位置参数
  • ✅ 灵活的配置方式
  • ✅ 支持命令补全

劣势

  • ❌ API 相对复杂
  • ❌ 学习曲线较陡
安装
pnpm add yargs
pnpm add @types/yargs -D
基础用法
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';

const argv = yargs(hideBin(process.argv))
  .option('name', {
    alias: 'n',
    type: 'string',
    description: '项目名称',
    demandOption: true,
  })
  .option('template', {
    alias: 't',
    type: 'string',
    default: 'default',
    description: '模板类型',
  })
  .command('init <name>', '初始化项目', (yargs) => {
    return yargs.positional('name', {
      describe: '项目名称',
      type: 'string',
    });
  })
  .parseSync();

console.log(argv);

meow

meow 是轻量级的命令行解析工具,适合简单场景。

优势

  • ✅ 轻量级,体积小
  • ✅ 配置简单
  • ✅ 自动处理帮助信息

劣势

  • ❌ 功能相对简单
  • ❌ 不支持复杂命令结构
安装
pnpm add meow
基础用法
import meow from 'meow';

const cli = meow(
  `
  用法
    $ my-cli <input>

  选项
    --name, -n  项目名称
    --template, -t  模板类型

  示例
    $ my-cli init --name my-project
`,
  {
    importMeta: import.meta,
    flags: {
      name: {
        type: 'string',
        alias: 'n',
      },
      template: {
        type: 'string',
        alias: 't',
        default: 'default',
      },
    },
  },
);

console.log(cli.input[0]); // 命令参数
console.log(cli.flags); // 选项

命令行解析工具对比

工具 体积 配置复杂度 功能丰富度 适用场景
commander 较大 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 功能丰富的 CLI 工具
yargs 较大 ⭐⭐ ⭐⭐⭐⭐⭐ 需要位置参数的场景
meow 轻量级 ⭐⭐⭐⭐⭐ ⭐⭐⭐ 简单 CLI 工具

选型建议

  • 功能丰富的 CLI 工具:commander(推荐)
  • 需要位置参数:yargs
  • 简单工具:meow

交互式输入

inquirer(推荐)

当命令行参数无法满足需求(如让用户选择框架、输入密码、确认操作)时,inquirer 提供「交互式输入」。

优势

  • ✅ 功能全面,支持多种交互类型
  • ✅ 生态丰富,插件多
  • ✅ 文档完善

劣势

  • ❌ 体积较大
  • ❌ 配置相对复杂
安装
pnpm add inquirer
pnpm add @types/inquirer -D
基础用法
import { program } from 'commander';
import inquirer from 'inquirer';
import chalk from 'chalk';

program
  .command('init')
  .description('初始化项目')
  .action(async () => {
    console.log(chalk.blue('📦 开始初始化项目...'));

    const answers = await inquirer.prompt([
      {
        type: 'input',
        name: 'projectName',
        message: '请输入项目名称:',
        default: 'my-project',
        validate: (value) => {
          if (!value.trim()) return '项目名称不能为空!';
          return true;
        },
      },
      {
        type: 'list',
        name: 'framework',
        message: '请选择项目框架:',
        choices: [
          { name: 'React + TypeScript', value: 'react-ts' },
          { name: 'Vue + TypeScript', value: 'vue-ts' },
          { name: 'Vanilla JS', value: 'vanilla' },
        ],
        default: 'react-ts',
      },
      {
        type: 'checkbox',
        name: 'modules',
        message: '请选择需要的功能模块:',
        choices: ['路由', '状态管理', 'UI 组件库', 'ESLint/Prettier'],
        default: ['路由', 'ESLint/Prettier'],
      },
      {
        type: 'confirm',
        name: 'initGit',
        message: '是否初始化 Git 仓库?',
        default: true,
      },
    ]);

    console.log(chalk.green('\n✅ 项目配置如下:'));
    console.log('项目名称:', answers.projectName);
    console.log('框架:', answers.framework);
    console.log('功能模块:', answers.modules.join(', '));
    console.log('初始化 Git:', answers.initGit ? '是' : '否');
  });

program.parse(process.argv);
核心交互类型
类型 用途 关键配置
input 普通文本输入(如项目名称、邮箱) message、default、validate
password 密码输入(输入内容隐藏) 同 input,自动隐藏输入
list 单选(如框架选择、环境选择) choices(选项数组)、default
checkbox 多选(如功能模块、依赖选择) choices、default(默认选中项)
confirm 二选一确认(是 / 否) message、default(true/false)
rawlist 带编号的单选(按数字选择) 同 list,选项前显示编号
autocomplete 带自动补全的输入(如文件路径) 需配合 inquirer-autocomplete-prompt 插件

prompts

prompts 是轻量级的交互式输入工具,API 简洁。

优势

  • ✅ 轻量级,体积小
  • ✅ API 简洁
  • ✅ 支持取消操作(Ctrl+C)

劣势

  • ❌ 功能相对简单
  • ❌ 生态较小
安装
pnpm add prompts
基础用法
import prompts from 'prompts';

const response = await prompts([
  {
    type: 'text',
    name: 'projectName',
    message: '项目名称',
    initial: 'my-project',
    validate: (value) => (value.trim() ? true : '项目名称不能为空'),
  },
  {
    type: 'select',
    name: 'framework',
    message: '选择框架',
    choices: [
      { title: 'React', value: 'react' },
      { title: 'Vue', value: 'vue' },
    ],
  },
]);

console.log(response);

enquirer

enquirer 是现代化的交互式输入工具,支持自定义提示符。

优势

  • ✅ 现代化设计
  • ✅ 支持自定义提示符
  • ✅ API 灵活

劣势

  • ❌ 文档相对较少
  • ❌ 生态较小
安装
pnpm add enquirer
基础用法
import { prompt } from 'enquirer';

const response = await prompt({
  type: 'input',
  name: 'projectName',
  message: '项目名称',
  initial: 'my-project',
});

console.log(response);

交互式输入工具对比

工具 体积 配置复杂度 功能丰富度 生态 适用场景
inquirer 较大 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 功能全面的 CLI 工具
prompts 轻量级 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ 简单交互场景
enquirer 中等 ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ 需要自定义提示符

选型建议

  • 功能全面的 CLI 工具:inquirer(推荐)
  • 简单交互场景:prompts
  • 需要自定义提示符:enquirer

终端美化

chalk(推荐)

chalk 是 Node.js 终端日志彩色打印工具,核心用途是给终端输出的文字添加颜色、背景色、加粗 / 下划线等样式。

优势

  • ✅ 功能丰富,API 友好
  • ✅ 支持链式调用
  • ✅ 生态完善

劣势

  • ❌ 体积较大(相比 picocolors)
安装
pnpm add chalk
封装日志函数
// src/utils/logger.ts
import chalk from 'chalk';

export enum LogType {
  SUCCESS = 'success',
  ERROR = 'error',
  WARN = 'warn',
  INFO = 'info',
}

export const log = (message: string, type: LogType = LogType.INFO) => {
  const prefixMap = {
    [LogType.SUCCESS]: chalk.green('✅'),
    [LogType.ERROR]: chalk.bold.red('❌'),
    [LogType.WARN]: chalk.yellow('⚠️'),
    [LogType.INFO]: chalk.blue('ℹ️'),
  };

  const colorMap = {
    [LogType.SUCCESS]: chalk.green,
    [LogType.ERROR]: chalk.red,
    [LogType.WARN]: chalk.yellow,
    [LogType.INFO]: chalk.blue,
  };

  const prefix = prefixMap[type];
  const color = colorMap[type];
  console.log(`${prefix} ${color(message)}`);
};

export const logSuccess = (message: string) => log(message, LogType.SUCCESS);
export const logError = (message: string) => log(message, LogType.ERROR);
export const logWarn = (message: string) => log(message, LogType.WARN);
export const logInfo = (message: string) => log(message, LogType.INFO);
使用
import { logSuccess, logError, logWarn, logInfo } from './utils/logger';

logInfo('正在初始化项目...');
logWarn('当前 Node 版本低于推荐版本');
logSuccess('项目初始化完成!');
logError('配置文件缺失,请检查 config.json');

picocolors

picocolors 是极轻量的终端颜色库,体积仅 0.5KB。

优势

  • ✅ 极轻量(0.5KB)
  • ✅ API 简洁
  • ✅ 性能好

劣势

  • ❌ 功能相对简单
  • ❌ 不支持链式调用
安装
pnpm add picocolors
基础用法
import pc from 'picocolors';

console.log(pc.green('成功'));
console.log(pc.red('错误'));
console.log(pc.bold(pc.blue('加粗蓝色')));

kleur

kleur 是轻量级的终端颜色库,API 类似 chalk。

优势

  • ✅ 轻量级(体积小)
  • ✅ API 类似 chalk,迁移成本低
  • ✅ 支持链式调用

劣势

  • ❌ 功能相对简单
安装
pnpm add kleur
基础用法
import kleur from 'kleur';

console.log(kleur.green('成功'));
console.log(kleur.red('错误'));
console.log(kleur.bold().blue('加粗蓝色'));

ora(展示加载动画)

ora 是一款 Node.js 终端加载动画工具,核心用途是在耗时操作时显示「加载中」动画 + 提示文字。

安装
pnpm add ora
封装加载动画函数
// src/utils/loader.ts
import ora from 'ora';
import chalk from 'chalk';

export const withLoader = async <T>(message: string, asyncFn: () => Promise<T>): Promise<T> => {
  const spinner = ora(chalk.bold.blue(message)).start();
  try {
    const result = await asyncFn();
    spinner.succeed(chalk.green('✅ 操作完成!'));
    return result;
  } catch (error) {
    spinner.fail(chalk.bold.red(`❌ 操作失败:${(error as Error).message}`));
    throw error;
  }
};
使用示例
import { withLoader } from './utils/loader';

await withLoader('正在请求接口数据...', async () => {
  await new Promise((resolve) => setTimeout(resolve, 1500));
});
高级用法
import ora from 'ora';

const spinner = ora('加载中...').start();

// 更新文本
spinner.text = '处理中...';

// 成功
spinner.succeed('完成!');

// 失败
spinner.fail('失败!');

// 警告
spinner.warn('警告!');

// 信息
spinner.info('信息');

cli-progress(进度条)

cli-progress 用于显示文件上传/下载、批量处理等操作的进度。

安装
pnpm add cli-progress
基础用法
import cliProgress from 'cli-progress';

const bar = new cliProgress.SingleBar({
  format: '进度 |{bar}| {percentage}% | {value}/{total}',
  barCompleteChar: '\u2588',
  barIncompleteChar: '\u2591',
  hideCursor: true,
});

bar.start(100, 0);

// 模拟进度
for (let i = 0; i <= 100; i++) {
  await new Promise((resolve) => setTimeout(resolve, 50));
  bar.update(i);
}

bar.stop();

boxen(边框框)

boxen 用于在终端中创建带边框的文本框,适合显示重要信息。

安装
pnpm add boxen
基础用法
import boxen from 'boxen';
import chalk from 'chalk';

const message = boxen(chalk.green('✅ 项目初始化完成!'), {
  padding: 1,
  margin: 1,
  borderStyle: 'round',
  borderColor: 'green',
});

console.log(message);

cli-table3(表格展示)

cli-table3 用于在终端中展示表格数据,适合显示配置信息、对比数据等。

安装
pnpm add cli-table3
pnpm add @types/cli-table3 -D
基础用法
import Table from 'cli-table3';

const table = new Table({
  head: ['工具', '用途', '推荐度'],
  colWidths: [20, 30, 10],
});

table.push(
  ['commander', '命令行解析', '⭐⭐⭐⭐⭐'],
  ['inquirer', '交互式输入', '⭐⭐⭐⭐⭐'],
  ['chalk', '终端美化', '⭐⭐⭐⭐⭐'],
);

console.log(table.toString());

终端美化工具对比

工具 体积 功能 适用场景
chalk 较大 颜色、样式 功能丰富的 CLI 工具
picocolors 极轻量 基础颜色 对体积敏感的项目
kleur 轻量级 颜色、样式 轻量级 CLI 工具
ora 中等 加载动画 耗时操作提示
cli-progress 中等 进度条 文件处理进度
boxen 轻量级 边框框 重要信息展示
cli-table3 中等 表格 数据展示

选型建议

  • 功能丰富的 CLI 工具:chalk(推荐)
  • 对体积敏感:picocolors
  • 需要进度条:cli-progress
  • 需要表格展示:cli-table3

文件操作

fs-extra(操作文件系统)

fs-extra 在 Node.js 原生 fs 模块基础上做了增强,核心优势:

  • 完全兼容原生 fs 模块(可直接替换 fs 使用)
  • 所有 API 支持 Promise(无需手动封装 util.promisify)
  • 新增高频实用功能(递归创建目录、递归删除目录、复制文件 / 目录等)
安装
pnpm add fs-extra
pnpm add @types/fs-extra -D
核心用法
import fs from 'fs-extra';
import path from 'path';

// 递归创建目录
await fs.ensureDir(path.resolve(__dirname, 'a/b/c'));

// 递归删除目录
await fs.remove(path.resolve(__dirname, 'a'));

// 复制文件/目录
await fs.copy(src, dest);

// 写入 JSON 文件
await fs.writeJson(jsonPath, { name: 'test', version: '1.0.0' }, { spaces: 2 });

// 读取 JSON 文件
const config = await fs.readJson(jsonPath);

// 判断文件/目录是否存在
const exists = await fs.pathExists(path);
常用 API
API 名称 核心用途 优势对比(vs 原生 fs)
fs.ensureDir(path) 递归创建目录(不存在则创建,存在则忽略) 原生需手动递归,fs-extra 一键实现
fs.remove(path) 递归删除文件 / 目录(支持任意层级) 原生需先遍历目录,fs-extra 一键删除
fs.copy(src, dest) 复制文件 / 目录(自动创建目标目录) 原生需区分文件 / 目录,fs-extra 自动适配
fs.writeJson(path, data) 写入 JSON 文件(自动 stringify) 原生需手动 JSON.stringify,fs-extra 简化步骤
fs.readJson(path) 读取 JSON 文件(自动 parse) 原生需手动 JSON.parse,fs-extra 简化步骤
fs.pathExists(path) 判断文件 / 目录是否存在(返回 boolean) 原生需用 fs.access 捕获错误,fs-extra 直接返回

glob(匹配文件)

glob 解决「按规则批量查找文件」的需求,支持用通配符(如 ***?)匹配文件路径。

安装
pnpm add glob
pnpm add @types/glob -D
核心用法
import glob from 'glob';
import path from 'path';

// 同步匹配
const files = glob.sync('src/**/*.ts', {
  cwd: process.cwd(),
  ignore: ['src/test/**/*'],
});

// 异步匹配(推荐)
const files = await glob.promise('src/**/*.{ts,js}', {
  cwd: process.cwd(),
  dot: true,
});

// 流式匹配(适合大量文件)
const stream = glob.stream('src/**/*.ts');
stream.on('data', (filePath) => {
  console.log('匹配到文件:', filePath);
});
常见通配符规则
通配符 含义 示例 匹配结果
* 匹配当前目录下的任意字符(不含子目录) src/*.ts src/index.tssrc/utils.ts
** 匹配任意层级的目录(递归) src/**/*.ts src/index.tssrc/a/b/utils.ts
? 匹配单个字符 src/file?.ts src/file1.tssrc/file2.ts
[] 匹配括号内的任意一个字符 src/[ab].ts src/a.tssrc/b.ts
! 排除匹配的文件 src/**/*.ts + !src/test.ts 所有 .ts 文件,排除 src/test.ts

模板生成

handlebars(生成模板文件)

handlebars 是一款逻辑少、轻量型的模板引擎,核心用途是「将数据与模板结合,动态生成文本内容」。

安装
pnpm add handlebars
pnpm add @types/handlebars -D
基础用法
import handlebars from 'handlebars';

// 1. 定义模板
const template = `{
  "name": "{{ projectName }}",
  "version": "{{ version }}",
  "description": "{{ description }}"
}`;

// 2. 编译模板
const compiledTemplate = handlebars.compile(template);

// 3. 传入数据渲染
const data = {
  projectName: 'my-ts-pkg',
  version: '1.0.0',
  description: '基于 TS + 双模式的 npm 包',
};

const result = compiledTemplate(data);
console.log(result);
核心语法
  • 变量占位符{{ 变量名 }}(支持嵌套对象)
  • 条件判断{{#if 条件}}...{{else}}...{{/if}}
  • 循环遍历{{#each 数组}}...{{/each}}
  • 注释{{! 注释内容 }}
高级用法
// 注册辅助函数
handlebars.registerHelper('uppercase', (str) => {
  return str.toUpperCase();
});

// 使用辅助函数
const template = `{{ uppercase name }}`;

完整示例

结合所有工具,实现一个完整的 CLI 工具:

import { program } from 'commander';
import inquirer from 'inquirer';
import chalk from 'chalk';
import ora from 'ora';
import fs from 'fs-extra';
import glob from 'glob';
import handlebars from 'handlebars';
import path from 'path';
import boxen from 'boxen';

program.version('1.0.0', '-v, --version').description('CLI 工具示例');

program
  .command('init [projectName]')
  .description('初始化项目')
  .option('-t, --template <template>', '模板类型', 'default')
  .option('-y, --yes', '跳过交互式询问', false)
  .action(async (projectName, options) => {
    console.log(chalk.blue('📦 开始初始化项目...'));

    let answers: any = {};

    // 如果提供了项目名称且使用了 --yes,跳过交互
    if (projectName && options.yes) {
      answers = {
        projectName,
        framework: 'react-ts',
        modules: ['路由', 'ESLint/Prettier'],
        initGit: true,
      };
    } else {
      // 交互式询问
      answers = await inquirer.prompt([
        {
          type: 'input',
          name: 'projectName',
          message: '项目名称',
          default: projectName || 'my-project',
          validate: (value) => {
            if (!value.trim()) return '项目名称不能为空!';
            if (!/^[a-z0-9-]+$/.test(value)) {
              return '项目名称只能包含小写字母、数字和连字符!';
            }
            return true;
          },
        },
        {
          type: 'list',
          name: 'framework',
          message: '选择框架',
          choices: [
            { name: 'React + TypeScript', value: 'react-ts' },
            { name: 'Vue + TypeScript', value: 'vue-ts' },
            { name: 'Vanilla JS', value: 'vanilla' },
          ],
          default: 'react-ts',
        },
        {
          type: 'checkbox',
          name: 'modules',
          message: '选择功能模块',
          choices: ['路由', '状态管理', 'UI 组件库', 'ESLint/Prettier'],
          default: ['路由', 'ESLint/Prettier'],
        },
        {
          type: 'confirm',
          name: 'initGit',
          message: '是否初始化 Git 仓库?',
          default: true,
        },
      ]);
    }

    const spinner = ora(chalk.blue('正在生成项目文件...')).start();

    try {
      // 1. 检查目录是否存在
      const targetDir = path.resolve(process.cwd(), answers.projectName);
      if (await fs.pathExists(targetDir)) {
        spinner.fail(chalk.red(`目录 ${answers.projectName} 已存在!`));
        process.exit(1);
      }

      // 2. 创建项目目录
      await fs.ensureDir(targetDir);

      // 3. 读取模板文件
      const templateDir = path.resolve(__dirname, '../templates', options.template);
      const templateFiles = await glob.promise('**/*.hbs', {
        cwd: templateDir,
        dot: true,
      });

      // 4. 渲染模板并写入文件
      for (const templateFile of templateFiles) {
        const templatePath = path.resolve(templateDir, templateFile);
        const templateContent = await fs.readFile(templatePath, 'utf8');

        const compiled = handlebars.compile(templateContent);
        const renderedContent = compiled(answers);

        const targetFile = templateFile.replace(/\.hbs$/, '');
        const targetPath = path.resolve(targetDir, targetFile);

        await fs.ensureDir(path.dirname(targetPath));
        await fs.writeFile(targetPath, renderedContent, 'utf8');
      }

      // 5. 初始化 Git(如果选择)
      if (answers.initGit) {
        spinner.text = '正在初始化 Git 仓库...';
        // 这里可以调用 git 命令
      }

      spinner.succeed(chalk.green('项目初始化完成!'));

      // 6. 显示成功信息
      const successMessage = boxen(
        chalk.green(`✅ 项目 ${answers.projectName} 创建成功!\n\n`) +
          chalk.cyan(`cd ${answers.projectName}\n`) +
          chalk.cyan('npm install\n') +
          chalk.cyan('npm run dev'),
        {
          padding: 1,
          margin: 1,
          borderStyle: 'round',
          borderColor: 'green',
        },
      );

      console.log(successMessage);
    } catch (error) {
      spinner.fail(chalk.red(`初始化失败:${(error as Error).message}`));
      process.exit(1);
    }
  });

program
  .command('build')
  .description('构建项目')
  .option('-e, --env <env>', '构建环境', 'production')
  .option('-o, --outDir <dir>', '输出目录', 'dist')
  .action(async (options) => {
    const spinner = ora(chalk.blue('正在构建项目...')).start();

    try {
      // 模拟构建过程
      await new Promise((resolve) => setTimeout(resolve, 2000));

      spinner.succeed(chalk.green(`构建完成!输出目录:${options.outDir}`));
    } catch (error) {
      spinner.fail(chalk.red(`构建失败:${(error as Error).message}`));
      process.exit(1);
    }
  });

program.parse(process.argv);

最佳实践

  1. 错误处理:使用 try-catch 捕获错误,提供友好的错误提示
  2. 用户体验:使用 ora 显示加载状态,使用 chalk 美化输出
  3. 参数验证:在 inquirer 中使用 validate 验证用户输入
  4. 文件操作:使用 fs-extra 的 Promise API,避免回调地狱
  5. 模板管理:将模板文件放在独立目录,使用 glob 批量处理
  6. 命令结构:使用 commander 组织命令,保持清晰的命令层次
  7. 帮助信息:为每个命令添加清晰的描述和示例

常见问题

命令行解析相关问题

Q: commander 和 yargs 如何选择?

A:

  • commander:API 友好,适合大多数场景(推荐)
  • yargs:需要位置参数或复杂参数解析时使用

Q: 如何获取未定义的选项?

A:

// commander
program.parse();
const unknownOptions = program.opts();

// yargs
const argv = yargs.parse();
const unknown = argv._; // 未定义的参数

交互式输入相关问题

Q: inquirer 和 prompts 如何选择?

A:

  • inquirer:功能全面,生态丰富(推荐)
  • prompts:轻量级,简单场景使用

Q: 如何中断交互式输入?

A:

// inquirer 会自动处理 Ctrl+C
// prompts 需要手动处理
const response = await prompts({
  type: 'text',
  name: 'value',
  message: '输入值',
  onCancel: () => {
    console.log('已取消');
    process.exit(0);
  },
});

终端美化相关问题

Q: chalk 和 picocolors 如何选择?

A:

  • chalk:功能丰富,适合大多数场景(推荐)
  • picocolors:对体积敏感的项目使用

Q: 如何检测终端是否支持颜色?

A:

import chalk from 'chalk';

// chalk 会自动检测,不支持时自动禁用颜色
// 手动检测
const supportsColor = chalk.supportsColor;

文件操作相关问题

Q: fs-extra 和原生 fs 的区别?

A:

  • fs-extra:Promise API、递归操作、JSON 操作更便捷
  • 原生 fs:需要手动封装 Promise、手动递归

Q: glob 如何排除多个文件?

A:

const files = await glob.promise('src/**/*.ts', {
  ignore: ['src/test/**/*', 'src/**/*.test.ts'],
});

模板生成相关问题

Q: handlebars 和其他模板引擎的区别?

A:

  • handlebars:逻辑少、轻量级(推荐)
  • ejs:支持 JavaScript 代码,功能强大但体积大
  • mustache:无逻辑模板,但功能较少

Q: 如何在模板中使用辅助函数?

A:

handlebars.registerHelper('eq', (a, b) => a === b);

// 模板中使用
// {{#if (eq value "test")}}...{{/if}}

参考资源

❌
❌