如何创建自己的cli脚手架

现代前端开发中已经离不开各种脚手架的使用了,例如vue-cli和create-react-app等脚手架,而我们在日常开发中都会在这些脚手架的基础上做一些升级改造,例如集成一些常用的工具、网络请求封装、状态管理、特定布局等等,然后在后续的新项目中复用这些基础架构。为了方便管理和使用,我们可以将其抽离出来形成一个项目模版,通过类似vue-cli脚手架的方式来创建一个新项目,接下来本文就介绍一下如何动手创建一个自己的脚手架工具。

一、创建项目

新建一个项目文件夹,这里我将其命名为easy-cli,然后在该目录下执行:npm init -y生成package.json文件。

为了能够使用全局命令,我们需要在package.json里增加bin字段。

json 复制代码
{
  "name": "easy-cli",
  "version": "1.0.0",
  // ...
  "bin": {
    "easy-cli": "./src/index.js"
  }
}

增加bin字段后我们就可以像下面这样全局使用命令:

bash 复制代码
easy-cli xxx

现在我们还无法执行命令,因为我们还没创建bin字段所指向的文件,接下来我们创建src/index.js

js 复制代码
#! /usr/bin/env node

console.log('hello')

第一行代码#! /usr/bin/env node是必须的,它用来告诉系统使用node环境来执行该文件。

然后执行命令:npm link将其链接到全局变量,接下来我们就可以使用easy-cli命令了。

可以看到js文件被执行成功了。

二、定义版本

首先需要安装commander库,commander可以帮助我们处理命令行。

bash 复制代码
npm i commander --save

commander内部做了封装,使用命令--version查看版本时会输出我们在version方法里定义的值。

js 复制代码
#! /usr/bin/env node

const program = require('commander')
const pkg = require('../package.json')

program.version(`v${pkg.version}`) // 定义当前版本

program.parse(process.argv) // 解析用户执行命令传入参数

三、命令行交互

我们希望在使用脚手架时通过如下步骤来创建一个新项目项目:

  • 首先执行easy-cli create命令。
  • 用户输入项目名称。
  • 用户选择模板。
  • 将用户选择的模板copy到本地。

在最后一步这里,一种选择是将项目模板放在脚手架里,然后将其对应的目录复制到本地。另一种选择是将项目模板放在git仓库里,然后在用户选择后将其下载到本地。这里我们选择第二种方案,这样当项目模板有改动时不需要频繁的去脚手架里改动。

1、获取项目名称

这里我们需要先安装inquirer,由于inquirer9.0以上版本无法使用require,所以安装时我们选择安装8版本的inquirer

bash 复制代码
npm i inquirer@8 --save
js 复制代码
#! /usr/bin/env node

const program = require('commander')
const inquirer = require('inquirer')
const pkg = require('../package.json')

program.version(`v${pkg.version}`) // 定义当前版本

program
  .command('create')
  .description('创建模版')
  .action(async () => {
    const { projectName } = await inquirer.prompt({
      type: 'input',
      name: 'projectName',
      message: '请输入项目名称:'
    })
    console.log(projectName)
  })
 
program.parse(process.argv) // 解析用户执行命令传入参数

项目名称我们需要用户手动输入,所以prompt里的type的值设置为input,除此之外type值还有list(列表选择)confirm(单选确认)等,name是设置结果返回值对应的key名称,设置为projectName则返回结果里通过projectName获取。

2、选择模板

js 复制代码
const program = require('commander')
const inquirer = require('inquirer')
const pkg = require('../package.json')

program
  .command('create')
  .description('创建模版')
  .action(async () => {
    const { projectName, template } = await inquirer.prompt([
      {
        type: 'input',
        name: 'projectName',
        message: '请输入项目名称:'
      },
      {
        type: 'list',
        name: 'template',
        message: '请选择模版:',
        choices: [
          {
            name: 'vue-admin',
            value: 'https://github.com:PanJiaChen/vue-element-admin'
          },
          {
            name: 'react-admin',
            value: 'https://github.com:marmelab/react-admin'
          }
        ]
      }
    ])
    console.log('projectName===', projectName)
    console.log('template===', template)
  })
 

prompt也支持传入数组的形式,这里我们可以将输入项目和选择模板的操作依次传入,choices接收的数组对象包含一个name和value,name用于显示,选择后会将对应的value值返回。

3、下载模版到本地

安装download-git-repo:

bash 复制代码
npm i download-git-repo --save
js 复制代码
const program = require('commander')
const inquirer = require('inquirer')
const pkg = require('../package.json')
const downloadGitRepo = require('download-git-repo')

