npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

rv-cli

v1.1.4

Published

多功能脚手架

Downloads

3

Readme

相信你一定用过vue-cli或者create-react-app或者你公司自己的脚手架。当我第一次用这些脚手架的时候,会觉得这一定是种很高级的玩意,其实了解过之后就发现,脚手架也并不是多么深奥的东西。怎么做一个脚手架呢?接下来我会一步步的教你如何完成一个自己的脚手架(源码 https://github.com/hui-fly/rv-cli 记得star哦)

原文地址: 一步步教你完成一个自己的脚手架并且发布到npm

1. 预备知识

本次教大家实现一个基础的cli工具,将会用到以下一些依赖,下面对这些依赖进行简单介绍:

Inquirer.js

Inquirer.js试图为NodeJs做一个可嵌入式的美观的命令行界面。它的功能主要是:

  • 询问操作者问题
  • 获取并解析用户输入
  • 检测用户回答是否合法
  • 管理多层级的提示
  • 提供错误回调 用法示例: 建立index.js,内容如下
let inquirer = require('inquirer')
inquirer.prompt([
  {
    type: 'confirm',
    name: 'handsome',
    message: '我是世界上最帅的男人吗?',
    default: true
  }
]).then((answers) => {
  console.log(answers)
})

在文件目录线执行 node index.js 得到结果如下: { handsome:true } 这里type的类型包括

  • input–输入
  • validate–验证
  • list–列表选项
  • confirm–提示
  • checkbox–复选框等等 想了解更多请参考:https://juejin.im/entry/5937c73cac502e0068cf1171

commander

官方解释:commander灵感来自 Ruby,它提供了用户命令行输入和参数解析的强大功能,可以帮助我们简化命令行开发,它提供的功能是:

  • 参数解析
  • 强制多态
  • 可变参数
  • Git 风格的子命令
  • 自动化帮助信息
  • 自定义帮助等

这么说想必大家也比较抽象,直接上例子:

const program = require("commander");
// 定义指令
program
  .version('0.0.1')
  .usage('<command> [options]')
  .command('init', 'Generate a new project from a template')
  .action((option) => {
    // 回调函数
    console.log('Hello World')
  })
// 解析命令行参数
program.parse(process.argv);

同样在文件目录下执行 node command.js init, 然后就会发现输出 Hello World, 如果你想了解更多,可以参考 https://wangchujiang.com/wcj/#Commander

download-git-repo

这个看名字大概就能猜出它是干嘛的了,没错---它就是帮我们完成下载远程仓库的,它的用法也比较简单,示例如下:

const download = require('download-git-repo')
download(repository, destination, options, callback)

其中 repository 是远程仓库地址;destination是存放项目的文件夹,下载完之后会默认建立在本目录下;options 是一些选项,比如 { clone:boolean } 表示用 http download 还是 git clone 的形式下载,callback就是下载完成之后的回调函数了。

chalk

这个依赖可以让你输出的内容变得更好看

const chalk = require('chalk');
console.log(chalk.green('success'));
console.log(chalk.red('error'));

ora

这个依赖可以产生一个loading的效果,在进行下载的时候我们会用到它 OK,以上就是我们这次会用到的所有依赖了,接下里开始搞起我们的脚手架吧!

2. 脚手架开发

项目文件结构搭建

  1. 首先初始化我们的项目,建立一个文件夹,我这里就叫rv-cli,你也可以随意取.

  2. 建立好之后,在文件夹下执行npm init(相信你一定已经安装了node了),一路回车即可,然后就会帮你生成一个package.json文件,在package.json添加如下代码--即我们预备知识介绍的依赖

    "dependencies": {
        "chalk": "^2.4.2",
        "commander": "^2.19.0",
        "download-git-repo": "^1.1.0",
        "inquirer": "^6.2.2",
        "ora": "^3.2.0"
    }

    然后执行npm i 安装这些依赖

  3. 建立bin文件夹,并在bin文件夹下建立以下文件,这些文件就是我们执行的脚本,其 中rv-add用于添加项目,rv-delete用于删除一个项目,rv-init用于初始化一个项目 ,rv-list用于展示已添加的项目列表,而rv可以作为我们的入口文件。接下来我们 一步步完善这些脚本

  4. 在建立的rv文件中添加一段熟悉的代码

    #!/usr/bin/env node
    console.log('hello world');

    然后执行 node ./bin/rv 就会输出 hello world了,这里大家会发现头部有一行#!/usr/bin/env node,这个的 作用就是告诉系统脚本的执行程序是node,并且系统会自动寻找node的所在路径。有了这行代码我们可以直 接执行 ./bin/rv而不用加node了

  5. 我们仿照大多数的脚手架的设计,实现以下需求:在命令行输入rv add 即执行rv-add,执行rv delete即执行rv-delete... 其他同理,以下以rv-add为例。 首先我们先搞一下rv这个文件,话不多讲,直接贴代码:

    #!/usr/bin/env node
    const program = require('commander')
    // 定义当前版本
    // 定义使用方法
    // 定义四个指令
    program
      .version(require('../package').version)
      .usage('<command> [options]')
      .command('add', 'add a new template')
      .command('delete', 'delete a template')
      .command('list', 'list all the templates')
      .command('init', 'generate a new project from a template')
         
    // 解析命令行参数
    program.parse(process.argv)

    这里简单解释下:前面在定义的时候 program.command('init').action(() => {})其实本来还有个 action 回调,但是我们没写,commander 就会尝试在入口脚本的目录中搜索可执行文件,找到形如 program-command,于是这样的话,我们输入./bin rv add 执行的其实就是./bin rv-add

    然后再简单搞一下rv-add文件

    #!/usr/bin/env node
    console.log('hello rv-add!')

    当我们执行./bin/rv时可以看到输出了以下内容 接下来验证一下执行 ./bin rv add 是否执行了rv-add,在命令行输入./bin rv add,如果输出 hello rv-add,说明我们成功了!

    完善各个脚本文件

    rv-add

    接下来我们完善一下rv-add,执行它之后帮我们添加一个项目,在完善它之前,我们首先在根目录下建立一个template.json,内容就是一个空对象{}即可。接下来直接贴代码

    #!/usr/bin/env node
    // 交互式命令行
    const inquirer = require('inquirer')
    // 修改控制台字符串的样式
    const chalk = require('chalk')
    // node 内置文件模块
    const fs = require('fs')
    // 读取根目录下的 template.json
    const tplObj = require(`${__dirname}/../template.json`)
    // 自定义交互式命令行的问题及简单的校验
    let question = [
      {
        name: "name",
        type: 'input',
        message: "请输入模板名称",
        validate (val) {
          if (val === '') {
            return 'Name is required!'
          } else if (tplObj[val]) {
            return 'Template has already existed!'
          } else {
            return true
          }
        }
      },
      {
        name: "url",
        type: 'input',
        message: "请输入模板地址",
        validate (val) {
          if (val === '') return 'The url is required!'
          return true
        }
      }
    ]
    inquirer
      .prompt(question).then(answers => {
        // answers 就是用户输入的内容,是个对象
        let { name, url } = answers;
        // 过滤 unicode 字符
        tplObj[name] = url.replace(/[\u0000-\u0019]/g, '')
        // 把模板信息写入 template.json 文件中
        fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(tplObj), 'utf-8', err => {
          if (err) console.log(err)
          console.log('\n')
          console.log(chalk.green('Added successfully!\n'))
          console.log(chalk.grey('The latest template list is: \n'))
          console.log(tplObj)
          console.log('\n')
        })
      })

    注释写的也比较清楚,我就不多解释啦 执行以下看看效果: 这里先说明一下模版地址不需要完整的,只需要贴上下边红框内的即可

    rv-delete文件

    删除一个项目

    #!/usr/bin/env node
    const inquirer = require('inquirer')
    const chalk = require('chalk')
    const fs = require('fs')
    const tplObj = require(`${__dirname}/../template`)
    let question = [
      {
        name: "name",
        message: "请输入要删除的模板名称",
        validate (val) {
          if (val === '') {
            return 'Name is required!'
          } else if (!tplObj[val]) {
            return 'Template does not exist!'
          } else  {
            return true
          }
        }
      }
    ]
    inquirer
      .prompt(question).then(answers => {
        let { name } = answers;
        delete tplObj[name]
        // 更新 template.json 文件
        fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(tplObj), 'utf-8', err => {
          if (err) console.log(err)
          console.log('\n')
          console.log(chalk.green('Deleted successfully!\n'))
          console.log(chalk.grey('The latest template list is: \n'))
          console.log(tplObj)
          console.log('\n')
        })
      })

    接下来看看执行的效果 delete完之后,template就是{}了,因为之后我们还要用test模版,这里我们再把它add回去

    rv-list

    这个就比较简单了,就是输出项目列表

    #!/usr/bin/env node
    const tplObj = require(`${__dirname}/../template`)
    console.log(tplObj)

    执行结果如下:

    rv-init

    接下来是 init命令,这个要稍微麻烦一点,不过还好,代码走起

    #!/usr/bin/env node
    const program = require('commander')
    const chalk = require('chalk')
    const ora = require('ora')
    const download = require('download-git-repo')
    const tplObj = require(`${__dirname}/../template`)
    program
      .usage('<template-name> [project-name]')
    program.parse(process.argv)
    // 当没有输入参数的时候给个提示
    if (program.args.length < 1) return program.help()
    // 好比 vue init webpack project-name 的命令一样,第一个参数是 webpack,第二个参数是 project-name
    let templateName = program.args[0]
    let projectName = program.args[1]
    // 小小校验一下参数
    if (!tplObj[templateName]) {
      console.log(chalk.red('\n Template does not exit! \n '))
      return
    }
    if (!projectName) {
      console.log(chalk.red('\n Project should not be empty! \n '))
      return
    }
    url = tplObj[templateName]
    console.log(chalk.white('\n Start generating... \n'))
    // 出现加载图标
    const spinner = ora("Downloading...");
    spinner.start();
    // 执行下载方法并传入参数
    download (
      url,
      projectName,
      err => {
        if (err) {
          spinner.fail();
          console.log(chalk.red(`Generation failed. ${err}`))
          return
        }
        // 结束加载图标
        spinner.succeed();
        console.log(chalk.green('\n Generation completed!'))
        console.log('\n To get started')
        console.log(`\n    cd ${projectName} \n`)
      }
    )

    这里便用到了下载远程仓库的依赖 download-git-repo,这里我仿照的是 vue init webpack project-name,webpack就是模版名,project-name就是项目名,也是项目的根目录,结下来我们执行一下 rv init test demo 看看发生了什么吧 执行之后,就开始下载远程仓库并生成demo文件夹,此时就生成了一个demo项目了 但是大家有没有发现,我的init命令是 rv init test demo 而不是 ./bin/rv init test demo,如果你直接执行 rv init test demo一定会失败,因为我做了一个操作让 rv 指向了./bin/rv,其实很简单,就是配置一下 package.json,即配置一下bin字段

    "bin": {
       "rv": "./bin/rv",
       "rv-add": "bin/rv-add",
       "rv-delete": "bin/rv-delete",
       "rv-list": "bin/rv-list",
       "rv-init": "bin/rv-init"
    },

    然后在package.json所在目录下执行npm link,这样便完成了命令的指向而且是全局的 至此,我们的脚手架就基本完成了,给自己鼓个掌吧!!

