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

qin-vue-cli

v1.0.7

Published

自定义node命令脚手架

Downloads

8

Readme

前言

本文章以MacBook为实例,Windows下可能有所差异。

在用vue-cli脚手架的时候,总感觉全局的vue命令 (vue init webpack project) 特别的神奇,一直想找时间学习、亲自动手手写一个自己的脚手架。这样就可以自定义属于自己的脚手架(初始化常用配置、类库...),来提高项目工作效率。

最终目标

发布到npm,然后安装到本地全局命令下。 以个人npm包为例:qinvue init test-project,终端下能执行,创建项目即可。

准备工作,理解以下工具

  1. commander,可以自动的解析命令和参数,用于处理用户输入的命令。
  2. download-git-repo,下载并提取 git 仓库,用于下载项目模板。
  3. Inquirer.js,通用的命令行用户界面集合,用于和用户进行交互。
  4. handlebars.js,模板引擎,将用户提交的信息动态填充到文件中。
  5. ora,下载过程久的话,可以用于显示下载中的动画效果。
  6. chalk,可以给终端的字体加上颜色。
  7. log-symbols,可以在终端上显示出 √ 或 × 等的图标。

初始化项目

首先创建一个空项目,暂时命名为 my-cli,然后新建一个 index.js 文件,再执行 npm init 生成一个 package.json 文件。

终端输入命令:

npm init

会依次让你输入一下package选项:

  1. package name: (testpackage) qin-vue-cli
  2. version: (1.0.0)
  3. description:自定义node命令脚手
  4. entry point: (index.js)
  5. test command:
  6. git repository:
  7. keywords:
  8. author:Qin
  9. license: (ISC)
  10. 省略多行代码提示,Is this ok? (yes)
  • 一、是输入你包的名称(testpackage)是我默认的测试包,默认值是根据文件夹名来显示的,qin-vue-cli是我的自定义值,输入覆盖掉默认值。注意: 不要跟npm上已有的包重名,否则包会发布失败。
  • 二、包的版本号。初始化默认为1.0.0。注意:每次更新包都需要手动改版本号,否则也发布失败。
  • 三、包的描述。
  • 四、入口文件默认为index.js。
  • 五、测试命令。在scripts选项里生成对应的test:'输入测试命令的代码',我这里默认跳过。
  • 六、Github 地址(回车默认跳过)
  • 七、关键词(回车默认跳过)
  • 八、作者,我填了Qin
  • 九、许可,默认ISC(可直接回车跳过)
  • 十、最后输入yes即可完成创建package.json

重点:在package.json中手动添加bin选项,值为即将要编写的命令代码文件index.js路径,因为index.js跟package.json为同一级目录所以直接写index.js。最终的package.json如下:

