如何命令行结束react程序_命令行交互工具

更好阅读体验:

命令行交互工具 · 语雀​www.yuque.com
a63d175d0470a4c20d42d7dd7c043e0e.png

CLI 是 Command-Line Interface 缩写,是命令行交互的意思,使用过 vue-cli 或者 create-react-app 一定会对脚手架给日常开发带来的效率提升印象深刻,这也是 Node.js 的一个典型应用场景——为前端解决代码初始化、构建甚至发布功能

需求分析

一个简单的 react 前端应用目录大概是这样的 https://github.com/Samaritan89/react-project-demo

.
├── src
│   ├── app.less
│   ├── app.tsx
│   ├── index.html
│   └── index.tsx
├── test
│   └── app.test.js
├── .eslintrc.js
├── .gitignore
├── README.md
├── babel.config.js
├── jest.config.js
├── package.json
├── tsconfig.json
└── webpack.config.js

除了目录结构本身还有大量的配置文件,每次从头开始非常麻烦,可以使用 Node.js 实现 CLI 工具,通过命令 sly 和用户做简单信息确认后直接生成对应的目录结构和内容,执行过程大概如下

9412329a9246c45b2c9b0b978704a8a3.png

命令是如何被执行的

在 Linux/Mac 最常用的命令就是 cd

$ cd /home/admin

cd 首先是一个可被 Shell 执行的程序,通过 where 命令可以查看其存储位置

$ where cd
/usr/bin/cd

之所以可以省略路径直接使用 cd 命令,是因为操作系统会尝试在几个预置的目录中匹配可执行程序名称,预置目录的配置在环境变量 $PATH

$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
POSIX 多个 PATH 使用 : 分隔,Windows 使用 ; 分隔

npm 通过 package.json 的 bin 字段提供了自动注册环境变量的功能

{
    ...
    "bin": {
        "sly": "src/index.js"
    }
}

这样配置后,当用户全局安装 package 就会把 sly 命令添加到 /usr/local/bin 目录

$ where sly
/usr/local/bin/sly

npm init

理解了命令注册后就可以开始动手写命令行工具的实现代码了,首先创建目录后执行 npm init

$ mkdir sly-cli
$ cd sly-cli
$ npm init

在 package.json 文件手工添加 bin 配置

{
    ...
    "bin": {
        "sly": "src/index.js"
    }
}

为了让 Shell 知道使用 node 解析执行 src/index.js 需要在文件顶部添加 Shebang

Shebang是一个由井号和叹号构成的字符序列#!,其出现在文本文件的第一行的前两个字符。 在文件中存在Shebang的情况下,类Unix操作系统的程序加载器会分析Shebang后的内容,将这些内容作为解释器指令,并调用该指令,并将载有Shebang的文件路径作为该解释器的参数

src/index.js

#!/usr/bin/env node

// node code

命令行交互

为了在命令行和用户形成问答交互需要两个工具

  1. commander Node.js 命令行工具书写框架,使用可以参考文档
  2. inquirer 在命令行提供和用户问答的交互
const inquirer = require('inquirer');
const program = require('commander');

const { version } = require('../package.json');

const questions = [
    {
      type: 'input',
      name: 'name',
      message: '项目名称',
    },
    {
      type: 'input',
      name: 'version',
      message: '版本',
      default: '1.0.0'
    },
    {
      type: 'input',
      name: 'description',
      message: '项目描述'
    },
    {
      type: 'input',
      name: 'gitUrl',
      message: 'git 地址',
    },
    {
      type: 'input',
      name: 'author',
      message: '作者',
    }
  ]

program
  .version(version, '-v, --version')
  .command('init')
  .action(async () => {
    const questions = [];
    const answers = await inquirer.prompt(questions);
    // TODO: 更根据 anwsers 渲染模板,复制到本地
  });

program.parse(process.argv);

为了防止用户输入过多信息比较繁琐,可以为每个问题通过 default 属性设置默认值

const path = require('path');
const gitRemoteOriginUrl = require('git-remote-origin-url');
const gitRepoInfo = require('git-repo-info');

const defaultName = path.parse(process.cwd()).name;
let author = '';
let repoUrl = '';

try {
  const gitInfo = gitRepoInfo();
  author = gitInfo.author;
  repoUrl = await gitRemoteOriginUrl();
} catch (ex) {

}