3. 将脚手架发布到npm

  1. 要发布npm包,首先你得有一个npm账号,注册完成之后,执行npm login命令输入你的用户名、密码、邮箱,然后就可以执行npm publish发布你的npm包了。但是rv-cli这个名字已经存在了,在发布之前,你得换个名字了,然后在npm上搜一下验证下你起的名字是不是已经被占用。哦对了,你还得把demo文件夹删掉,这个我们用来测试的就不要发上去了。
  2. 建立.npmignore,这个和.gitignore一样,我们在.npmignore添加 /node_modules即可,哦对了,你还得把demo文件夹删掉,这个我们用来测试的就不要发上去了。最后我们执行npm publish即可
  3. 发布成功之后,你可以去npm上搜一下,就可以看到自己的包啦,鼓掌!!!
  4. 最后我们在验证一波,是不是可以安装下来,命令行执行 npm i rv-cli -g,安装完之后,执行一下几个命令,耶,成功啦!!

4. 总结

看到这里,你是不是也觉得一个脚手架也不过如此而已嘛,虽然这个还比较初级,但是其实也具备了基本的功能,好啦,我要去研究研究vue-cli了,等我研究完再来更新一篇

码字不易,欢迎点赞

参考:

https://juejin.im/post/5a31d210f265da431a43330e

https://juejin.im/post/5c94fef7f265da60fd0c15e8