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
對應的結果進行輸出。