Skip to content

创建脚手架

⏱️最后更新: 2025/07/27

创建一个 Node.js 脚手架项目可以帮助你快速初始化新项目结构,节省重复配置时间。

- win10
- vscode v1.98.2
- node v22.14.0
- npm v10.9.2

我们的脚手架同步发布在 npm,持续维护更新中。
node-cli

1. 初始化脚手架项目

bash
# 创建项目目录
mkdir node-template
cd node-template

# 初始化 npm 项目
npm init -y

2. 安装必要依赖

bash
npm install commander inquirer chalk figlet shelljs fs-extra --save
  • commander: 处理命令行参数
  • inquirer: 交互式命令行界面
  • chalk: 终端字符串样式(用于在命令行输出中添加颜色,提升用户体验。)
  • figlet: 生成 ASCII 艺术字
  • shelljs: 执行 shell 命令
  • fs-extra: Node.js 内置 fs 模块的增强版,提供更方便的文件操作方法(例如,复制整个目录)。

3. 创建基本文件结构

node-template/
├── bin/
│    └── cli.js          # 命令行入口
├── templates/          # 项目模板
│   ├── basic/          # 基础模板
│   │   ├── src/
│   │   ├── test/
│   │   ├── .gitignore
│   │   ├── package.json
│   │   └── README.md
│   └── advanced-001/       # 定制模版001
│   └── advanced-002/       # 定制模版002
│   └── advanced-003/       # 定制模版003
├── lib/
│   ├── create.js       # 创建项目逻辑
│   └── utils.js        # 工具函数
├── package.json
└── README.md

4. 编写 CLI 入口 (bin/cli.js)

脚手架的主文件
在项目根目录下创建一个主文件,通常命名为 index.js 或者 cli.js。
我们用 cli.js。

javascript
#!/usr/bin/env node

import { program } from "commander";
import { createProject } from "../lib/create.js";
import { readFileSync } from "fs";
import { dirname, join } from "path";
import { fileURLToPath } from "url";

// 获取当前模块路径
const __dirname = dirname(fileURLToPath(import.meta.url));

// 读取 package.json
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json")));

program
  .version(pkg.version)
  .command("create <project-name>")
  .description("创建一个新项目")
  .option("-t, --template <template>", "选择项目模板")
  .action((name, cmd) => {
    createProject(name, cmd.template);
  });

program.parse(process.argv);

5. 编写创建逻辑 (lib/create.js)

javascript
// 使用 ES Module 导入
import fs from "fs-extra";
import path from "path";
import chalk from "chalk";
import inquirer from "inquirer";
import shell from "shelljs";
import { successLog, errorLog } from "./utils.js"; // 导入 ESM 模块

// ES Module 中,__dirname 和 __filename 需要特殊处理
import { dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export async function createProject(projectName, templateName) {
  console.log(chalk.blue("欢迎使用 Node.js 脚手架!"));
  // 检查项目名称是否合法
  if (!/^[a-zA-Z0-9_-]+$/.test(projectName)) {
    errorLog("项目名称只能包含字母、数字、下划线和横线");
    return;
  }

  // 检查目录是否已存在
  const targetPath = path.join(process.cwd(), projectName);
  if (fs.existsSync(targetPath)) {
    errorLog(`目录 ${projectName} 已存在`);
    return;
  }

  // 如果没有指定模板,让用户选择
  if (!templateName) {
    const { selectedTemplate } = await inquirer.prompt([
      {
        type: "list",
        name: "selectedTemplate",
        message: "请选择项目模板",
        choices: ["basic", "advanced-001"], // 确保你的 templates 目录下有这些文件夹
      },
    ]);
    templateName = selectedTemplate;
  }

  // 复制模板文件
  const templatePath = path.join(__dirname, "../templates", templateName);
  console.log(templatePath);
  if (!fs.existsSync(templatePath)) {
    errorLog(`模板 ${templateName} 不存在`);
    return;
  }
  console.log(`模板 ${templateName} 存在`);

  // shelljs 在 ESM 环境下使用没有问题,但如果它内部有 CommonJS 依赖,需要注意
  // 复制模板文件到目标目录(以下二选一复制都OK)
  // shell.cp("-R", `${templatePath}`, targetPath);
  await fs.copy(templatePath, targetPath);
  // 可以在这里进行一些文件内容的替换(例如,修改 package.json 的 name)
  // 让我们先简单地复制
  const packageJsonPath = path.join(targetPath, "package.json");
  if (await fs.pathExists(packageJsonPath)) {
    const packageJson = await fs.readJson(packageJsonPath);
    packageJson.name = projectName;
    await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
  }
  // 更新 package.json 中的项目名称
  const packagePath = path.join(targetPath, "package.json");
  if (fs.existsSync(packagePath)) {
    // 在 ESM 中,直接 require 一个 JSON 文件会失败,需要使用 import
    // 如果 packageJson 不是 .mjs 文件,直接 require 会有警告
    // 更好的做法是使用 fs.readFileSync 和 JSON.parse
    const packageJsonContent = fs.readFileSync(packagePath, "utf-8");
    const packageJson = JSON.parse(packageJsonContent);

    packageJson.name = projectName;
    fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
  }

  successLog(`项目 ${projectName} 创建成功!`);
  console.log(chalk.blue(`\ncd ${projectName}`));
  console.log(chalk.blue("npm install\n"));
}

6. 添加工具函数 (lib/utils.js)

javascript
import chalk from "chalk";

function successLog(message) {
  console.log(chalk.green(`✓ ${message}`));
}

function errorLog(message) {
  console.log(chalk.red(`✗ ${message}`));
}

export { successLog, errorLog };

7. 配置 package.json

添加 bin 字段和准备脚本:

json
{
  "name": "eai_nodejs_template",
  "version": "1.0.0",
  "type": "module",
  "bin": {
    "node-cli": "./bin/cli.js"
  },
  "scripts": {
    "prepublishOnly": "npm run build"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "Node.js 项目脚手架",
  "dependencies": {
    "chalk": "^5.4.1",
    "commander": "^14.0.0",
    "execa": "^9.6.0",
    "figlet": "^1.8.2",
    "fs-extra": "^11.3.0",
    "inquirer": "^12.8.2",
    "shelljs": "^0.10.0"
  }
}

8. 创建模板文件

templates/basic/ 目录下创建基础项目模板文件,例如:

  • package.json
  • README.md
  • src/index.js
  • test/
  • .gitignore

9. 测试和使用脚手架

bash
# 在开发时链接到全局
npm link

# 测试创建项目
node-cli create my-new-project

10. 发布到 npm (可选)

bash
# 1. 切换源
nrm use npm
# 2. 登录
npm login
# 3. 移除全局符号链接
npm unlink -g eai_nodejs_template
# 4. 发布
npm publish