2021-03-13 更新使用说明 2019-12-04 初稿 create-self-cli.html 2019-03-26 写一个npm包来规范专题写法

制作一个基于Commander的cli脚手架工具

为啥要写一个脚手架:开发提效、约定配置、统一开发流程。

在中大时候一直觉得写页面不够规范,看了一些经验,可以通过npm cli来实现一键创建目标项目,经过调研发现以下技术:

  • inquirer, enquirer or prompts for 处理用户输入
  • email-prompt for convenient email input prompts
  • chalk or kleur for 颜色输出
  • ora for beautiful spinners
  • boxen for drawing boxes around your output
  • stmux for a tmux like UI
  • listr for progress lists
  • ink to build CLIs with React
  • meow or arg for basic argument parsing
  • commander and yargs for complex argument parsing and subcommand support
  • oclif a framework for building extensible CLIs by Heroku (gluegun as an alternative)

理想状态,我们可以通过脚手架来创建日常专题:

  1. yarn global add @zdwx/cli
  2. zdwx add p pc womensday // 新建pc专题,默认pc
  3. zdwx add -w --wap womensday // 新建wap专题

也可以和 vue 的脚手架一样,让用户自己选,最后拼接选项。开发完成一键发布,用到了 ftp 功能。

这样想达成下面的成果:

  • 创建项目、通用代码,封装axios工具、组件库
  • git操作,自动提交推送、打tag
  • 自动构建、部署cdn、打点埋点

让交付的前端页面:自动、标准、可量化

技术储备

什么是脚手架CLI

  1. vue create xxx -r xx -p 10000

有命令、参数、选项

options 一个- 表示短标,如果是 -a -b -c 可以写成 -abc
两个--表示长标,--dev-version

vue 为何能全局系统级别生效?
vue这个本质上是 node的js脚本,通过注入node全局脚本到系统环境变量,实现了node全局命令的映射,映射用到的是软连接概念。

  • whice vue 查找环境命令,得到 vue注册的位置 等同于 xxx/bin/vue
  • 查找软连接找到具体脚本位置

vue —> node [nodePath]/lib/node_modules/@vue/cli/bin/vue —> path/vue.js

安装的是 @vue/cli 但注册的是 vue?
通过 package.bin 注册了 vue 命令

如何通过执行js脚本?
开头声明使用node来执行 #!/usr/bin/env node

Commander

介绍第一个npm库:commander ,这是TJ大神的作品,用来解析cli输入的工具。

node.js 命令行接口的完整解决方案,灵感来自 Ruby 的 commander。

有几个核心概念:

  • options 选项,提供某种选项,比如告诉我版本号、进入安静模式、输出结果不要颜色等。
  • commands 命令,
  1. #!/usr/bin/env node
  2. const program = require('commander')
  3. program.version(require('../package').version, '-v', '--version')
  4. .command('init <name>', 'init project')
  5. .command('refresh','refresh routers...')
  6. program.parse(process.argv)
  7. //commander.option(‘-c —color <color>’,”指定颜色”).parse(process.argv)
  8. //if(commander.color) console.log(‘颜色是 %s’, commander.color)

Chalk

介绍 chalk 这个最简单,就是彩色输出的。https://www.npmjs.com/package/chalk

  1. log(chalk.blue('Hello') + ' World' + chalk.red('!'));
  2. log(chalk.blue.bgRed.bold('Hello world!'));

ora

  1. const ora = require('ora');
  2. const process = ora(`正在下载....${repo}`);
  3. process.start();
  4. process.fail()
  5. process.succeed()

inquirer

交互式选项

基本流程

1. 列出要使用到的依赖库

  • download-git-repo 下载仓库。可以指定一个文件夹,下载到本地。
  • ora 等待进度条
  • commander
  • handlebars
  • figlet
  • clear
  • chalk console变色
  • open

2. 创建 .bin/mycli.js

指明运行脚本要执行的命令

  1. #! /usr/bin/env node
  2. // 上一行固定写死
  3. console.log('cli')

package.json 添加

  1. "bin": {
  2. "axe": "bin/index.js"
  3. }

3. 链接全局

通过 npm i -g xxx 安装的包会全局生效,如何做到的?通过软连接 ln -s

比如:

  1. /usr/local/bin/http-server
  2. # 链接到
  3. /usr/local/lib/node_modules/http-server/bin/http-server

在当前目录执行 npm link ,在项目中就可以 npm link xxx package了

如果要取消

  • npm unlink 从全局包中移除,这时候软链接已经失效了
  • npm unlink [package] 这时候目标软连接已经失效了
  • 删除 node_module 重新安装

4. 命令参数解析

  1. const argv = require('process').argv;
  2. console.log(argv)

比如 axe create . -p 123
解析到的结果

  1. [
  2. '/Users/xinbao/.nvm/versions/node/v14.15.5/bin/node',
  3. '/Users/xinbao/myCode/cli-demo/node_modules/.bin/axe',
  4. 'create',
  5. '.',
  6. '-p',
  7. '123'
  8. ]

因此,commander 存在的意义就是帮我们把参数解析做好

发包

  1. #!/usr/bin/env bash
  2. npm config get registry # 检查仓库镜像库
  3. npm config set registry=http://registry.npmjs.org
  4. echo '请进行登录相关操作:'
  5. npm login # 登陆
  6. echo "-------publishing-------"
  7. npm publish # 发布
  8. npm config set registry=https://registry.npm.taobao.org # 设置为淘宝镜像
  9. echo "发布完成"
  10. exit

简单的参考案例 github.com/su37josephxia/vue-template

带 @scope 发包,比如 @axe.dev/create-web 会提示 You must sign up for private packages

  1. npm publish --access public

小技巧

放最后,但是很有用

使用更炫酷,更专业:让包名通过 create 开头。

yarn create

如果一个包 create- 开头,就可以说过它

  1. yarn create <create-xxx-package> [<args>]
  2. # 比如
  3. yarn create react-app my-app
  4. # 等同于
  5. yarn global add create-react-app
  6. create-react-app myapp

全局安装,如果有group必须还是要 @scope

npx

  1. npx package name

先把package下载到临时目录,使用后删除,下次执行会重新下载
可以添加 —no-install 不远程下载走本地

npm init

  1. npm init @scope/name
  2. # 等同于
  3. npx @scope/create-<name>

如果包本身叫 @scope/create 直接 npm init @scope