Stories

Detail Return Return

創建一個專屬的 CLI - Stories Detail

作為一個前端,基本上每次初始化項目都會用到腳手架,通過一些腳手架可以快速的搭建一個前端的項目並集成一些所需的功能模塊,避免自己每次都手動一個一個去安裝。安裝各個包的這個過程其實沒啥營養,通過封裝一個腳手架來跳過這個步驟,把精力聚焦到功能研發上。

由於最近自己在寫項目都是相同的技術棧:Nextjs + TailwindCSS + TypeScript + ShadcnUI ,有時候如果忘記了 ShadcnUI 安裝的命令的話,還得去 ShadcnUI 官網 去查看先關的文檔。正好最近在看前端工程化相關的內容,簡單封裝一個 cli ,為了以後給 DevNow 來擴展一些內容模版做些基建。説 · 感覺就逼格就上來了😎。

順便給自己的開源博客項目打個廣告,歡迎大家體驗、star:
DevNow 是一個精簡的開源技術博客項目模版,支持 Vercel 一鍵部署,支持評論、搜索等功能,歡迎大家體驗。

1. 初始化

新建一個 cli 文件夾 用來存放我們相關的內容。

|-- cli
  |-- index.js

我們主要的 cli 內容都在 index.js 中,接下來我們來實現一下。先來看完整代碼如下:

#!/usr/bin/env node

const { Command } = require('commander');
const { execSync } = require('child_process');
const prompts = require('prompts');

const program = new Command();
let projectPath = '';
async function initProject() {
  console.log('歡迎使用項目初始化工具!');

  const opts = program.opts();

  // 詢問用户輸入和選擇
  const response = await prompts([
    {
      type: 'text',
      name: 'projectName',
      message: '請輸入項目名稱:',
      validate: (name) => (name ? true : '項目名稱不能為空'),
      initial: projectPath
    },
    {
      type: 'select',
      name: 'template',
      message: '請選擇項目模板:',
      choices: [
        { title: 'JavaScript', value: 'javascript' },
        { title: 'TypeScript', value: 'typescript' }
      ],
      initial: opts.ts ? 1 : 0 // 默認 TypeScript
    },
    {
      type: 'select',
      name: 'tailwindCSS',
      message: 'Would you like to use Tailwind CSS?',
      choices: [
        { title: 'No', value: false },
        { title: 'Yes', value: true }
      ],
      initial: opts.tailwind ? 1 : 0 // 默認 TypeScript
    },
    {
      type: 'select',
      name: 'eslint',
      message: 'Would you like to use eslint',
      choices: [
        { title: 'No', value: false },
        { title: 'Yes', value: true }
      ],
      initial: opts.eslint ? 1 : 0 // 默認 eslint
    },
    {
      type: 'select',
      name: 'shadcnUI',
      message: 'Would you like to use Shadcn/ui?',
      choices: [
        { title: 'No', value: false },
        { title: 'Yes', value: true }
      ],
      initial: opts.shadcnUI ? 1 : 0 // 默認 shadcnUI
    }
  ]);

  // 項目初始化命令拼接
  const templateFlag = (response.template || opts.ts) === 'typescript' ? '--typescript' : '';
  const eslintFlag = response.eslint || opts.eslint ? '--eslint' : '';
  const tailwindCSS = response.tailwindCSS || opts.tailwind ? '--tailwind' : '';

  try {
    // 執行 Next.js 初始化命令
    console.log(`正在創建項目:${response.projectName}...`);
    execSync(
      `pnpm create next-app@latest ${response.projectName} ${templateFlag} ${eslintFlag} ${tailwindCSS} --turbopack --app --src-dir --import-alias "@/*"`,
      { stdio: 'inherit' }
    );

    // 切換到項目目錄
    process.chdir(response.projectName);

    // 安裝 shadcn/ui
    if (response.shadcnUI || opts.shadcnUI) {
      console.log('安裝 shadcn/ui...');
      execSync('pnpm dlx shadcn@latest init -d', { stdio: 'inherit' });
    }

    console.log('項目初始化完成!');
    console.log(`請運行以下命令進入項目並啓動開發服務器:`);
    console.log(`  cd ${response.projectName}`);
    console.log(`  pnpm dev`);
  } catch (error) {
    console.error('項目創建失敗:', error.message);
  }
}

// 定義 CLI 命令
program
  .name('create-next-app')
  .description('創建一個 Next.js 項目')
  .argument('<project-name>', '項目名稱')
  .option('--ts, --typescript', 'Initialize as a TypeScript project. (default)')
  .option('--tailwind', 'Initialize with Tailwind CSS config. (default)')
  .option('--eslint', 'Initialize with ESLint config.')
  .option('--shadcnUI', 'Enable ShadcnUi.')
  .action((name) => {
    projectPath = name;
    initProject();
  });

// 解析命令行參數
program.parse(process.argv);

1.1 execSync

execSyncNode.js 的內置模塊 child_process 中的一個同步執行命令的方法。它會在當前進程中運行指定的系統命令,並返回結果。

1.2 Commadner.js

Commander.js 完整的 node.js 命令行解決方案。編寫代碼來描述你的命令行界面。 Commander 負責將參數解析為選項和命令參數,為問題顯示使用錯誤,並實現一個有幫助的系統。

