从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 nodeconsole.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.jsconsole.log(1)setTimeout(() => { console.log(2)}, 4000)console.log(3)
// ./demo2/bin/ope2.jsconsole.log(4)
执行后果:
1324
多命令并行
具备平台差别
Unix
:&
能够连贯多个命令,使之并行执行。Windows
:&
多命令仍旧串行。- 跨平台:借助
npm-run-all
第三方NPM包
串行示例在Mac
输入后果:
1342
条件执行
在多命令编排的流程中,可能在某些条件下须要完结流程。
立刻完结process.exit(1)
// demo2/bin/op_error.jsconsole.log(1)process.exit(1)setTimeout(() => { console.log(2)}, 4000)console.log(3)// demo2/bin/ope2.jsconsole.log(4)
执行命令"d2:error": "npm run d2:err && npm run d2:o2"
,输入后果:
1Error
其中process.exit(1)
后续的代码及工作都不再执行。
以后过程执行完完结process.exitCode = 1
// demo2/bin/op_error.jsconsole.log(1)process.exitCode = 1setTimeout(() => { console.log(2)}, 4000)console.log(3)
革新op_error.js
,执行npm run d2:error
,输入后果:
132Error
其中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 -fnpm 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
对应的后果进行输入。