从 0 编写 cli ,原来自己写一个 cli 也不难嘛~

2,601 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

公司里写前端代码这么多年,看着周围的小伙伴还在用着各种不同的工具来进行创建项目、打包项目、发布项目,就想着我得为他们做点什么~

要么,我们把这些常用高频功能集成到一个 CLI 里好啦!

再手撸一个 CLI 之前,我想先问一下:

CLI 是什么?

官方介绍:

命令行界面(英语:command-line interface缩写CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。

像我们常用的 npm installnpm run xxx 这些都属于 CLI 命令。

那如何基于 nodejs 编写自定义 CLI 库呢?

先别被自己手写 CLI 吓退,其实也没有那么复杂,说白了

  1. 写一段 nodejs 脚本,干了某件事
  2. 给这段脚本命个名叫 xxx
  3. 然后把这个脚本注册到全局

最后,我们就可以愉快的访问 xxx 啦!

郑重说明,我没有在开玩笑,实际上就是这个流程而已,很多事情甚至 npm 已经帮我们完善的很简单啦!

怎么创建?

1、新建项目

先随便找个文件夹,然后初始化项目 npm init,这个都会是吧,那我们过~

2、编辑 package.json

{
    "name": "my-vue",
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT",
    "type": "module", // 这里如果不用 ESM 的话可以不用加
    "bin": { 
        "my-vue": "index.js"
    }, // 这里才是重中之重
    "dependencies": {
        ...
    }
}

我们发现这里比我们以往写的项目多了一个 bin,那么 bin 里面的对象代表什么意思呢?

其实啊,bin 中的 my-vue 相当于我们给我们的 CLI 命了名,然后指向我们的入口文件 index.js

通过 npm link 命令可以把我们的库在 node 全局建立一个软链接,相当于我们 npm install -g my-vue 一样,可以通过 my-vue 直接访问到指向的 index.js 文件

这个时候需要在目录下新建一个 index.js 文件,然后在第一句写这么一句话

#!/usr/bin/env node

这是为了告诉系统该脚本要用 node 环境来执行

#!/usr/bin/env node

console.log("进来啦");

然后,我们在命令行执行 my-vue

就能看到这样的结果

image.png

小结一下

  1. 编辑 package.json ,通过 bin 属性,给我们的包命名
  2. 通过 npm link 命令将包在全局注册
  3. 命令行访问 my-vue 查看输出

自己写的 CLI 已经可以运行了,开心撒花,那我们今天的分享到这里就结束啦~

......

小儿科了不是,我们继续往下看

怎么写命令?

通过上一环节,我们已经可以成功的在命令行里执行我们的代码了

接下来我们要像 vue-cli 那样用 vue create app 这样的命令来做一些事情。

这个时候就要用到大名鼎鼎的 commander 库,毕竟它是 nodejs 作为命令行界面的完整解决方案,不服不行啊。

简单介绍一下这个库:

import { program } from "commander";

// 用来声明库的版本号
program.version("1.0.0");

// Commander 的第一个参数为命令名称。
// 命令参数可以跟在名称后面,也可以用`.argument()`单独指定。
// 参数可为必选的(尖括号表示)、可选的(方括号表示)或变长参数(点号表示,如果使用,只能是最后一个参数)
program
    .command("init <app-name>");
    // 该命令的描述,可选的
    .description("create a new project by my-template")
    // Commander 使用`.option()`方法来定义选项,同时可以附加选项的简介。
    // 每个选项可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(--后面接一个或多个单词),使用逗号、空格或`|`分隔。
    .option("--v3", "create a new project by my-template-v3")
    .option("-p, --pc", "create pc template")
    .option("-a, --app", "create app template")
    .option("-f, --force", "force update template")
    // 命令处理函数的参数,为该命令声明的所有参数,除此之外还会附加两个额外参数:
    // 一个是解析出的选项,另一个则是该命令对象自身。
    .action((name, options, command) => {
        ...
    }

// 声明多个命令直接注册即可
program
    .command("publish")
    .description("publish project to remote server")
    .option("-c, --config <key name>", "publish upload config,config is sftp.config.js key name")
    .action((options) => {
        ...
    });
    
// 解析命令行中的输入,根据输入内容匹配相对应的命令实现
program.parse()

详细的使用教程可以去 GitHub 仓库查看,传送门

好,有了基本的命令结构,我们只需要专注于功能实现即可。

那上面的代码为例,我们通过命令配合多个选项组合,基本可以实现定制初始化工程模板以及工程发布行为。

小结一下

  1. npm install commander
  2. 通过全局的 program 创建自己的命令,以及配置可选项
  3. 实现相关业务逻辑

一键安装

对于多人协作的情况下,这么一套自定义命令行如何快速推广呢?

  1. 注册 npm 账号,发布 CLI 到公共仓库,小伙伴们通过 npm install -g my-vue 来进行全局安装并使用
  2. 很多情况下公司内部定制的 CLI 都是内部使用的,也没有必要发布到公共仓库,我们可以让小伙伴从代码仓库拉到本地,install 之后再 npm link 即可。

针对第二种方案,为了减少小伙伴的心智负担,我们可以写一个 install 脚本,拉下来之后直接执行 install 脚本来完成一键安装,简单好用。

这里分享一下我的 install 脚本,这个脚本依赖了一个好用的 shelljs 库,可以在 node 环境执行 shell 脚本,大家有兴趣可以学习一下

import shelljs from "shelljs";

// 兼容大家本地的各种包管理工具
if (shelljs.which("pnpm")) {
    shelljs.exec("pnpm install");
} else if (shelljs.which("yarn")) {
    shelljs.exec("yarn");
} else {
    shelljs.exec("npm install");
}

// link 到全局
shelljs.exec("npm link");

// 验证是否全局安装成功
shelljs.exec("my-vue");

npm unlink 可以解除软连接,也就是全局卸载

总结

我们介绍了如何自行开发一款 CLI 工具,这不仅对于个人来说扩展了知识面,也能很好的利用技术来提高团队协作的效率,很多事情能用脚本自动化完成的就努力的让其代替重复的人工操作,工具越积累越多,团队协作效率也会越来越高。

如果有对 CLI 功能实现细节有想法的小伙伴,欢迎在评论区讨论哦~