我们来开发一个npm命令行工具吧!!!

·  阅读 562

前言

因为之前在学习webpack相关的东西,就顺手搞了一个极简的脚手架,方便自己后面开发脚手架或者其他东西都可以热更新。因为每次都要复制这个脚手架出来作为基础进行开发,就有点烦了。因此 为什么不想办法通过命令行直接下载多好!

作为社会主义前端的接班人,当然是能用js就js了呀。所以 npm命令行工具当仁不让,就搞你了!

初始化项目

新建一个npm项目。我打算叫它 daily_template_cli。 因为目标是不仅仅下载上文的webpack模板,也希望平时用的其他杂七杂八的东西都可以下载一下。

在项目目录下运行 npm init生成 package.json文件。各位随便填了哈。 这是我的👇

{
  "name": "daily_template_cli",
  "version": "1.0.0",
  "description": " for download usually used template",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "lishang",
  "license": "ISC"
}

复制代码

定义一个简单的命令

定义命令我们需要用到一个字段 叫做bin。这个字段对应一个对象,key是我们对应的命令 value 则是对应的文件。 具体的描述戳这里 定义一个 daily_template_cli的命令吧。

{
 "name": "daily_template_cli",
 "version": "1.0.0",
 "description": " for download usually used template",
 "main": "index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "bin": {
   "daily_template_cli": "index.js"
 },
 "author": "lishang",
 "license": "ISC"
}

复制代码

这里设置了对应的文件为index.js。我们在根目录下新建一个index.js文件,然后写上程序猿学习的通用开始宣言.

// index.js
#!/usr/bin/env node

console.log('hello world!') 
复制代码

