CLI 與GUI介紹
命令行界面(英語:Command Line Interface,縮寫:CLI)用戶通過鍵盤輸入指令,計算機接收到指令后,予以執(zhí)行笔喉。
圖形用戶界面(英語:Graphical User Interface,縮寫:GUI)是指采用圖形方式顯示的計算機操作用戶界面疯兼。與早期計算機使用的命令行界面相比然遏,除了降低用戶的操作負擔(dān)之外贫途,對于新用戶而言吧彪,圖形界面對于用戶來說在視覺上更易于接受。
CLI 程序中的一些概念
命令
通常我們執(zhí)行的 CLI 程序本身就是一個命令(主命令)丢早,當(dāng) CLI 程序功能分類比較多的時候姨裸,可以根據(jù)子功能的不同提供更多的一些子命令秧倾,如:
// create 子命令
vue create <project-name>
// add 子命令
vue add <plugin-name>
參數(shù)
參數(shù)是配合著命令調(diào)用傳入的數(shù)據(jù),類似函數(shù)參數(shù)傀缩,如下 <project-name> app1就是參數(shù):
vue create app1
選項
選項是命令內(nèi)置好的一些選項那先,以供調(diào)用命令的時候根據(jù)不同的需要進項選配:
// -f 或 --force 選項(當(dāng)app1已存在的時候,-f 強制重新創(chuàng)建并覆蓋)
vue create app1 -f
bin 文件
通常赡艰,node.js 文件需要使用 node 命令來運行售淡,如:
node test.js
我們可以使用如下的方式來簡化腳本運行
#!/usr/bin/env node
// #! 行必須寫在文件第一行,指定該腳本解析器路徑
// /usr/bin/env => env: 獲取環(huán)境變量信息
// /usr/bin/env node => env | grep PATH => 從電腦的環(huán)境變量 PATH 中查找 node 并執(zhí)行
console.log('hello');
現(xiàn)在可以命令行中省略 node 執(zhí)行文件了
./test.js
命令行參數(shù)的獲取
使用 Node.js 內(nèi)置 process
對象的 argv
屬性來獲取這些數(shù)據(jù):
//app.js文件
#!/usr/bin/env node
// process : 獲取到當(dāng)前程序運行的進程相關(guān)的一些信息和數(shù)據(jù)
// process.argv : 當(dāng)前程序運行的參數(shù)信息
// output
// ['node的路徑','當(dāng)前執(zhí)行文件的路徑','參數(shù)慷垮、選項'...]
console.log( process.argv );
//命令行
$ ./app.js -v
[
'C:\\Program Files\\nodejs\\node.exe',
'C:\\Users\\Desktop\\test\\app.js',
'-v'
]
commander 庫
該庫對 process.argv 進行解析揖闸,并提供了更易用的 API
npm install commander
Commander類
通過實例化 Commander 類對象來完成 CLI 程序構(gòu)建。
const { Command } = require('commander');
const program = new Command();
或直接調(diào)用內(nèi)置構(gòu)建好的一個 實例對象:
const { program } = require('commander');
選項
通過 option
方法指定要解析的選項:
program.option('-v, --version', '這是參數(shù)的描述');
// 設(shè)置選項參數(shù)
program.option('-p, --port', '端口', 80);
可選
<>
表示必填料身。
program.option('-p, --port <port>', '端口', 80);
必填
[]
表示可選
program.option('-p, --port [port]', '端口', 80);
命令參數(shù)
program.argument('<username>', '登錄用戶名', '默認值');
處理函數(shù)
當(dāng)命令解析后的執(zhí)行函數(shù)
program.action((參數(shù)1汤纸,參數(shù)2, 選項列表, program) => {
//...
});
解析
program.parse(process.argv)
使用示例
const { Command } = require('commander');
const program = new Command();
program.option('-v, --version', '這是參數(shù)的描述');
program.option('-p,--port [port]', '端口', '8888');
console.log( process.argv );
// 執(zhí)行動作 opts里包含傳入的參數(shù)
program.action((opts) => {
console.log(`輸入的參數(shù)`, opts)
if (opts.version) {
console.log(`version: 1.0.0`)
}
if (opts.port) {
console.log(`端口: ${opts.port}`)
}
});
program.parse(process.argv)
命令行字體美化
chalk 庫
//安裝
npm i chalk
//使用
const chalk = require('chalk');
console.log(chalk.blue('Hello world!'));
交互式命令行
有時候,需要 CLI 程序能夠與用戶進行一些交互芹血,比如提供給用戶選項或者輸入些文本贮泞。
//安裝
npm install inquirer
使用示例
const inquirer = require('inquirer');
const promptOptions = [];
promptOptions.push({
type: "input",
name: "serverName",
message: "請輸入應(yīng)用名稱",
default: "app",
});
promptOptions.push({
type: "checkbox",
name: "middlewares",
message: "請選擇要安裝的中間件",
choices: ['koa-static-cache', 'koa-router', 'koa-body'],
default: ['koa-static-cache', 'koa-router'],
});
inquirer.prompt(promptOptions).then(answer => {
console.log('answer', answer)
// answer {
// serverName: ' app2',
// middlewares: [ 'koa-static-cache', 'koa-router', 'koa-body' ]
// }
})
如何執(zhí)行shell
通過指令獲取參數(shù)和創(chuàng)建對應(yīng)的文件夾后,需要執(zhí)行npm init 和等npm i 指令
在這之前需要先了解下node如何實現(xiàn)多進程通信幔烛。因為我們需要在新創(chuàng)建的文件夾內(nèi)執(zhí)行命令啃擦。而在當(dāng)前cli的路徑內(nèi)打印出執(zhí)行消息。
Node.js 內(nèi)置 process_child(子進程)
process_child 提供了幾種方式來新建子進程
新建子進程的方式
spawn :
子進程中執(zhí)行的是非node程序说贝,提供一組參數(shù)后议惰,執(zhí)行的結(jié)果以流(Stream)的形式返回。
exec:
子進程執(zhí)行的是非node程序乡恕,傳入一串shell命令言询,執(zhí)行后結(jié)果以回調(diào)的形式(Buffer)返回,與execFile不同的是exec可以直接執(zhí)行一串shell命令傲宜。
execFile:
子進程中執(zhí)行的是非node程序运杭,提供一組參數(shù)后,執(zhí)行的結(jié)果以回調(diào)的形式返回函卒。
fork:
子進程執(zhí)行的是node程序辆憔,提供一組參數(shù)后,執(zhí)行的結(jié)果以流的形式返回报嵌,與spawn不同虱咧,fork生成的子進程只能執(zhí)行node應(yīng)用。
子進程的和父進程的通信有三類信息stdin锚国、stdout腕巡、stderr,通過來設(shè)置血筑,
介紹地址:https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio
stdio 設(shè)置
pipe:
相當(dāng)于['pipe', 'pipe', 'pipe']绘沉,子進程的stdio和父進程的stdio通過管道進行連接煎楣。
ignore:
相當(dāng)于['ignore','ignore', 'ignore'],子進程的stdio綁定到/dev/null,丟棄數(shù)據(jù)的輸入輸出车伞。
inherit:
繼承父進程相關(guān)的stdio,等同于[process.stdin,process.stdout,process.sterr]或者[0,1,2],此時子進程的stdio都是綁定在同一個地方择懂。
使用示例
const spawn = require('child_process').spawn;
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
//或者
const spawn = require('child_process').spawn;
// 在node里執(zhí)行shell命令
spawn('ls', ['-lh', '/usr'],{
stdio: [ 'inherit', 'inherit', 'inherit' ]
});
一個執(zhí)行shell的便捷庫
//安裝
npm install execa
// 使用 同步模式執(zhí)行指定命令。
const cmd = `npm init -y`;
execa.commandSync(cmd, {
//指令執(zhí)行路徑
cwd: options.rootDirectory,
//通信方式設(shè)置
stdio: ["inherit", "inherit", "inherit"],
});
其它一些工具庫
package name 驗證庫
npm: validate-npm-package-name
增強版的 fs 模塊
打開瀏覽器
package.json 中的 bin 字段
安裝依賴時另玖,如果包的 package.json
文件有 bin 字段困曙,就會在 node_modules
文件夾下面的 .bin
目錄中復(fù)制了 bin 字段鏈接的執(zhí)行文件。我們在調(diào)用執(zhí)行文件時谦去,可以不帶路徑赂弓,直接使用命令名來執(zhí)行相對應(yīng)的執(zhí)行文件。
{
"bin": "./xxx.js"
}
scripts: {
start: './node_modules/bin/xxx.js build'
}
// 簡寫為
scripts: {
start: 'xxx build'
}
完整的使用例子
地址:https://www.npmjs.com/package/le-koa-server
//安裝
npm i le-koa-server -g
//執(zhí)行命令
le-koa-server
le-koa-server.js
#!/usr/bin/env node
const { Command } = require('commander');
const packageJson = require('./package.json')
const fs = require('fs')
const chalk = require('chalk');
const validateNpmProjectName = require('validate-npm-package-name')
const inquirer = require('inquirer');
const execa = require('execa')
const open = require("open")
const program = new Command();
program.version(packageJson.version);
// 設(shè)置選項信息
program.option('-p,--port [port]', '端口');
// 設(shè)置參數(shù)
// le-koa-server app -p 9999
program.argument('[server-name]', 'server 的名稱哪轿,英文盈魁、數(shù)字、_組成');
// 執(zhí)行動作窃诉,參數(shù)是一一對應(yīng)的杨耙。選項會集中解析到對象,放在最后一項飘痛。(參數(shù)1珊膜,參數(shù)2,...選項)
program.action(async (webServerName, opts) => {
const promptOptions = [];
promptOptions.push({
type: "input",
name: "serverName",
message: "請輸入應(yīng)用名稱",
default: "app",
});
promptOptions.push({
type: "input",
name: "serverPort",
message: "請輸入應(yīng)用端口",
default: 8888,
});
promptOptions.push({
type: "checkbox",
name: "middlewares",
message: "請選擇要安裝的中間件",
choices: ['koa-static-cache', 'koa-router', 'koa-body'],
default: ['koa-static-cache', 'koa-router'],
});
//第二個參數(shù)說明:如果用戶已經(jīng)通過指令輸入了值宣脉,不必再詢問用戶车柠。
const answer = await inquirer.prompt(promptOptions, {
serverName: webServerName,
serverPort: opts.port,
});
// 整理用戶輸入和選擇的信息嗅回。process.cwd()當(dāng)前用戶執(zhí)行指令的路徑
const options = {
serverName: answer.serverName,
serverPort: answer.serverPort,
rootDirectory: process.cwd() + `/${answer.serverName}`,
dependencies: ['nodemon', 'koa', ...answer.middlewares]
}
//校驗名稱是否合法
if (validateNpmProjectName(options.serverName).errors?.length) {
console.error(chalk.red(`無效的項目名稱:${options.serverName}`))
process.exit(1);
}
//創(chuàng)建文件夾
try {
fs.mkdirSync(options.serverName);
} catch (e) {
console.error(chalk.red.bgWhite(`${options.serverName} 已經(jīng)存在了`))
process.exit(1);
}
// 初始化package.json
const cmd = `npm init -y`;
execa.commandSync(cmd, {
cwd: options.rootDirectory,
stdio: ["ignore", "ignore", "ignore"],
});
// 安裝依賴
const dependeniesCmd = `npm install ${options.dependencies.join(' ')}`
execa.commandSync(dependeniesCmd, {
cwd: options.rootDirectory,
stdio: ["inherit", "inherit", "inherit"],
});
// 生成入口文件
const log = `"服務(wù)啟動成功:http://localhost:${options.serverPort}"`
const content = `
const Koa = require('koa');
const app = new Koa();
app.use((ctx, next) => {
ctx.body = 'Hello';
});
app.listen(${options.serverPort}, () => {
console.log(${log});
});
`;
const entryFile = options.rootDirectory + "/app.js";
fs.writeFileSync(entryFile, content, {
encoding: "utf-8",
});
// 打開瀏覽器
await open(`http://localhost:${options.serverPort}`);
// 啟動應(yīng)用
const runCmd = `nodemon app.js --port=${options.serverPort}`;
execa.commandSync(runCmd, {
cwd: options.rootDirectory,
stdio: ["inherit", "inherit", "inherit"],
});
});
// 開始解析
program.parse();
package.json
{
"name": "le-koa-server",
"version": "1.0.0",
"description": "",
"main": "app.js",
"dependencies": {
"chalk": "^4.1.2",
"commander": "^8.2.0",
"execa": "^5.1.1",
"inquirer": "^8.1.5",
"open": "^8.2.1",
"validate-npm-package-name": "^3.0.0"
},
"devDependencies": {},
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"bin": "./le-koa-server.js",
"keywords": [],
"author": "",
"license": "ISC"
}
在 NPM 上發(fā)布 package
我們可以把本地的 package 發(fā)布到 npm 倉庫讓其他人使用己沛,相關(guān)操作如下:
1、注冊 npm 賬戶
注冊:https://www.npmjs.com/signup 攀细。
如果要發(fā)布npm包羊苟,需要驗證郵箱也完成塑陵。
2、登錄
使用 npm login 登錄授權(quán)
npm login
// 后續(xù)會提示輸入用戶名和密碼
3蜡励、發(fā)布
使用 npm publish 命令發(fā)布
npm publish
也可以登錄 npm 的 web 端令花,對已發(fā)布的應(yīng)用進行管理
注意
發(fā)布到 npm 倉庫上的 package,必須包含 package.json 文件凉倚,且內(nèi)容格式必須滿足特定要求:
https://docs.npmjs.com/cli/v7/configuring-npm/package-json
package 的名字除了滿足格式要求外兼都,要發(fā)布到 npm 倉庫中的 package 名稱不能重復(fù),可以使用 scope 來進行命名
4稽寒、更新
npm update
5扮碧、刪除
npm unpublish