Skip to content
文章目录

开发自己的项目模板cli

为什么要开发这个?

每次创建项目都有一堆配置太麻烦,轻则花1-2小时找配置,配置没找对可能延误1-2天,把项目模板放在git托管平台,每次都去下载不就行了!可以,但做法太low

分析

  • 提供一个模板选择列表,让选择是要创建什么样的模板
  • 提供一个配置文件,能配置自己可选的项目模板地址
  • 选择完模板之后,提示输入项目名称,然后自动从git远程仓库下载该模板,并用该模板创建项目

需要用到的库

开始开发

添加依赖

pnpm add download-git-repo inquirer ora
pnpm add @types/inquirer ts-node typescript @types/node -D

修改package.json

json
{
  "name": "demo01",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "download-git-repo": "^3.0.2",
    "gitly": "^2.2.2",
    "inquirer": "^9.1.4",
    "ora": "^6.1.2"
  },
  "devDependencies": {
    "@types/inquirer": "^9.0.3",
    "ts-node": "^10.9.1",
    "typescript": "^4.9.3"
  }
}

项目根目录添加 tsconfig.json

json
{
  "compilerOptions": {
    "target": "esnext",                                /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
    "module": "esnext",                           /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    "strict": true,                                 /* Enable all strict type-checking options. */
    "moduleResolution": "node",                  
    "baseUrl": "./",                             
    "paths": {
      "@/*": [ "./src/*" ]
    },
    "esModuleInterop": true,                        /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    "skipLibCheck": true,                           /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true        /* Disallow inconsistently-cased references to the same file. */
  }
}

编写/测试运行代码

demo01.ts

ts
import inquirer, { QuestionCollection } from 'inquirer'
//@ts-ignore
import download from 'download-git-repo'
import path from 'path'
import ora from 'ora'

interface IPromptOption {
  /**
   * 项目名
   */
  projectName: string
  /**
   * 下载的模板名
   */
  templateName: string
}

interface TemplateInfo {
  // 模板压缩文件下载地址
  downloadUrl: string
  // 模板描述
  desc: string
}
const repositoryList: Record<string, TemplateInfo> = {
  vue3: {
    downloadUrl:
      'https://gitcode.net/pzy_666/front-project-template/-/archive/vue3/front-project-template-vue3.zip',
    desc: 'vue3项目基础模板',
  },
  'vue3-element-plus': {
    downloadUrl:
      'https://gitcode.net/pzy_666/front-project-template/-/archive/vue3-element-plus/front-project-template-vue3-element-plus.zip',
    desc: 'vue3项目基础模板上整合进element-plus',
  },
  'vue3-vant': {
    downloadUrl:
      'https://gitcode.net/pzy_666/front-project-template/-/archive/vue3-vant/front-project-template-vue3-vant.zip',
    desc: 'vue3项目基础模板上整合进vant',
  },
  'vitepress-doc': {
    downloadUrl:
      'direct:https://gitcode.net/pzy_666/front-project-template/-/archive/vitepress-doc/front-project-template-vitepress-doc.zip',
    desc: '用于编写文档的vitepress模板',
  },
}

const projectTemplateChoices = Object.keys(repositoryList).map(
  propertyName => ({
    name: repositoryList[propertyName].desc,
    value: propertyName,
  })
)

/**
 * 问题确认列表
 *
 * @var {[type]}
 */
const PROMPT_LIST: QuestionCollection = [
  {
    type: 'input',
    message: '请输入项目名',
    name: 'projectName',
    default: 'demo',
  },
  {
    type: 'list',
    message: '请选择需要的项目模板',
    name: 'templateName',
    choices: [
      { name: 'vue3项目基础模板', value: 'vue3' },
      {
        name: 'vue3项目基础模板上整合进element-plus',
        value: 'vue3-element-plus',
      },
      { name: 'vue3项目基础模板上整合进vant', value: 'vue3-vant' },
      { name: '用于编写文档的vitepress模板', value: 'vitepress-doc' },
    ],
  },
]

function entry() {
  inquirer.prompt<IPromptOption>(PROMPT_LIST).then(async answer => {
    const newOra = ora('开始创建项目...').start()
    const CURRENT_PATH = process.cwd() // 获取当前路径
    const downloadUrl = `direct:${
      repositoryList[answer.templateName].downloadUrl
    }`
    const targetPath = path.resolve(CURRENT_PATH, answer.projectName) // 目标路径
    await download(downloadUrl, targetPath, {}, (err: any) => {
      if (err) {
        newOra.fail(`项目创建失败:${err.message}`)
        console.log(err)
      }
      newOra.succeed(`项目创建成功

cd ${answer.projectName}
pnpm install
pnpm run dev

`)
    })
  })
}

entry()

运行代码

 node --loader ts-node/esm .\demo01.ts

参考资料

如何制作一个属于自己的cli工具