// 定義 CLI 命令
program
  .name('create-next-app')
  .description('創建一個 Next.js 項目')
  .argument('<project-name>', '項目名稱')
  .option('--ts, --typescript', 'Initialize as a TypeScript project. (default)')
  .option('--tailwind', 'Initialize with Tailwind CSS config. (default)')
  .option('--eslint', 'Initialize with ESLint config.')
  .option('--shadcnUI', 'Enable ShadcnUi.')
  .action((name) => {
    projectPath = name;
    initProject();
  });

// 解析命令行參數
program.parse(process.argv);

這段代碼通過 commander.js 定義了一個功能豐富的 CLI 工具,允許用户通過不同的選項初始化一個定製化的 Next.js 項目。 parse(process.argv) 是核心步驟,用來解析命令行中的參數並執行對應的邏輯。
我們通過 --help 可以看到一些可選的配置參數等等內容。

1.3 prompts

prompts 是一個輕量級、用户友好的交互式 CLI 庫,用於在命令行中向用户提出問題並收集輸入。它支持多種類型的提示,比如文本輸入、選擇、多選、確認等,允許開發者靈活地設計命令行應用的交互體驗。

通過使用 prompts 庫,我們可以在 CLI 中實現類似 Next.js 的交互體驗,這樣可以在初始化項目時根據需要選擇不同的選項,體驗會更好一點。

const response = await prompts([
  {
    type: 'text',
    name: 'projectName',
    message: '請輸入項目名稱:',
    validate: (name) => (name ? true : '項目名稱不能為空'),
    initial: projectPath
  },
  {
    type: 'select',
    name: 'template',
    message: '請選擇項目模板:',
    choices: [
      { title: 'JavaScript', value: 'javascript' },
      { title: 'TypeScript', value: 'typescript' }
    ],
    initial: opts.ts ? 1 : 0 // 默認 TypeScript
  },
  {
    type: 'select',
    name: 'tailwindCSS',
    message: 'Would you like to use Tailwind CSS?',
    choices: [
      { title: 'No', value: false },
      { title: 'Yes', value: true }
    ],
    initial: opts.tailwind ? 1 : 0 // 默認 TypeScript
  },
  {
    type: 'select',
    name: 'eslint',
    message: 'Would you like to use eslint',
    choices: [
      { title: 'No', value: false },
      { title: 'Yes', value: true }
    ],
    initial: opts.eslint ? 1 : 0 // 默認 eslint
  },
  {
    type: 'select',
    name: 'shadcnUI',
    message: 'Would you like to use Shadcn/ui?',
    choices: [
      { title: 'No', value: false },
      { title: 'Yes', value: true }
    ],
    initial: opts.shadcnUI ? 1 : 0 // 默認 shadcnUI
  }
]);

這裏通過配置了一些選項,結合了 command 初始化命令行的默認選項,效果如下:

1.4 執行代碼

try {
  // 執行 Next.js 初始化命令
  console.log(`正在創建項目:${response.projectName}...`);
  execSync(
    `pnpm create next-app@latest ${response.projectName} ${templateFlag} ${eslintFlag} ${tailwindCSS} --turbopack --app --src-dir --import-alias "@/*"`,
    { stdio: 'inherit' }
  );

  // 切換到項目目錄
  process.chdir(response.projectName);

  // 安裝 shadcn/ui
  if (response.shadcnUI || opts.shadcnUI) {
    console.log('安裝 shadcn/ui...');
    execSync('pnpm dlx shadcn@latest init -d', { stdio: 'inherit' });
  }

  console.log('項目初始化完成!');
  console.log(`請運行以下命令進入項目並啓動開發服務器:`);
  console.log(`  cd ${response.projectName}`);
  console.log(`  pnpm dev`);
} catch (error) {
  console.error('項目創建失敗:', error.message);
}

這裏就是主要的執行內容,通過前面的配置參數來定製化執行,首先是 Nextjs 相關內容,可以看到我默認了一些配置,這些看個人的需求了,感覺這些東西默認的會更好。最後就是根據配置參數判斷是否要安裝 ShadcnUI .

其實到這裏 CLI 的基本內容就完事了,我們可以在本地執行 node index.js cli-test --tailwind 去測試下。

注意:
本地 Node 環境中需要安裝 commanderprompts 兩個庫,不然會報錯。

當通過 npx create-next-app 等命令初始化項目時,你不需要擔心 commander 等依賴的安裝。工具本身已經打包好了這些依賴,並通過 npx 或 pnpm dlx 臨時加載執行,簡化了使用流程。

2. 發佈

這裏簡單記錄下吧,這個應該很多人已經會了,

2.1 npm init

首先通過 npm init 初始化,通過提示可以生成一些配置文件,這個時候項目結構應該是:

|-- cli
  |-- index.js
  |-- package.json

package.json 主要內容如下:

{
  "dependencies": {
    "commander": "^12.1.0",
    "prompts": "^2.4.2"
  },
  "name": "create-devnow-app",
  "version": "0.0.6",
  "description": "create devnow app",
  "main": "./index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "LaughingZhu",
  "license": "MIT",
  "bin": {
    "create-devnow-app": "./index.js"
  }
}

確保你的 package.json 文件中定義了正確的 bin 配置。這樣才能執行。

2.2 登錄

npm login

2.3 發佈

npm publish

使用自己專屬的 CLI 😏

pnpm dlx create-devnow-app@latest my-app

大功告成,這樣在後續就可以直接使用了,如果業務中需要其他的一些配置的話,可以通過相同的方式集成,比如 T3 這個項目,就集成了 TRPC 等內容到腳手架中,可以方便快速構建一個全棧的項目。

Add a new Comments

Some HTML is okay.