【不要说话】假前甜品脚手架

503 阅读4分钟

CLI

命令行界面(command-line inteface),缩写CLI,对于我们大前端来说,cli的使用已经屡见不鲜了,如框架三巨头的Angular CLIcreate-react-appVue CLI,每一个都坐拥成千上万的死忠。

需求

当我们创建一个新的项目的时候,往往是用三大佬的脚手架生成新的项目再进行开发,但是这样的问题是初始化模版都是官方自己维护的,关于我们经常使用的代码片段、发布配置、layout布局等等都得重新搞,(vue cli 可以使用 --preset参数指定模版路径, react的没有,angular没有用过,暂时不知道)。

框架我们采用dvaumi,它俩都是react的应用框架。

流程

流程大概如下,接下来开始我们的撸码之旅。

向大佬问好

我们创建个项目,来实现超级简单的一个cli,获取用户输入姓名,并向大佬问好。

我们的cli叫rollins-cli,为什么叫rollins, 因为Seth Rollins啊, 前the shield成员。

Burn it down!

好了,继续写我们的代码

创建node项目三板斧

mkdir rollins-cli
cd $_
npm init -y

touch index.js, 创建index.js,写入以下内容

#! /usr/bin/env node 
const readline = require('readline')

/**
 * @param { String } input 显示的文案
 * @returns { Promise } Promise对象
 */
const getCommandParams = input => {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    })
    return new Promise((resolve, reject) => {
        rl.question(input, data => {
            rl.close()
            resolve(data)
        })
    })
}

const getUserInput = (input= '来了,老弟,你叫啥?\n') => {
  getCommandParams(input).then(data => {
    data ?  console.log(`原来是${data} 大佬,失敬失敬`) : getUserInput('你不说我就一直问\n你叫啥?\n')
  })
}

getUserInput()

运行效果如下

万里长征第一步,以上代码我们就已经实现了一个简易的CLI。

bin字段

package.json文件中添加bin字段, bin字段用来指定各个内部命令对应的可执行文件的位置。

{
    "bin": {
        "rollins": "./bin/index.js"
    }
}

上面代码说明rollins命令的执行文件在bin文件夹下的index.js

# 创建bin目录
mkdir bin

# 将index.js文件移动到bin目录下
mv index.js ./bin

执行npm link命令

会将定义的rollins字段拷贝到全局安装的node_modules文件夹内,并创建一个软连接。

在全局node的bin目录中同样也创建了软连接

然后我们就可以在全局中使用rollins这个命令了。

同样是完美运行,接下来让我们招兵买马继续我们的革命之旅。

摇人吧

执行npm i commander inquirer chalk ora -S,来壮大我们的势力。

接下来,依次介绍登场的各位豪杰

commander

commander 是一个node命令行的解决方案,可以简化我们的开发流程。

demo

#! /usr/bin/env node 
const program = require('commander')
program
    .version('0.0.1')
    .command('init')
    .alias('i')
    .description('初始化')
    .option('-a, --name  <name>', '参数介绍', '我是默认值')
    .action(option => {
        console.log('init命令被调用')
    })

program.parse(process.argv)

// 输入为空时 显示帮助信息
if(!program.args.length){
  program.help()
}

简单说下上面命令

  • version 版本
  • command 定义命令
  • alias 定义命令的简写
  • description 命令描述
  • option 传递给命令的参数
    • -a 参数名简写
    • --name 参数名
    • <name>或者 [name] <>表示强制用户输入 []表示可选参数 用户没有输入使用默认值或者undefined
  • action 命令触发时候的回调

当我们什么命令也没有输入的时候,会触发帮助信息,我们直接调用grogram.help()函数即可,grogram会根据我们定义的命令自动生成帮助信息

更多关于grogram的内容,请去官网查看,这点不多的东西,我们就够用了。

inquirer

inquirer 给用户提供了一个漂亮的界面和提出问题流的方式。

demo

#! /usr/bin/env node 
const { prompt } = require('inquirer')

const promptList = [
  {
    type: 'input',
    name: 'name',
    message: '请输入文件夹名',
    filter: function(val) {
      return val.trim()
    },
    validate: function(val) {
      return !!val.trim() ? true : '请输入名称'
    }
  },
  {
    type: 'list',
    name: 'template',
    message: '请选择系统',
    choices: [
      'Mac',
      'windows',
      'Linux'
    ]
  }
]

prompt(promptList).then(params => {
  console.log(params)
})

chalk

chalk是命令行美化的模块,并且支持模板输出。

demo

#! /usr/bin/env node 
const chalk = require('chalk')
console.log(`${chalk.yellow(`${process.cwd()}`)}`)
console.log(`
  ${chalk.cyan(`
    ${chalk.gray('$')} cd test
    ${chalk.gray('$')} npm install
    ${chalk.gray('$')} npm start
  `)}
`)