program
  .command('create')
  .description('创建模版')
  .action(async () => {
    const { projectName, template } = await inquirer.prompt([
      {
        type: 'input',
        name: 'projectName',
        message: '请输入项目名称:'
      },
      {
        type: 'list',
        name: 'template',
        message: '请选择模板:',
        choices: [
          {
            name: 'vue-admin',
            value: 'https://github.com:PanJiaChen/vue-element-admin'
          },
          {
            name: 'react-admin',
            value: 'https://github.com:marmelab/react-admin'
          }
        ]
      }
    ])
    console.log('projectName===', projectName)
    console.log('template===', template)
    
    // 获取目标文件夹
    const dest = path.join(process.cwd(), projectName)
    downloadGitRepo(template, dest, (err) => {
      if (err) {
        console.log('下载模板失败' + err.message)
        return
      }
      console.log('下载模板成功')
    })
  })

通过process.cwd将项目模板下载到命令行当前目录下。

运行命令后可以看到项目模板已经成功下载下来了,一个自制的脚手架已经基本完成了。接下来我们做一些优化。

4、优化:判断项目是否已存在

将项目模版下载到本地前,我们可以做一下优化判断,如果用户输入的项目名称在本地目录中已经存在,则弹出选择让用户确认是否删除覆盖。

接下来我们安装第三方包fs-extra方便进行文件操作:

bash 复制代码
npm i fs-extra --save
js 复制代码
const program = require('commander')
const inquirer = require('inquirer')
const pkg = require('../package.json')
const downloadGitRepo = require('download-git-repo')
const fs = require('fs-extra')

program
  .command('create')
  .description('创建模版')
  .action(async () => {
    const { projectName, template } = await inquirer.prompt([
      {
        type: 'input',
        name: 'projectName',
        message: '请输入项目名称:'
      },
      {
        type: 'list',
        name: 'template',
        message: '请选择模板:',
        choices: [
          {
            name: 'vue-admin',
            value: 'https://github.com:PanJiaChen/vue-element-admin'
          },
          {
            name: 'react-admin',
            value: 'https://github.com:marmelab/react-admin'
          }
        ]
      }
    ])
    
    // 获取目标文件夹
    const dest = path.join(process.cwd(), projectName)
    if (fs.existsSync(dest)) {
      const { isExist } = await inquirer.prompt({
        type: 'confirm',
        name: 'isExist',
        message: '您输入的项目名称已存在,是否覆盖?',
      })
      // 如果覆盖就删除文件夹,否则退出
      isExist ? fs.removeSync(dest) : process.exit(1)
    }
    downloadGitRepo(template, dest, (err) => {
      if (err) {
        console.log('下载模板失败' + err.message)
        return
      }
      console.log('下载模板成功')
    })
  })

5、优化:增加loading

安装ora

bash 复制代码
npm i ora@5 --save
js 复制代码
const ora = require('ora')

const loading = ora('正在下载中...')
loading.start()
downloadGitRepo(template, dest, (err) => {
  if (err) {
    loading.fail('创建模版失败' + err.message)
    return
  }
  loading.succeed('创建模版成功')
})

6、优化:修改package.json

我们下载下来的项目模板里,package.json里的nameversion记录的都是项目模板里的信息。

通常我们需要把name改成我们新创建的的项目名称,version从1.0.0开始,所以我们可以简单的借助node的文件操作能力进行修改。

js 复制代码
downloadGitRepo(template, dest, (err) => {
  if (err) {
    loading.fail('创建模版失败' + err.message)
    return
  }
  loading.succeed('创建模版成功')
  
  // 修改package.json
  const pkgPath = path.join(process.cwd(), projectName, 'package.json')
  const pkgContent = fs.readFileSync(paths, 'utf-8')
  const pkgJson = JSON.parse(pkgContent)
  
  pkgJson.name = projectName
  pkgJson.version = '1.0.0'
  
  fs.writeFileSync(paths, JSON.stringify(pkgJson, null, 2))
})

四、发布npm

上面的工作完成后就可以选择发布到npm了,发布npm的步骤比较简单,这里就不多赘述了。

注册一个npm账号,然后npm loginnpm publish

五、感谢

本次分享到这里就结束了,感谢您的阅读,如果本文对你有什么帮助的话,别忘了动动手指点个赞❤❤❤!

相关推荐
y先森4 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy4 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189114 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿5 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡6 小时前
commitlint校验git提交信息
前端
虾球xz7 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇7 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒7 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员7 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐7 小时前
前端图像处理(一)
前端