从 npm run-script
利用开始
查看某些 NPM 包的 npm_package_scripts
,常常能够看到一下run-script
示例:
...
"scripts": {
"prerelease": "npm test && npm run integration",
"release": "env-cmd lerna version",
"postversion": "lerna publish from-git",
"fix": "npm run lint -- --fix",
"lint": "eslint . -c .eslintrc.yaml --no-eslintrc --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint",
"integration": "jest --config jest.integration.js --maxWorkers=2",
"pretest": "npm run lint",
"test": "jest"
},
...
对其中一一解说:
自定义npm run-script
在 NPM
敌对型环境 (npm init -y
) 下,能够将 node index.js
定义在 npm_package_scripts_*
中作为别名间接执行。
{
"name": "cli",
"version": "1.0.0",
"description": "","main":"index.js","scripts": {"d1":"node ./demo1/bin/operation.js"},"keywords": [],"author":"",
"license": "ISC"
}
在命令行中输出 npm run d1
就是执行node ./demo1/bin/operation.js
npm_package
变量
npm run-script
自定义的命令,能够将 package.json
其它配置项当变量应用
{
"name": "cli",
"version": "1.0.0",
"description": "","main":"index.js","scripts": {"d1":"node ./demo1/bin/operation.js","d1:var":"%npm_package_scripts_d1%",},
"keywords": [],
"author": "","license":"ISC"
}
在日常利用中,能够用 config
字段定义常量:
{
"name": "cli",
"version": "1.0.0",
"description": "","main":"index.js","config": {"port": 8081},
"scripts": {
"d1": "node ./demo1/bin/operation.js",
"d1:var": "%npm_package_scripts_d1%",
"test": "echo %npm_package_config_port%"
},
"keywords": [],
"author": "","license":"ISC"
}
平台差别:
- Linux/Mac:
$npm_package_*
- Windows:
$npm_package_*
- 跨平台:
cross_var
第三方 NPM 包
shebang
仅在 Unix
零碎中可用,在首行指定#!usr/bin/env node
,执行文件时,会在该用户的执行门路下运行指定的执行环境
能够通过 type env
确认环境变量门路。
#!/usr/bin/env node
console.log('-------------')
能够间接以文件名执行上述文件,而不须要 node index.js
去执行
E:\demos\node\cli> ./index.js
--------
process.env
环境变量
具备 平台差别
Unix: run-cli
mode=development npm run build
即可在逻辑代码中可取得process.env.mode === "develop"
Windows: run-cli
不容许该形式定义环境变量
- 跨平台
借助 cross-env
定义环境变量
多命令串行
示例如下:
{
"name": "cli",
"version": "1.0.0",
"description": "","main":"index.js","scripts": {"d2:o1":"node ./demo2/bin/ope1.js","d2:o2":"node ./demo2/bin/ope2.js","d2:err":"node ./demo2/bin/op_error.js","d2":"npm run d2:o1 && npm run d2:o2"},"keywords": [],"author":"",
"license": "ISC"
}
&&
能够连贯多个命令,使之串行执行。
若前一命令中有异步办法,会等异步执行完结,过程齐全完结后,才会执行后继命令。
// ./demo2/bin/ope1.js
console.log(1)
setTimeout(() => {console.log(2)
}, 4000)
console.log(3)
// ./demo2/bin/ope2.js
console.log(4)
执行后果:
1
3
2
4
多命令并行
具备 平台差别
Unix
:&
能够连贯多个命令,使之并行执行。Windows
:&
多命令仍旧串行。- 跨平台:借助
npm-run-all
第三方 NPM 包
串行示例在 Mac
输入后果:
1
3
4
2
条件执行
在多命令编排的流程中,可能在某些条件下须要完结流程。
立刻完结process.exit(1)
// demo2/bin/op_error.js
console.log(1)
process.exit(1)
setTimeout(() => {console.log(2)
}, 4000)
console.log(3)
// demo2/bin/ope2.js
console.log(4)
执行命令"d2:error": "npm run d2:err && npm run d2:o2"
,输入后果:
1
Error
其中 process.exit(1)
后续的代码及工作都不再执行。
以后过程执行完完结process.exitCode = 1
// demo2/bin/op_error.js
console.log(1)
process.exitCode = 1
setTimeout(() => {console.log(2)
}, 4000)
console.log(3)
革新op_error.js
,执行npm run d2:error
,输入后果:
1
3
2
Error
其中 process.exitCode = 1
后续的代码仍继续执行,而后继工作不再执行。
npm run-script
传参
npm run-script
参数
自定义命令"d4": "node ./demo4/bin/operation.js"
:
console.log(process.argv)
执行npm run d4 -f
,输入后果:
E:\demos\node\cli>npm run d4 -f
npm WARN using --force I sure hope you know what you are doing.
> cli@1.0.0 d4 E:\demos\node\cli
> node ./demo4/bin/operation.js
[
'D:\\nodejs\\node.exe',
'E:\\demos\\node\\cli\\demo4\\bin\\operation.js'
]
其中,-f
不被 bin/operation.js
承接,而是作为 npm run-script
的参数消化掉(即便 npm run-script
不辨认该参数)。
-
-s
- 静默执行
npm run-script
:疏忽日志输入
- 静默执行
-
-d
- 调试模式执行
npm run-script
:日志全 Level 输入
- 调试模式执行
界定 npm run-script
完结
执行npm run d4 -- -f
,输入后果:
E:\demos\node\cli>npm run d4 -- -f
> cli@1.0.0 d4 E:\demos\node\cli
> node ./demo4/bin/operation.js "-f"
[
'D:\\nodejs\\node.exe',
'E:\\demos\\node\\cli\\demo4\\bin\\operation.js',
'-f'
]
其中,-f
被 bin/operation.js
承接。
可见,在 npm run-script <command>
后应用 --
界定 npm
参数的完结,npm
会将 --
之后的所有参数间接传递给自定义的脚本。
NPM
钩子
npm_package_scripts_*
定义
{
"name": "cli",
"version": "1.0.0",
"description": "","main":"index.js","scripts": {"pred5":"node ./demo5/bin/pre.js","d5":"node ./demo5/bin/operation.js","postd5":"node ./demo5/bin/post.js"},"keywords": [],"author":"",
"license": "ISC"
}
执行 npm run d5
,在执行node ./demo5/bin/operation.js
之前会主动执行 "pred5": "node ./demo5/bin/pre.js"
,在执行node ./demo5/bin/operation.js
之后会主动执行"postd5": "node ./demo5/bin/post.js"
。
node_modules/.hooks/
定义
Unix
可用
- 创立
node_modules/.hooks/
目录 -
创立
pred5
文件console.log('---------pre--------')
- 批改文件权限为可执行
chmod 777 node_modules/.hooks/pred5
- 执行命令
npm run d5
即可
场景:
"postinstall": "husky install"
NPM 全局命令行调试
npm link
能够将本地包以软链的模式注册到全局 node_modules/bin
下,做伪全局调试。
基于 Node 模块的命令行
示例 1:process.stdin
& process.stdout
交互命令行
function cli () {process.stdout.write("Hello");
process.stdout.write("World");
process.stdout.write("!!!");
process.stdout.write('\n')
console.log("Hello");
console.log("World");
console.log("!!!");
process.on('exit', function () {console.log('----exit')
})
process.stdin.setEncoding('utf8')
process.stdin.on('data', (input) => {console.dir(input)
input = input.toString().trim()
if (['Y', 'y', 'YES', 'yes'].indexOf(input) > -1) {console.log('success')
}
if (['N', 'n', 'No', 'no'].indexOf(input) > -1) {console.log('reject')
}
})
process.stdout.write('......\n')
console.log('----------------00000000000------------')
process.stdout.write('确认执行吗(y/n)?')
process.stdout.write('......\n')
}
cli()
stdin
- 规范输出监听控制台的输出
- 以回车标识完结
- 获取的输出蕴含回车字符
stdout
process.stdout vs. console.log
其中 console.log
输入底层调用的是 process.stdout
,在输入之前进行了解决,比方调用util.format
办法
区别 | process.stdout | console.log |
---|---|---|
参数 | 只能接管字符串做参数 | 反对 ECMA 的所有数据类型 |
参数个数 | 仅一个字符串 | 能够接管多个 |
换行 | 行内间断输入 | 主动追加换行 |
格式化 | 不反对 | 反对 ’%s’、’%c’ 格式化 |
输入本身 | WriteStream 对象 | 字符串 |
示例 2:process.stdin
工作模式
process.stdin.setEncoding('utf8');
function readlineSync() {return new Promise((resolve, reject) => {console.log(`--status----${process.stdin.readableFlowing}`);
process.stdin.resume();
process.stdin.on('data', function (data) {console.log(`--status----${process.stdin.readableFlowing}`);
process.stdin.pause(); // stops after one line reads // 暂停 input 流,容许稍后在必要时复原它。console.log(`--status----${process.stdin.readableFlowing}`);
resolve(data);
});
});
}
async function main() {let input = await readlineSync();
console.log('inputLine1 =', input);
console.log('bye');
}
main();
若 n 次调用 readlineSync()
,会为data
事件监听屡次绑上处理函数,回调函数会执行 n 次。
stdin
规范输出是可读流的实例
工作模式
合乎可读流的工作模式:
-
流动模式(flowing)
在流动模式中,数据主动从底层零碎读取,并通过
EventEmitte
接口的事件尽可能快地被提供给应用程序 -
暂停模式(paused)
在暂停模式中,必须显式调用
stream.read()
读取数据块
工作状态
null
false
true
可通过readable.readableFlowing
查看相应的工作模式
状态切换
- 增加
'data'
事件句柄。 - 调用
stream.resume()
办法。 - 调用
stream.pipe()
办法将数据发送到可写流。
过程完结
- 如果事件循环中没有待处理的额定工作,则
Node.js
过程会自行退出。 - 调用
process.exit()
会强制过程尽快退出,即便还有尚未齐全实现的异步操作在期待,包含对process.stdout
和process.stderr
的 I/O 操作。
示例 3:readline
模块
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: '请输出 >'
});
rl.prompt();
rl.on('line', (line) => {console.dir(line)
switch (line.trim()) {
case 'hello':
console.log('world!');
break;
default:
console.log(` 你输出的是:'${line.trim()}'`);
break;
}
rl.prompt();}).on('close', () => {console.log('再见!');
process.exit(0);
});
readline 模块
创立 UI 界面
const rl = readline.createInterface({
input: process.stdin, // 定义输出 UI
output: process.stdout, // 定义输入 UI
historySize: 0, // 禁止历史滚动 —— 默认:30
removeHistoryDuplicates: true, // 输出历史去重 —— 默认:false
completer: function (line) { // 制表符主动填充匹配文本
const completions = '.help .error .exit .quit .q'.split(' ');
const hits = completions.filter((c) => c.startsWith(line));
return [hits.length ? hits : completions, line]; // 输入数组:0 —— 匹配后果;1 —— 输出
},
prompt: '请输出 >' // 命令行前缀
});
办法
rl.prompt()
以前缀开启新的输出行
rl.close()
敞开 readline.Interface
实例,并放弃对 input
和output
流的管制
事件
line
事件
rl.on('line', (line) => {// 绝对比 process.stdin.on('data', function (chunk) {}),输出 line 不蕴含换行符
switch (line.trim()) {
case 'hello':
console.log('world!');
break;
default:
console.log(` 你输出的是:'${line.trim()}'`);
break;
}
rl.prompt();});
inquirer 源码解析
外围:
-
命令行 UI
- readline.createInterface
-
渲染输入
- rl.output.write
-
事件监听
- rxjs
加强交互体验:
- mute-stream:管制输入流输入
- chalk:多彩日志打印
- figures:命令行小图标
- cli-cursor:光标的暗藏、显示管制
上面以 type=”list” 为例进行阐明
创立命令行
this.rl = readline.createInterface({
terminal: true,
input: process.stdin,
output: process.stdout
})
渲染输入
var obs = from(questions)
this.process = obs.pipe(concatMap(this.processQuestion.bind(this)),
publish())
将传入的参数转换为数据流模式,对其中的每一项数据进行渲染processQuestion
render(error) {var message = this.getQuestion();
if (this.firstRender) {message += chalk.dim('(Use arrow keys)');
}
if (this.status === 'answered') {message += chalk.cyan(this.opt.choices.getChoice(this.selected).short);
} else {var choicesStr = listRender(this.opt.choices, this.selected);
var indexPosition = this.opt.choices.indexOf(this.opt.choices.getChoice(this.selected)
);
message +=
'\n' + choicesStr;
}
this.firstRender = false;
this.rl.output.unmute();
this.rl.output.write(message);
this.rl.output.mute();}
其中:
- 借助
chalk
进行输入的色调多样化; listRender
将每一个choice
拼接为字符串;- 应用
this.selected
标识以后选中项,默认为 0; - 应用
this.rl.output.write
将字符串输入; - 借助
mute-stream
管制命令行有效输入;
事件监听
function observe(rl) {var keypress = fromEvent(rl.input, 'keypress', normalizeKeypressEvents)
.pipe(takeUntil(fromEvent(rl, 'close')))
// Ignore `enter` key. On the readline, we only care about the `line` event.
.pipe(filter(({ key}) => key !== 'enter' && key.name !== 'return'));
return {line: fromEvent(rl, 'line'),
keypress: keypress,
normalizedUpKey: keypress.pipe(
filter(({ key}) =>
key.name === 'up' || key.name === 'k' || (key.name === 'p' && key.ctrl)
),
share()),
normalizedDownKey: keypress.pipe(
filter(({ key}) =>
key.name === 'down' || key.name === 'j' || (key.name === 'n' && key.ctrl)
),
share()),
numberKey: keypress.pipe(filter((e) => e.value && '123456789'.indexOf(e.value) >= 0),
map((e) => Number(e.value)),
share()),
};
};
借助 Rx.fromEvent
监听命令行的 keypress
、line
事件。
var events = observe(this.rl);
events.normalizedUpKey
.pipe(takeUntil(events.line))
.forEach(this.onUpKey.bind(this));
events.normalizedDownKey
.pipe(takeUntil(events.line))
.forEach(this.onDownKey.bind(this));
events.line
.pipe(take(1)
)
.forEach(this.onSubmit.bind(this));
订阅事件,对相应的事件进行解决
onUpKey () {console.log('--------up')
this.selected = incrementListIndex(this.selected, 'up', this.opt);
this.render();}
onDownKey () {console.log('--------down')
this.selected = incrementListIndex(this.selected, 'down', this.opt);
this.render();}
onSubmit () {console.log('------------submit')
}
批改 this.selected
值,通过 this.render
进行命令行的界面更新。
监听 line
事件,将 this.selected
对应的后果进行输入。