输出如下

download-git-repo

download-git-repo是支持从git仓库中下载源码的库,细化到分支级别,支持GitHub, GitLab, Bitbucket。

api

download(repository, destination, options, callback)

  • repository 仓库地址
  • destination 项目保存地址
  • options 可选参数
    • { clone: false } 默认为fasle 表示是否使用git clone,默认使用 http download 方式进行下载
  • callback 回调函数

demo

以github为例,重点说下repository这个参数,仓库地址是我们的用户名/项目名

代码如下

const download = require('download-git-repo')

download('Ortonzhang/template-dva', './test', (err) => {
    err ? console.log('err is: %s', err) : console.log('success')
})

整合

首先创建我们的模版,使用umidva创建好项目后,发送到github, 也可以基于别的框架进行创建项目。

创建template.json, 写入模版地址

{
  "umi": "Ortonzhang/template-umi",
  "dva": "Ortonzhang/template-dva"
}

将上面我们的demo整合起来,完成脚手架的全部代码。

#! /usr/bin/env node 
const program = require('commander')

const { prompt } = require('inquirer')

const chalk = require('chalk')

const download = require('download-git-repo')
const fs = require('fs')

// 用来实现loading效果
const ora = require('ora')

const { promisify } = require('util');
const path = require('path')

// 使用 promisify 设置成promise
const readFileAsync = promisify(fs.readFile);
const writeFileAsync = promisify(fs.writeFile)

// 模版配置文件
const templateConfig = require(`${__dirname}/../template`)

// 获取当前设备的路径分割符号
const sep = path.sep

// 设置问题
const promptList = [
  {
    type: 'input',
    name: 'name',
    message: '请输入文件夹名',
    filter: function(val) {
      return val.trim()
    },
    validate: function(val) {
      return !!val.trim() ? true : '请输入名称'
    }
  },
  {
    type: 'list',
    name: 'template',
    message: '请选择模版',
    choices: [
      'dva',
      'umi'
    ]
  }
]


const create = async () => {

  // 获取用户输入 拿到项目名和模版
  let { name, template } = await prompt(promptList)
  
  // 判断Linux和windows 设置不同的分隔符
  let step = sep === '/' ? '/' : '\\'

  console.log(`
    ${chalk.white(`✨ Creating project in ${chalk.yellow(`${process.cwd()}${step}${name}`)}.`)}
  `)

  const spinner = ora('🗃 Initializing git repository...');

  spinner.start()
  
  // 下载模版
  await downloadPromise(`${templateConfig[template]}`, `${name}`)
  
  // 获取模版的package.json文件数据
  let stringPackage = await readFileAsync(`./${name}/package.json`, {encoding: 'utf8'})
  
  // 转化成json
  let jsonPackage = JSON.parse(stringPackage)
  
  // 设置名称
  jsonPackage.name = `${name}`

  // 创建新的json数据
  const updateJson = JSON.stringify(jsonPackage, null, 2)

  // 写入新的package.json
  await writeFileAsync(`./${name}/package.json`, updateJson)

  spinner.stop();

  console.log(`
    ${chalk.green(`🎉  Successfully created project ${chalk.yellow(`${name}`)}.`)}
    ${chalk.white('👉  Get started with the following commands:')}
    ${chalk.cyan(`
      ${chalk.gray('$')} cd ${name}
      ${chalk.gray('$')} npm install
      ${chalk.gray('$')} npm start
    `)}
  `);
}


/**
 * 下载模版
 * @param {String} path  模版路径
 * @param {String} name  项目名称
 * @return {Promise}  promise对象
 */
const downloadPromise = (path, name) => {
  return new Promise((resolve, reject) => {
    download(path, name, (err) => {
      if(err){
        console.log(chalk.red(err))
        reject()
      }
      resolve()
    })
  })
}

program
  
  // 版本号 
  .version(require('../package').version)

  // 设置命令
  .command('create')
  
  //设置别名
  .alias('c')
  
  // 描述
  .description('创建新项目')
  
  // 动作
  .action(option => {
    create()
  })

program.parse(process.argv)

// 输入为空时 显示帮助信息
if(!program.args.length){
  program.help()
}

以上就是rollins-cli的全部代码,发布到npm上就可以了。

发布

执行npm login, 登陆npm输入用户名密码

执行npm publish,进行发布操作。

常见错误

  • 镜像地址
    • 当前镜像地址是淘宝的话,就会导致失败,执行npm config set registry https://registry.npmjs.org/修改npm的registry
  • 版本号重复
    • 修改package.json里面的version字段
  • 没有权限
    • 执行npm adduser,输入用户名、密码、邮箱

参考

inquirer.js —— 一个用户与命令行交互的工具

跟着老司机玩转Node命令行

总结

以上内容,我们就编写发布了一个属于自己的npm包。麻雀虽小,五脏倒也全,本文代码请戳github