【JS】【自学总结】基于 node 写一个 todo 命令行工具
最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。
github 地址:https://github.com/qq44924588...
前言
最后公司官网要用 node 重构,所以这段时间开始在学习 node 系列知识,一般对于新东西的上手,就是做一个 todo
的功能,所以这里也是基于 node 写一个 todo
命令行工具。
最终成品:https://github.com/qq44924588...
命令行相关的库
commander.js
node.js
命令行界面的完整解决方案是Ruby中Commander
在node.js
中的实现。
为commandline程序提供强大的参数解析能力.是TJ所写的一个工具包,其作用是让node命令行程序的制作更加简单。
具体用法请参考 github: https://github.com/tj/command...
inquirer.js
inquirer.js 一个用户与命令行交互的工具
开始通过npm init
创建package.json
的时候就有大量与用户的交互(当然也可以通过参数来忽略输入);而现在大多数工程都是通过脚手架来创建的,使用脚手架的时候最明显的就是与命令行的交互,如果想自己做一个脚手架或者在某些时候要与用户进行交互,这个时候就不得不提到inquirer.js
了。
具体用法请参考 github: https://github.com/SBoudrias/...
初始化
1.运行yarn init -y
,添加package.json
文件
2.运行yarn add commander
,引入commander库
3.创建cli.js
使用 commander
创建子命令,这里指写一个子命令的使用方式,具体可以参考 github 上的项目
const program = require('commander');program
.command('add')
.description('add a task')
.action(() => {
console.log('添加任务');
});
4.运行node cli add
,node + 文件名 + 子命令,输出添加任务
5.对于数据的保存这里没有使用数据库存,简单的使用了文件,为此,我们需要封装文件相关的读和写操作,创建 db.js
,内容如下
const homedir = require('os').homedir()// 优先获取用户设置的 home
const home = process.env.HOME || homedir
const p = require('path')
const fs = require('fs')
const dbPath = p.join(home, '.todo')
const db = {
read (path = dbPath) {
// 读取之前的任务
return new Promise((resolve, reject) => {
fs.readFile(path, {
flag: 'a+'
}, (error, data) => {
if (error) return reject(error)
let list
try{
list = JSON.parse(data.toString())
} catch(error2) {
list = []
}
resolve(list)
})
})
},
write (list, path = dbPath) {
return new Promise((resolve, reject) => {
const string = JSON.stringify(list)
fs.writeFile(path, string, (error) => {
if (error) return reject(error)
resolve()
})
})
}
}
module.exports = db
这里我们在 home环境变量路径创建了一个 .todo
的文件,用来保存我们输入的数据。
对于 node Api 的查询,这里推荐一个网站 https://devdocs.io/,可以很方便查到对应方法使用方式,如下图所示:
6.任务的增删改都可以看作是一个接口,为此创建 index.js 用来封闭这些操作,这也叫作 面向接口编程,这里我们举例 添加方法的实现方式:
const db = require('./db.js')module.exports.add = async (title) => {
// 读取之前的任务
const list = await db.read()
// 往里面添加一个 title 任务
list.push({title, done: false})
// 存储任务到文件
await db.write(list)
}
测试
输入node cli.js add 买水
查看:cat ~/.todo
输出[{"title":"买水","done":false}]
7.显示所有任务
在 cli.js 添加一条子命令 list
:
program.command('list')
.description('show all todo list')
.action((...args) => {
api.showAll().then(res => {
// console.log('显示任务完毕!')
}, () => {
console.log('显示任务失败!')
})
});
在 index.js 中实现 showAll()
方法
const inquirer = require('inquirer')module.exports.showAll = async () => {
//读取之前的任务
const list = await db.read()
//对任务进行操作
inquirer
.prompt({
type: 'list',
name: 'index',
message: '请选择你想操作的任务?',
choices: [{name: '退出', value: '-1'}, ...list.map((task, index) => {
return {name: `${task.done ? '[x]' : '[_]'} ${index + 1} - ${task.title}`, value: index.toString()}
}), {name: '+ 创建任务', value: '-2'}]
})
.then(answer => {
const index = parseInt(answer.index)
if(index >= 0) {
//选中了一个任务
inquirer.prompt({
type: 'list',
name: 'action',
choices: [
{name: '退出', value: 'quit'},
{name: '已完成', value: 'markAsDone'},
{name: '未完成', value: 'markAsUndone'},
{name: '改标题', vaule: 'updateTitel'},
{name: '删除', value: 'remove'}
]
}).then(answer2 => {
switch (answer2.action) {
case 'markAsDone':
list[index].done = true
db.write(list)
break
case 'markAsUndone':
list[index].done = false
db.write(list)
break
case 'updateTitle':
inquirer.prompt({
type: 'input',
name: 'title',
message: '新的标题',
default: list[index].title
}).then(answer => {
list[index].title = answer.title
db.write(list)
})
break
case 'remove':
list.splice(index, 1)
db.write(list)
break
}
})
}else if(index === -2) {
//创建任务
inquirer.prompt({
type: 'input',
name: 'title',
message: '输入任务标题',
}).then(answer => {
list.push({
title: answer.title,
done: false
})
db.write(list)
})
}
});
}
然后运行 node cli.js list
就可以显示所有任务了,这里列出这个方法主要是分享一下如何来优化这段冗长的代码。
优化代码
封装函数,重命名,提高代码可读性
我们先把选中一个任务也就是 index >=0
的逻辑先全部提取出来,放到函数 askForAction
,然后把每个 switch case
的逻辑也都封装成一个函数
function askForAction (list, index) {
inquirer.prompt({
type: 'list', name: 'action',
message: '请选择操作',
choices: [
{ name: '退出', value: 'quit'},
{ name: '已完成', value: 'markAsDone'},
{ name: '未完成', value: 'markAsUnDone'},
{ name: '改标题', value: 'updateTitle'},
{ name: '删除', value: 'remove'},
]
}).then(answer => {
switch (answer.action) {
case 'markAsDone':
markAsDone(list, index)
break;
case 'markAsUnDone':
markAsUnDone()
break;
case 'updateTitle':
updateTitle()
break;
case 'remove':
remove()
break;
}
})
}
上面的 case
条件与我们函数又一致,所以可以在进一步优化,如下所示:
function askForAction (list, index) {// 选中一个任务
const actions = {
markAsDone,
markAsUnDone,
updateTitle,
updateTitle,
remove
}
inquirer.prompt({
type: 'list', name: 'action',
message: '请选择操作',
choices: [
{ name: '退出', value: 'quit'},
{ name: '已完成', value: 'markAsDone'},
{ name: '未完成', value: 'markAsUnDone'},
{ name: '改标题', value: 'updateTitle'},
{ name: '删除', value: 'remove'},
]
}).then(answer => {
const action = actions[actions.action]
action && action(list, index)
})
}
在重构中,大部分的 switch 是可以被优化成更多简单的模式。
发布到 npm
配置 package.json
{"name": "hi-node-todo",
"bin": {
"t": "cli.js"
},
"files": [
"*.js"
],
"version": "0.0.1",
"main": "index.js",
"license": "MIT",
"dependencies": {
"commander": "^6.2.1",
"inquirer": "^7.3.3"
}
}
bin 的作用
很多包都有一个或多个可执行的文件希望被放到PATH中。(实际上,就是这个功能让npm可执行的)。要用这个功能,给`package.json`中的bin字段一个命令名到文件位置的`map`。初始化的时候npm会将他链接到`prefix/bin`(全局初始化)或者`./node_modules/.bin/`(本地初始化)。
{ "bin" : { "npm" : "./cli.js" } }
当你初始化npm
,它会创建一个符号链接到cli.js
脚本到/usr/local/bin/npm
。如果你只有一个可执行文件,并且名字和包名一样。那么你可以只用一个字符串,比如
{ "name": "my-program" , "version": "1.2.5" , "bin": "./path/to/program" }
// 等价于
{ "name": "my-program" , "version": "1.2.5" , "bin" : { "my-program" : "./path/to/program" } }
files 的作用
files
是一个包含项目中的文件的数组。如果命名了一个文件夹,那也会包含文件夹中的文件。(除非被其他条件忽略了)你也可以提供一个.npmignore
文件,让即使被包含在files
字段中得文件被留下。其实就像.gitignore
一样。
{ "files": [ "bin/", "templates/", "test/" ]}
main 的作用
main
字段是一个模块ID,它是一个指向你程序的主要项目。就是说,如果你包的名字叫foo
,然后用户安装它,然后require("foo")
,然后你的main
模块的exports
对象会被返回。这应该是一个相对于根目录的模块ID。对于大多数模块,它是非常有意义的,其他的都没啥。
{ "main": "bin/index.js"}
发布:
- npm adduser
- npm publish
用户使用:
- yarn global add node-todos
- t list
版本升级: 增加查看版本的功能
cli.js
const pkg = require('./package.json')program
.version(pkg.version)
测试,输入node cli.js --version
,输出0.0.1
重新发布,直接npm publish
用户使用时,更新包yarn global add [email protected]
,运行t --version
,输出0.0.2
完~,我是小智,我们下期见!
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
交流
文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。
以上是 【JS】【自学总结】基于 node 写一个 todo 命令行工具 的全部内容, 来源链接: utcz.com/a/96412.html