这样一个没有实际功能的脚手架就完成了,接下来完善具体动作

模板下载

文章开始展示的 react 项目代码中模块名称、版本号、描述、作者、git 地址等都是固定的,需要稍微修改为变量的方式,结合用户输入,生成正确内容 模板化后 package.json 部分内容

{
  "name": "{{name}}",
  "version": "{{version}}",
  "description": "{{description}}",
  "main": "lib/index.js",
  "repository": {
    "type": "git",
    "url": "{{gitUrl}}"
  },
  "author": "{{author}}",
  "license": "ISC",
  "dependencies": {
  }
}

模板完整代码:https://github.com/Samaritan89/react-project-demo/tree/template

模板使用了 Handlebars 语法,为了让用户可以实时获取最新内容,需要把模板文件存储在网络,每次执行命令的时候下载最新版本,可以借助 download-git-repo 实现

const { promisify } = require('util');
const downloadGitRepo = require('download-git-repo');
const download = promisify(downloadGitRepo);

async (){
  const downloadFolder = path.join(process.cwd(), '.tmp');
  await download(
    'direct:https://github.com/Samaritan89/react-project-demo.git#template',
    downloadFolder, 
    { clone: true }
  );
}
url 地址 hash #template 是因为模板文件在 template 分支

生成本地文件

模板下载完成后使用 Handlerbas 渲染引擎传入用户交互数据即可渲染为最终文件内容,因为涉及多个文件 使用 vinyl-fs 方便处理 src/copy.js

const chalk = require('chalk');
const vfs = require('vinyl-fs');
const through = require('through2');
const Handlebars = require('handlebars');

function tpl(data) {
  return through.obj(function (file, encoding, callback) {
    console.log(`复制文件 ${chalk.grey(file.path)}`);
    if (file.contents) {
      const content = file.contents.toString(encoding);
      const template = Handlebars.compile(content);
      file.contents = Buffer.from(template(data), encoding);
    }
    this.push(file);
    callback();
  });
}

function copy(source, dest, data) {
  const worker = vfs.src(source)
    .pipe(tpl(data))
    .pipe(vfs.dest(dest));

  return new Promise(resolve => {
    worker.on('finish', () => {
      resolve();
    });
  });
}

module.exports = copy;

复制模板功能也可以使用 mem-fs-editor 实现

const memFs = require("mem-fs");
const editor = require("mem-fs-editor");

const store = memFs.create();
const fs = editor.create(store);
fs.copyTpl(from, to, context[, templateOptions [, copyOptions]]);

测试

把主要代码串联一下开发工具就完成了

  1. 和用户交互,收集名称、版本、描述、作者、git 地址等信息
  2. 下载模板
  3. 复制模板到本地

完整代码:https://github.com/Samaritan89/sly-cli

可以在项目根目录通过 npm link 的方式在本地验证

$ npm link
$ sly init

发布到 npm

测试没问题了就可以把工具发布到 npm 了

  1. 在 npm 官网注册一个账户
  2. 在命令行通过 npm login 登录
  3. 在项目根目录执行 npm publish 发布

接下来就可以和使用 vue-cli 一样在全局安装后使用了

$ npm i -g sly-cli # 这个包名被 demo 项目占用了,需要自己用一个新的名称
$ sly init

package 更新

如果 package 有了代码的更新,需要修改版本号后重新发布到 npm,版本号由 x.y.z 三位组成,修改要遵守语义化版本号规则

  • x:break change
  • y:add feature
  • z:bug fix

npm 也提供了命令辅助升级

$ npm version major # x 位 +1
$ npm version minor # y 位 +1
$ npm version patch # z 位 +1

为了防止包的发布造成问题,可以先发不 beta 版本,实际测试一段时间后再发布正式版本

$ npm publish --tag=beta

这时候用户安装的还是原来的版本,内测用户可以通过指定 beta tag 的方式安装测试版本

$ npm i -g sly-cli@beta

当测试完成后可以把内容更新到正式版本

$ npm dist-tag add sly-cli@x.y.z latest
x.y.z 表示实际的版本号

使用 yeoman

个人工具开发使用 commander 就足够了,标准的命令行交互都可以轻松开发,如果是团队工程需要多种脚手架管理可以使用专门做脚手架 & 生态的工具 yeoman

  1. yeoman generator 文档
  2. yeoman 官方教程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值