npm run-script
应用开始查看某些NPM包的npm_package_scripts
,常常能够看到一下run-script
示例:html
... "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" }, ...
对其中一一讲解:node
npm run-script
在NPM
友好型环境(npm init -y
)下,能够将node index.js
定义在npm_package_scripts_*
中做为别名直接执行。git
{ "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
npm_package
变量npm run-script
自定义的命令,能够将package.json
其它配置项当变量使用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
字段定义常量:api
{ "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" }
平台差别:数组
$npm_package_*
$npm_package_*
cross_var
第三方NPM包仅在Unix
系统中可用,在首行指定#!usr/bin/env node
,执行文件时,会在该用户的执行路径下运行指定的执行环境异步
能够经过type env
确认环境变量路径。async
#!/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 link
能够将本地包以软链的形式注册到全局node_modules/bin
下,作伪全局调试。
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()
其中console.log
输出底层调用的是process.stdout
,在输出以前进行了处理,好比调用util.format
方法
区别 | process.stdout | console.log |
---|---|---|
参数 | 只能接收字符串作参数 | 支持ECMA的全部数据类型 |
参数个数 | 仅一个字符串 | 能够接收多个 |
换行 | 行内连续输出 | 自动追加换行 |
格式化 | 不支持 | 支持'%s'、'%c'格式化 |
输出自身 | WriteStream对象 | 字符串 |
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次。
标准输入是可读流的实例
符合可读流的工做模式:
流动模式(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 操做。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); });
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(); });
核心:
命令行UI
渲染输出
事件监听
加强交互体验:
下面以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
对应的结果进行输出。