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']

其中,-fbin/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可用

  1. 创立node_modules/.hooks/目录
  2. 创立pred5文件

    console.log('---------pre--------')
  3. 批改文件权限为可执行chmod 777 node_modules/.hooks/pred5
  4. 执行命令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.stdoutconsole.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.stdoutprocess.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实例,并放弃对inputoutput流的管制

事件

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监听命令行的keypressline事件。

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对应的后果进行输入。