前言
因为之前在学习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的发布流程了, 这个不用过多赘述,各位自行谷歌或者百度即可。
发布成功之后大致如下图
接下来 将其全局安装
npm install daily_template_cli -g
复制代码
在命令行输入 daily_template_cli
就可以看到hello world
啦
目前你已经学会了 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。 如下图
这里是先打个样, 了解一下commander
.为了实现我们开头的目标,我们要用的不是 option
而是command
也叫 命令。
command 命令
我们可以通过 command
来添加命令。
command
和 option
的不同之处最直观的体现就是 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!!!
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();
复制代码
接下来就是拉取模板操作了。
执行下载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
方法 并下载模板。
接下来就是见证奇迹的时刻:
呐 这里就可以看到我们下载的结果啦!!!
注意
有一点需要注意的是。有可能你照着上面敲完 会出现报错情况。 初步估计可能和 node的版本有关(因为在我两台电脑上一个成功一个失败)。后来统一node的版本就可以了。 我目前用的是14.16.0
的node版本。
另外 如果我们直接使用gihub上复制下来的地址 比如 https://github.com/JustDoIt521/daily_templates.git
会有128
的报错,因此 我上面采用了那样的格式。