通過一個簡單的 node 命令行的 todo 項目學習了 nodejs 文件模塊,收獲頗多。
項目介紹:
- 功能
可以展示所有 todo 任務
可以新創(chuàng)建一個任務
可以編輯 todo 任務,修改任務名、完成狀態(tài)
可以刪除 todo 任務 - 命令
d 展示所有任務
d add xxx 添加新任務
d clear 清除所有任務 - 展示
引入 node fs模塊
fs.readFile(path[, options], callback)
path(被讀取文件路徑);
options(文件以什么形式被讀取,flag: a+打開文件進行讀取和追加,如果文件不存在,則創(chuàng)建該文件);
cllback(回調函數(shù),讀取文件之后的操作,接受兩個參數(shù),Error讀取文件失敗結果,data讀取成功結果)
fs.writeFile(file, data[, options], callback)
file(被寫入的文件路徑);
data(文件被寫入的內(nèi)容);
cllback(回調函數(shù),讀取文件之后的操作,只接受一個參數(shù),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() 來自定義命令;
需要通過獨立的的可執(zhí)行文件來實現(xiàn)命令,如index.js,自定義命令的執(zhí)行部分主要放在這里;
.action((arg)=>{}) 調用可執(zhí)行文件 index.js 中對應的函數(shù);
.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: 后續(xù)在 .then() 時使用來操作answer的 key;
message: 命令行提示信息,通常是一個詢問;
choices: 數(shù)組或返回一個選擇數(shù)組的函數(shù),如果定義為函數(shù),則第一個參數(shù)將是當前會話的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 可實現(xiàn)各種層級的命令行交互界面;
此時即可通過 node cli 可執(zhí)行查看任務列表,并進行后續(xù)的交互操作;
通過 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 主要用于在修改后的環(huán)境中運行命令。寫 /usr/bin/env node 告訴 OS 運行 env,而 env 將運行 node,最后 node 將依次執(zhí)行腳本。
命令行工具配置
在 package.json 中添加 bin 屬性,指定命令的名稱
"bin": {
"d": "cli.js"
},
發(fā)布至 npm
- 在
package.json添加 files 屬性,確定要上傳包的文件,上傳所有 .js 文件
"files": [
"*.js"
],
- 保證 name 不會與現(xiàn)有的 npm 包重名;
- 輸入
npm login登錄賬戶,注意使用nrm use npm切換為 npm 源然后登錄; - 登錄之后使用
npm publish發(fā)布。