注意:第一行一定要添加脚本来指定运行环境(#!/usr/bin/env node)!!!

发布

为了让我们的命令跑起来,那么我们肯定是要将这个简单的npm包发布到npm上,然后全局安装。 首先 检查我们的仓库地址。很多时候我们会使用taobao的镜像地址,这个我们是发布不了的。我们要把地址换成 npm的官方地址。

在命令行下运行如下命令

npm config set registry https://registry.npmjs.org/
复制代码

然后就是npm的发布流程了, 这个不用过多赘述,各位自行谷歌或者百度即可。

发布成功之后大致如下图

屏幕快照 2021-08-23 下午11.09.23.png

接下来 将其全局安装

npm install daily_template_cli -g
复制代码

在命令行输入 daily_template_cli 就可以看到hello world

屏幕快照 2021-08-23 下午11.20.05.png

目前你已经学会了 1+1啦,接下来请解决 $%$@%$#$问题。 哈哈 接下来我们就开始编写我们的模板下载工具吧。

预说明

如果我们改动一些东西就要发布一次,这样会很麻烦,好在npm也考虑到了这个问题,我们可以使用 npm link 来将解决这个问题。

在我们的项目下运行 npm link 会建立一个映射关系,将我们的开发环境映射到全局,这样我们在本地修改之后,就不用辛苦发布了。 npm link的具体用法及详情我们这里不做讨论,有兴趣可以参考这个

传送门一号

官方文档

option 选项

我们使用commander来开发命令行。官方文档

npm install commander
复制代码

定义-v选项获取版本号

通过option 定义一个选项,让我们可以通过 -v--version的方式获取版本号

对入口文件 index.js改造如下

#!/usr/bin/env node

const { program } = require('commander');

// 定义命令 -v  --verson
program.option('-v, --version', 'get project version');

// 解析参数
program.parse(process.argv);

// 获取到option
const options = program.opts();

if (options.version) {
    let info = require('./package.json');
    console.log(info.version);
}

复制代码

解释一下上面的代码,首先我们引入了全局对象 program。 根据官方文档的说明,其实引入commander对象有多种方法,因为项目不怎么复杂,所以直接引入全局对象program即可。

然后我们定义了命令-v, --version。 这里我们使用了 option方法。option中定义的命令也被称之为选项, 我们可以给一个选项提供多种对应的名称, 比如这里使用-v--version。 后面可以通过 daily_template_cli -v 或者 daily_template_cli --version均可以触发这个选项。

顺便多说一句

--可以标记选项的结束,后续的参数均不会被命令解释

举上面的例子,-v, --version, -ww, 则 -ww命令并不会被识别。

// 解析参数
program.parse(process.argv);

// 获取到option
const options = program.opts();
复制代码

上面这两句是我们解析参数以及获取到解析后的options。 如果我们输入的命令携带了某个选项,则在获取到的options中,对应的key会为true。 如下图

屏幕快照 2021-08-24 下午11.08.45.png

屏幕快照 2021-08-24 下午11.08.20.png

这里是先打个样, 了解一下commander.为了实现我们开头的目标,我们要用的不是 option而是command 也叫 命令。

command 命令

我们可以通过 command来添加命令。 commandoption 的不同之处最直观的体现就是 option定义的操作是通过-xxx的或者--xxx的方式触发, 而command 则没有 - 或者--的前缀。

dailiy_template_cli -v  // 这是选项  option
daily_template_cli init  // 这是命令
复制代码

定义一个 init 命令

在 commander中对 命令的使用方式也是多种多样,这里不多做介绍,有兴趣可以参考上面的文档, 这里用的是把 命令作为独立可执行的子命令(官方描述)。 从我浅薄的经验来看,我更愿意称之为 把命令抽离成单独的子模块。

创建一个 init.js 文件

const { program } = require('commander');

function init() {
    console.log('install template');
} 

init();
复制代码

在 index.js 文件中注册命令

#!/usr/bin/env node

const { program } = require('commander');

// 定义命令 -v  --verson
program.option('-v, --version, -ww', 'get project version');

// 注册 init命令,并指定init.js文件来处理
program.command('init', 'download template', { executableFile: 'init'}); 

// 解析参数
program.parse(process.argv);

const options = program.opts();

if (options.version) {
    let info = require('./package.json');
    console.log(info.version);
}
复制代码

然后就是 duang!!!

屏幕快照 2021-08-25 下午10.26.58.png

command命令是可以携带参数的。我们接下来改造一下我们的init命令。定义一个参数文件名吧 projectname

对上面的init命令改造如下

program.command('init <projectName>', 'download template', { executableFile: 'init'}); 
复制代码

然后在init.js

const { program } = require('commander');

program.parse(process.argv); // 解析获取参数
const args = program.args; // argus 会以数组形式返回 比如输入 basic_cli init testDemo  args 为 ['testDemo']
function init() {
    console.log('init', args)
}

init();
复制代码

屏幕快照 2021-08-28 下午3.19.03.png

接下来就是拉取模板操作了。

执行下载git模板操作

这里引入两个npm包

download-git-repo git下载工具

ora 显示加载中的loading效果

这里主要是改造 init.js文件

const { program } = require('commander');
const { promisify } = require('util'); // util.promisify (node8) 可以将回调方法 转化成返回promise的方法
const download = promisify(require('download-git-repo'));
const ora = require('ora');

// 模板地址   这个地址是 代表 github 下。JustDoIt521 账号下的 daily_templates仓库的 main分支。  
const BASE_URL = 'github:JustDoIt521/daily_templates#main';

program.parse(process.argv);
const args = program.args; // argus 会以数组形式返回 比如输入 basic_cli init testDemo  args 为 ['testDemo']

function init(projectName) {
    cloneTemplate(projectName);
}

async function cloneTemplate(projectName) {
    const processing = ora('clone is starting...');
    processing.start();
    try {
        await download(BASE_URL, projectName, {clone: false});
        processing.succeed('success');
    }  catch(e) {
        processing.fail('fail');
    }
}

init(...args);

复制代码

简单描述下, 这里我们通过program.parse先解析参数,并通过args获取。 在获取到参数之后,执行init方法 并下载模板。

接下来就是见证奇迹的时刻:

屏幕快照 2021-08-28 下午5.32.22.png

屏幕快照 2021-08-28 下午5.48.18.png

屏幕快照 2021-08-28 下午5.51.18.png

呐 这里就可以看到我们下载的结果啦!!!

注意

有一点需要注意的是。有可能你照着上面敲完 会出现报错情况。 初步估计可能和 node的版本有关(因为在我两台电脑上一个成功一个失败)。后来统一node的版本就可以了。 我目前用的是14.16.0的node版本。

另外 如果我们直接使用gihub上复制下来的地址 比如 https://github.com/JustDoIt521/daily_templates.git 会有128 的报错,因此 我上面采用了那样的格式。

项目地址

脚手架地址

分类:
开发工具
标签:
分类:
开发工具
标签:
收藏成功!
已添加到「」, 点击更改