通过一个简单的 node 命令行的 todo 项目学习了 nodejs 文件模块,收获颇多。
Github 源码
项目介绍:
- 功能
可以展示所有 todo 任务
可以新创建一个任务
可以编辑 todo 任务,修改任务名、完成状态
可以删除 todo 任务 - 命令
d 展示所有任务
d add xxx 添加新任务
d clear 清除所有任务 - 展示
引入 node fs模块
nodejs fs 中文文档
fs.readFile(path[, options], callback)
path
(被读取文件路径);
options
(文件以什么形式被读取,flag: a+
打开文件进行读取和追加,如果文件不存在,则创建该文件);
cllback
(回调函数,读取文件之后的操作,接受两个参数,Error
读取文件失败结果,data
读取成功结果)
fs.writeFile(file, data[, options], callback)
file
(被写入的文件路径);
data
(文件被写入的内容);
cllback
(回调函数,读取文件之后的操作,只接受一个参数,Error
写入文件失败结果)
- 读文件与写文件都是异步操作,所以需要返回一个 promise 对象,调用
read()
与write()
时需要使用await
和async
,如果读取或者写入文件失败,则 callback() 传入的 error 需要使用 reject(error) 返回。 - 导入文件时使用
require('xxx')
,导出时使用module.exports
// db.js
// 配置 .todo 文件到 home 目录
const homedir = require('os').homedir()
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 + '\n', (error) => {
if (error) {return reject(error)}
resolve()
})
})
}
}
module.exports = db
引入 commander.js 配置命令
commander.js 是完整的命令行解决方案;
安装并引入之后可以通过 .command()
来自定义命令;
需要通过独立的的可执行文件来实现命令,如index.js
,自定义命令的执行部分主要放在这里;
.action((arg)=>{})
调用可执行文件 index.js
中对应的函数;
.description()
用来描述命令干了什么。
// cli.js (add示例)
const program = require('commander');
const api = require('./index')
program
.command('add')
.description('add a task')
.action((...args) => {
const words = args.slice(0, -1).join(' ')
api.add(words)
.then(() => {console.log('添加成功')}, () => {console.log('添加失败')})
});
program.parse(process.argv);
// index.js (add示例)
const db = require('./db')
module.exports.add = async (title) => {
// 读取之前的任务
const list = await db.read()
// 添加一个新任务
list.push({title: title, done: false})
// 存储任务到文件
await db.write(list)
}
注意:添加和清空任务后都要将新的任务列表写入文件。
引入 inquirer.js
Inquirer.js 是一个命令行交互工具;
安装并引入后通过 inquirer.prompt({type, name, message, choices[fn]}).then((answer)=>{})
来配置命令行操作界面;
type
: prompt 的类型. 默认值:input
可选值:input, number, confirm, list, rawlist, expand, checkbox, password, editor
;
name
: 后续在 .then() 时使用来操作answer
的 key;
message
: 命令行提示信息,通常是一个询问;
choices
: 数组或返回一个选择数组的函数,如果定义为函数,则第一个参数将是当前会话的answer
;
answer
: 由一个键值对组成,key 是该 prompt 的 name 属性,value 取决于 prompt 的类型,在这里是每一项任务。
// index.js (askForAction示例)
const inquirer = require('inquirer')
module.exports.show = async () => {
// 读取之前的 list
const list = await db.read()
// 切换进行操作任务
printTasks(list)
}
function printTasks(list) {
inquirer
.prompt({
type: 'list',
name: 'index',
message: '请选择需要操作的任务',
choices: [...list.map((item, index) => {
return {
name: `${item.done ? '[√]' : '[x]'}${index + 1}:${item.title}`, value: index.toString()
}
}), {name: ' + 添加', value: '-1'}, {name: ' x 取消', value: '-2'}]
}).then((answer) => {
const index = parseInt(answer.index)
if (index >= 0) {
askForAction(list, index)
} else if (index === -1) {
askForCreatTask(list)
}
})
}
通过嵌套 inquirer 可实现各种层级的命令行交互界面;
此时即可通过 node cli
可执行查看任务列表,并进行后续的交互操作;
通过 node cli add
添加任务,node clear
清除任务列表;
但是这样每次必须要都输入 node。
Node.js 使用 Shebang
当对 cli.js 文件设置了正确的 Shebang 时,只需输入 ./cli.js
即可
设置方法:
- 命令行输入
chmod u+x cli.js
- cli.js 文件首行输入
#!/usr/bin/env node
env 主要用于在修改后的环境中运行命令。写 /usr/bin/env node
告诉 OS 运行 env,而 env 将运行 node,最后 node 将依次执行脚本。
命令行工具配置
在 package.json
中添加 bin 属性,指定命令的名称
"bin": {
"d": "cli.js"
},
发布至 npm
- 在
package.json
添加 files 属性,确定要上传包的文件,上传所有 .js 文件
"files": [
"*.js"
],
- 保证 name 不会与现有的 npm 包重名;
- 输入
npm login
登录账户,注意使用nrm use npm
切换为 npm 源然后登录; - 登录之后使用
npm publish
发布。