# 二.npm-run-all

前言

用来运行多个命令

  • 串行(严格按照执行顺序执行)
  • 并行(不相互阻塞的命令)

# 1. 背景

通常来说,前端项目会包含 js、css、less、scss、json、markdown 等格式的文件,为保障代码质量,给不同的代码添加检查是很有必要的,代码检查不仅保障代码没有低级的语法错误,还可确保代码都遵守社区的最佳实践和一致的编码风格,在团队协作中尤其有用。

需要注意的是,html 代码也应该检查,但是工具支持薄弱,就略过不表。此外,为代码添加必要的单元测试也是质量保障的重要手段,常用的单测技术栈是:

提示

测试工具如 tap (opens new window)ava (opens new window) 也都提供了命令行接口,能很好的集成到 npm script 中,原理是相通的。

包含了基本的代码检查、单元测试命令的 package.json 如下:

// package.json
{
  "name": "demo",
  "version": "0.1.0",
  "main": "index.js",
  "scripts": {
    "lint:js": "eslint *.js",
    "lint:css": "stylelint *.less",
    "lint:json": "jsonlint --quiet *.json",
    "lint:markdown": "markdownlint --config .markdownlint.json *.md",
    "test": "mocha tests/"
  },
  "devDependencies": {
    "chai": "^4.1.2",
    "eslint": "^4.11.0",
    "jsonlint": "^1.6.2",
    "markdownlint-cli": "^0.5.0",
    "mocha": "^4.0.1",
    "stylelint": "^8.2.0",
    "stylelint-config-standard": "^17.0.0"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2. 串行

&& 可以把多条 npm script 按先后顺序串起来使用

// package.json
{
  "name": "demo",
  "version": "0.1.0",
  "main": "index.js",
  "scripts": {
    "test": "npm run lint:js && npm run lint:css && npm run lint:json && npm run lint:markdown && mocha tests"
  }
}
1
2
3
4
5
6
7
8
9

然后直接执行 npm testnpm t,从输出可以看到子命令的执行顺序是严格按照我们在 scripts 中声明的先后顺序来的:

eslint ==> stylelint ==> jsonlint ==> markdownlint ==> mocha

需要注意的是,串行执行的时候如果前序命令失败(通常进程退出码非 0),后续全部命令都会终止

# 3. 并行

把连接多条命令的 && 符号替换成 & 即可

// package.json
{
  "name": "demo",
  "version": "0.1.0",
  "main": "index.js",
  "scripts": {
    "test": "npm run lint:js & npm run lint:css & npm run lint:json & npm run lint:markdown & mocha tests"
  }
}
1
2
3
4
5
6
7
8
9

重新运行 npm t,我们得到如下结果:

细心的同学可能已经发现上图中哪里不对,npm run lint:js 的结果在进程退出之后才输出,如果你自己运行,不一定能稳定复现这个问题,但 npm 内置支持的多条命令并行跟 js 里面同时发起多个异步请求非常类似,它只负责触发多条命令,而不管结果的收集,如果并行的命令执行时间差异非常大,上面的问题就会稳定复现。怎么解决这个问题呢?

答案也很简单,在命令的增加 & wait 即可,这样我们的 test 命令长这样:

npm run lint:js & npm run lint:css & npm run lint:json & npm run lint:markdown & mocha tests/ & wait
1

加上 wait 的额外好处是,如果我们在任何子命令中启动了长时间运行的进程,比如启用了 mocha 的 --watch 配置,可以使用 ctrl + c 来结束进程,如果没加的话,你就没办法直接结束启动到后台的进程。

# 4. npm-run-all

使用 npm-run-all 实现更轻量和简洁的多命令运行。

用如下命令将 npm-run-all 添加到项目依赖中:

npm i npm-run-all -D
1

--parallel: 并行运行多个命令,例如:npm-run-all --parallel lint build

--serial: 多个命令按排列顺序执行,例如:npm-run-all --serial clean lint build:**

--continue-on-error: 是否忽略错误,添加此参数 npm-run-all 会自动退出出错的命令,继续运行正常的

--race: 添加此参数之后,只要有一个命令运行出错,那么 npm-run-all 就会结束掉全部的命令

这个包提供三个命令,分别是 npm-run-all run-s run-p,其中后两个都是 npm-run-all 带参数的简写,分别对应串行和并行

然后修改 package.json 实现多命令的串行执行:

// package.json
{
  "name": "demo",
  "version": "0.1.0",
  "main": "index.js",
  "scripts": {
    // "test": "npm run lint:js & npm run lint:css & npm run lint:json & npm run lint:markdown & mocha tests/ & wait",
    "mocha": "mocha tests/",
    "test": "npm-run-all lint:js lint:css lint:json lint:markdown mocha"
  }
}
1
2
3
4
5
6
7
8
9
10
11

npm-run-all 还支持通配符匹配分组的 npm script,上面的脚本可以进一步简化成:

// package.json
{
  "name": "demo",
  "version": "0.1.0",
  "main": "index.js",
  "scripts": {
    // "test": "npm-run-all lint:js lint:css lint:json lint:markdown mocha",
    "test": "npm-run-all lint:* mocha"
  }
}
1
2
3
4
5
6
7
8
9
10

如何让多个 npm script 并行执行?也很简单:

// package.json
{
  "name": "demo",
  "version": "0.1.0",
  "main": "index.js",
  "scripts": {
    // "test": "npm-run-all lint:js lint:css lint:json lint:markdown mocha",
    "test": "npm-run-all --parallel lint:* mocha"
  }
}
1
2
3
4
5
6
7
8
9
10

并行执行的时候,我们并不需要在后面增加 & wait,因为 npm-run-all 已经帮我们做了。