{
  "name": "qin-vue-cli",
  "version": "1.0.0",
  "description": "自定义node命令脚手架",
  "main": "index.js",
  "bin": {
    "qinvue": "index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Qin",
  "license": "ISC",
  "dependencies": {
    "chalk": "^2.4.1",
    "commander": "^2.19.0",
    "download-git-repo": "^1.1.0",
    "handlebars": "^4.0.12",
    "inquirer": "^6.2.0",
    "log-symbols": "^2.2.0",
    "ora": "^3.0.0"
  }
}

最后安装上面需要用到的依赖。

npm install commander download-git-repo inquirer handlebars ora chalk log-symbols -S

创建index.js

这里先上完整的代码:

#!/usr/bin/env node
var fs = require('fs')
var program = require('commander')
var download = require('download-git-repo')
var handlebars = require('handlebars')
var inquirer = require('inquirer')
var ora = require('ora')
var chalk = require('chalk')
var symbols = require('log-symbols')

program.version('1.0.0', '-v, --version')
        .command('init <name>')
    .action((name) => {
      console.log(name)
      if(!fs.existsSync(name)){
        inquirer.prompt([
          {
            name: 'description',
            message: '请输入项目描述'
          },
          {
            name: 'author',
            message: '请输入作者名称'
          }
        ]).then((answers) => {
          console.log(answers.author);
          var spinner = ora('正在下载模板')
          spinner.start()
          download(
            'https://github.com:123428653/vue-cli-webpack4', 
            name, 
            {
              clone: true
            },
            (err) => {
              console.log(err ? 'Error' : 'Success')
              if(err){
                spinner.fail()
                console.log(symbols.error, chalk.red(err))
              }else{
                spinner.succeed()
                var fileName = `${name}/package.json`
                var meta = {
                  name,
                  description: answers.description,
                  author: answers.author
                }
                if(fs.existsSync(fileName)){
                  var content = fs.readFileSync(fileName).toString()
                  var result = handlebars.compile(content)(meta)
                  fs.writeFileSync(fileName, result)
                }
                console.log(symbols.success, chalk.green('项目初始化成功'))
              }
            }
          )
        })
      }else{
        console.log(symbols.error, chalk.red('项目已存在'))
      }
    })
program.parse(process.argv)

代码讲解

填坑一: #!/usr/bin/env node,必须写,否则执行命令会报错 报错信息如下:

/usr/local/bin/qinvue: line 2: syntax error near unexpected token `('

/usr/local/bin/qinvue: line 2: `var fs = require('fs')'

填坑二: 代码结尾不能有分号出现,否则执行命令也报以上一样错。

program.version('1.0.5', '-v, --version')
    .command('init <name>')
    .action((name) => {
        console.log(name)
        ...
    })

调用 version('1.0.0', '-v, --version') 会将 -v 和 --version 添加到命令中,可以通过这些选项打印出版本号。

调用 command('init ') 定义 init 命令,name 则是必传的参数,为项目名。 action() 则是执行 init 命令会发生的行为,要生成项目的过程就是在这里面执行的,这里暂时只打印出 name。

其实到这里,已经可以执行 init 命令了。我们来测试一下,在 my-cli 的同级目录下执行:

node index.js init HelloWorld

可以看到命令行工具也打印出了 HelloWorld,那么很清楚, action((name) => {}) 这里的参数 name,就是我们执行 init 命令时输入的项目名称。

命令已经完成,接下来就要下载模板生成项目结构了。

下载模板

#!/usr/bin/env node
var program = require('commander');
var download = require('download-git-repo');

program.version('1.0.5', '-v, --version')
    .command('init <name>')
    .action((name) => {
        ...
        download(
          'https://github.com:123428653/vue-cli-webpack4', 
          name, 
          {
            clone: true
          }, 
          (err) => {
      console.log(err ? 'Error' : 'Success')
      ...
      }
      ...
    })

download() 第一个参数就是仓库地址,但是有一点点不一样。实际的仓库地址是https://github.com/123428653/vue-cli-webpack4 ,可以看到github.com后面的 '/' 在参数中要写成 ':'。第二个参数是路径,上面我们直接在当前路径下创建一个 name 的文件夹存放模板,也可以使用二级目录比如 test/${name}

命令行交互

命令行交互功能可以在用户执行 init 命令后,向用户提出问题,接收用户的输入并作出相应的处理。这里使用 inquirer.js 来实现。

...
const inquirer = require('inquirer');
...

inquirer.prompt([
  {
    type: 'input',
    name: 'author',
    message: '请输入作者名称'
  }
]).then((answers) => {
  console.log(answers.author);
  ...
})

通过这里例子可以看出,问题就放在 prompt() 中,问题的类型为 input 就是输入类型,name 就是作为答案对象中的 key,message 就是问题了,用户输入的答案就在 answers 中,使用起来就是这么简单。更多的参数设置可以参考官方文档。

通过命令行交互,获得用户的输入,从而可以把答案渲染到模板中。

渲染模板

这里用 handlebars 的语法对 123428653/vue-cli-webpack4 仓库的模板中的 package.json 文件做一些修改

{
  "name": "{{name}}",
  "version": "1.0.0",
  "description": "{{description}}",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "{{author}}",
  "license": "ISC"
}

并在下载模板完成之后将用户输入的答案渲染到 package.json 中

#!/usr/bin/env node
var program = require('commander');
var download = require('download-git-repo');

program.version('1.0.5', '-v, --version')
    .command('init <name>')
    .action((name) => {
        ...
        download(
          'https://github.com:123428653/vue-cli-webpack4', 
          name, 
          {
            clone: true
          }, 
          (err) => {
            ...
          
        var fileName = `${name}/package.json`
            var meta = {
              name,
              description: answers.description,
              author: answers.author
            }
            if(fs.existsSync(fileName)){
              var content = fs.readFileSync(fileName).toString()
              var result = handlebars.compile(content)(meta)
              fs.writeFileSync(fileName, result)
            }
            ...
      }
      ...
    })

这里使用了 node.js 的文件模块 fs,将 handlebars 渲染完后的模板重新写入到文件中。

提示、Loading、图标

在用户输入答案之后,开始下载模板,这时候使用 ora 来提示用户正在下载中。

const ora = require('ora');
// 开始下载
const spinner = ora('正在下载模板...');
spinner.start();

// 下载失败调用
spinner.fail();

// 下载成功调用
spinner.succeed();

然后通过 chalk 来为打印信息加上样式,比如成功信息为绿色,失败信息为红色,这样子会让用户更加容易分辨,同时也让终端的显示更加的好看。

const chalk = require('chalk');
console.log(chalk.green('项目创建成功'));
console.log(chalk.red('项目创建失败'));

除了给打印信息加上颜色之外,还可以使用 log-symbols 在信息前面加上 √ 或 × 等的图标

const chalk = require('chalk');
const symbols = require('log-symbols');
console.log(symbols.success, chalk.green('项目创建成功'));
console.log(symbols.error, chalk.red('项目创建失败'));

发布NPM包

没有npm账号的,先上npm官网注册个人账号。 终端下执行:

npm login

Username: 输入已有的npm用户名

Password: 输入密码

Email: 输入注册的邮箱

显示如下:

Logged in as xxx on https://registry.npmjs.org/.

说明登录成功

最后在执行命令:

npm publish

提示一下即可发布成功:

截图

浏览器登录npm上查看发布的包:

截图

全局安装发布的node命令

sudo npm i qin-vue-cli -g

sodu 是Mac下权限的命令

安装完成:

截图

完美执行自定义命令

qinvue init test-project

初始化项目:

截图

结束

以上内容部分出自 https://github.com/lin-xin/blog/issues/27#issuecomment-441503411

最后留下本人npm发布的包 https://www.npmjs.com/package/qin-vue-cli