如何并行运行多个npm脚本?

在我的package.json中,我有这两个脚本:

  "scripts": {"start-watch": "nodemon run-babel index.js","wp-server": "webpack-dev-server",}

每次我开始在Node.js.开发时,我都必须运行这2个脚本并行我首先想到的是添加第三个脚本,如下所示:

"dev": "npm run start-watch && npm run wp-server"

…但这将等待start-watch在运行wp-server之前完成。

我如何并行运行这些?请记住,我需要查看这些命令中的output。此外,如果您的解决方案涉及构建工具,我宁愿使用gulp而不是grunt,因为我已经在另一个项目中使用它。

609922 次浏览

使用名为同时的包。

npm i concurrently --save-dev

然后设置您的npm run dev任务如下:

"dev": "concurrently --kill-others \"npm run start-watch\" \"npm run wp-server\""

快速解决方案

在这种情况下我觉得最好的办法如果此脚本用于仅在基于*nix的机器上运行的私有模块,您可以使用控制运算符进行分叉过程,如下所示:&

在部分package.json文件中执行此操作的示例:

{"name": "npm-scripts-forking-example","scripts": {"bundle": "watchify -vd -p browserify-hmr index.js -o bundle.js","serve":  "http-server -c 1 -a localhost","serve-bundle": "npm run bundle & npm run serve &"}

然后,您将通过npm run serve-bundle并行执行它们。您可以增强脚本以将分叉进程的pid输出到一个文件,如下所示:

"serve-bundle": "npm run bundle & echo \"$!\" > build/bundle.pid && npm run serve & echo \"$!\" > build/serve.pid && npm run open-browser",

谷歌类似分叉的bash控制操作符的东西以了解有关它如何工作的更多信息。我还在下面的Node项目中提供了一些关于利用Unix技术的进一步背景:

更多背景RE: Unix工具和Node.js

如果您不在Windows上,Unix工具/技术通常可以很好地使用Node脚本实现某些功能,因为:

  1. 许多Node.js模仿Unix原则
  2. 您使用的是*nix(包括OS X),而NPM无论如何都在使用shell

Nodeland中用于系统任务的模块通常也是Unix工具的抽象或近似,从fsstreams

如果您使用的是类UNIX环境,只需使用&作为分隔符:

"dev": "npm run start-watch & npm run wp-server"

否则,如果您对跨平台解决方案感兴趣,您可以使用npm-run-all实现方式模块:

"dev": "npm-run-all --parallel start-watch wp-server"

我遇到了&|的问题,它们分别退出状态和错误抛出。

其他解决方案希望使用给定名称运行任何任务,例如npm-run-all,这不是我的用例。

所以我创建了项目名称npm-run,它异步运行npm脚本,并在完成后报告。

因此,对于您的脚本,它将是:

npm-run-parallel wp-server start-watch

从windows cmd中,您可以使用start

"dev": "start npm run start-watch && start npm run wp-server"

以这种方式启动的每个命令都在自己的窗口中开始。

你应该使用npm-run-all实现方式(或concurrentlyparallelshell),因为它对启动和终止命令有更多的控制。操作符&|是个坏主意,因为你需要在所有测试完成后手动停止它。

这是通过npm进行量角器测试的示例:

scripts: {"webdriver-start": "./node_modules/protractor/bin/webdriver-manager update && ./node_modules/protractor/bin/webdriver-manager start","protractor": "./node_modules/protractor/bin/protractor ./tests/protractor.conf.js","http-server": "./node_modules/http-server/bin/http-server -a localhost -p 8000","test": "npm-run-all -p -r webdriver-start http-server protractor"}

-p=并行运行命令。

-r=当其中一个命令以零的退出代码结束时杀死所有命令。

运行npm run test将启动Selenium驱动程序,启动超文本传输协议服务器(为您提供文件)并运行量角器测试。所有测试完成后,它将关闭超文本传输协议服务器和selenium驱动程序。

如果将双与号替换为单个与号,则脚本将并发运行。

我已经检查了上面几乎所有的解决方案,只有npm-run-all实现方式我能够解决所有问题。与所有其他解决方案相比,主要优势是能够运行带参数的脚本

{"test:static-server": "cross-env NODE_ENV=test node server/testsServer.js","test:jest": "cross-env NODE_ENV=test jest","test": "run-p test:static-server \"test:jest -- {*}\" --","test:coverage": "npm run test -- --coverage","test:watch": "npm run test -- --watchAll",}

注释run-pnpm-run-all --parallel的快捷方式

这允许我使用npm run test:watch -- Something等参数运行命令。

编辑:

对于npm-run-all,还有一个更有用的选项

 -r, --race   - - - - - - - Set the flag to kill all tasks when a taskfinished with zero. This option is valid onlywith 'parallel' option.

-r添加到npm-run-all脚本以在完成代码0时终止所有进程。当您运行HTTP服务器和另一个使用该服务器的脚本时,这尤其有用。

  "test": "run-p -r test:static-server \"test:jest -- {*}\" --",

我有一个跨平台解决方案,无需任何额外模块。我正在寻找类似于try catch块的东西,我可以在cmd.exe和bash中使用。

解决方案是command1 || command2,似乎在两个环境中都有效。所以OP的解决方案是:

"scripts": {"start-watch": "nodemon run-babel index.js","wp-server": "webpack-dev-server",// first command is for the cmd.exe, second one is for the bash"dev": "(start npm run start-watch && start npm run wp-server) || (npm run start-watch & npm run wp-server)","start": "npm run dev"}

那么简单的npm start(和npm run dev)将适用于所有平台!

npm-run-all --parallel task1 task2

编辑:

您需要事先安装npm-run-all实现方式。还要检查此页面以了解其他使用场景。

分叉怎么样

运行多个Node脚本的另一种选择是使用单个Node脚本,它可以分叉许多其他脚本。分叉在Node中原生支持,因此它不添加依赖项并且是跨平台的。


最小的例子

这只会按原样运行脚本,并假设它们位于父脚本的目录中。

// fork-minimal.js - run with: node fork-minimal.js
const childProcess = require('child_process');
let scripts = ['some-script.js', 'some-other-script.js'];scripts.forEach(script => childProcess.fork(script));

详细示例

这将运行带有参数的脚本,并由许多可用选项配置。

// fork-verbose.js - run with: node fork-verbose.js
const childProcess = require('child_process');
let scripts = [{path: 'some-script.js',args: ['-some_arg', '/some_other_arg'],options: {cwd: './', env: {NODE_ENV: 'development'}}},{path: 'some-other-script.js',args: ['-another_arg', '/yet_other_arg'],options: {cwd: '/some/where/else', env: {NODE_ENV: 'development'}}}];
let runningScripts= [];
scripts.forEach(script => {let runningScript = childProcess.fork(script.path, script.args, script.options);
// Optionally attach event listeners to the scriptrunningScript.on('close', () => console.log('Time to die...'))
runningScripts.push(runningScript); // Keep a reference to the script for later use});

使用分叉脚本进行通信

分叉还有一个额外的好处,即父脚本可以从分叉的子进程接收事件并发回。一个常见的例子是父脚本杀死其分叉的子进程。

 runningScripts.forEach(runningScript => runningScript.kill());

有关更多可用的事件和方法,请参阅#0留档

在我的例子中,我有两个项目,一个是UI,另一个是api,并且都在各自的package.json文件中拥有自己的脚本。

这就是我所做的。

npm run --prefix react start&  npm run --prefix express start&

我已经使用npm-run-all实现方式一段时间了,但我从来没有相处过,因为在监视模式下命令的输出不能很好地协同工作。例如,如果我在监视模式下启动create-react-appjest,我将只能看到我运行的最后一个命令的输出。所以大多数时候,我都是手动运行所有的命令…

这就是为什么,我实现了自己的lib,运行屏幕。它仍然是一个非常年轻的项目(从昨天:p),但它可能值得一看,在您的情况下,它将是:

run-screen "npm run start-watch" "npm run wp-server"

然后按数字键1查看wp-server的输出,按0查看start-watch的输出。

简单的节点脚本让你没有太多麻烦。使用readline组合输出,这样行就不会损坏。

const { spawn } = require('child_process');const readline = require('readline');
[spawn('npm', ['run', 'start-watch']),spawn('npm', ['run', 'wp-server'])].forEach(child => {readline.createInterface({input: child.stdout}).on('line', console.log);
readline.createInterface({input: child.stderr,}).on('line', console.log);});

我的解决方案类似于Piittis,尽管我在使用Windows时遇到了一些问题。所以我必须验证win32。

const { spawn } = require("child_process");
function logData(data) {console.info(`stdout: ${data}`);}
function runProcess(target) {let command = "npm";if (process.platform === "win32") {command = "npm.cmd"; // I shit you not}const myProcess = spawn(command, ["run", target]); // npm run server
myProcess.stdout.on("data", logData);myProcess.stderr.on("data", logData);}
(() => {runProcess("server"); // package json scriptrunProcess("client");})();
npm install npm-run-all --save-dev

package.json:

"scripts": {"start-watch": "...","wp-server": "...","dev": "npm-run-all --parallel start-watch wp-server"}

更多信息:https://github.com/mysticatea/npm-run-all/blob/master/docs/npm-run-all.md

在父文件夹的package.json中:

"dev": "(cd api && start npm run start) & (cd ../client && start npm run start)"

在windows中工作

只需将此npm脚本添加到根文件夹中的package.json文件中。

{..."scripts": {..."start": "react-scripts start", // or whatever else depends on your project"dev": "(cd server && npm run start) & (cd ../client && npm run start)"}}

这招对我管用

{"start-express": "tsc && nodemon dist/server/server.js","start-react": "react-scripts start","start-both": "npm -p -r run start-react && -p -r npm run start-express"}

客户端和服务器都是用打字稿编写的。

React应用程序是使用带有打字脚本模板的create-react-app创建的,位于默认的src目录中。

Express位于服务器目录中,条目文件server.js

打字脚本代码并转译成js并放在dist目录中。

查看我的项目以获取更多信息:https://github.com/nickjohngray/staticbackeditor

更新:调用npm run dev,开始运行

{"server": "tsc-watch --onSuccess \"node ./dist/server/index.js\"","start-server-dev": "npm run build-server-dev && node src/server/index.js","client": "webpack-dev-server --mode development --devtool inline-source-map --hot","dev": "concurrently \"npm run build-server-dev\"  \"npm run server\" \"npm run client\""}

…但在运行wp-server之前,这将等待start-watch完成。

要做到这一点,您必须在命令上使用start。其他人已经说明了但这就是它的工作方式,您的代码如下:

"dev": "npm run start-watch && npm run wp-server"

应该是的

"dev": " start npm run start-watch && start npm run wp-server"

这将做的是,它将为每个命令打开一个单独的实例并同时处理它们,就您的初始问题而言,这不应该是一个问题。我为何这样说呢?这是因为当您仅运行1条语句时,这些实例都会自动打开,这是您的初始目标。

使用npm运行多个并行脚本的分步指南。全局安装npm-run-all实现方式

npm i -g npm-run-all

现在在package.json存在的项目中安装并保存此包

npm i npm-run-all --save-dev

现在以这种方式修改package.json文件中的脚本

"scripts": {"server": "live-server index.html","watch": "node-sass scss/style.scss --watch","all": "npm-run-all --parallel server watch"},

现在运行这个命令

npm run all

在给定链接中有关此包的更多详细信息npm-run-all实现方式

您还可以使用prepost作为特定脚本的前缀。

  "scripts": {"predev": "nodemon run-babel index.js &","dev": "webpack-dev-server"}

然后运行:npm run dev

在Linux上只使用shell脚本。

"scripts": {"cmd": "{ trap 'trap \" \" TERM; kill 0; wait' INT TERM; } && blocking1 & blocking2 & wait"}

npm run cmd然后^C将杀死孩子并等待干净退出。

Windows CMD的简单和原生方式

"start /b npm run bg-task1 && start /b npm run bg-task2 && npm run main-task"

start /b表示从后台开始)

由于您可能需要向此脚本添加越来越多的内容,它将变得混乱且难以使用。如果您需要一些条件来检查、使用变量怎么办?所以我建议您查看允许使用js创建脚本的google/zx

简单用法:

  1. 安装zx:npm i -g zx
  2. 添加package.json命令(可选,您可以将所有内容移动到脚本中):
  "scripts": {"dev": "zx ./scripts/dev.mjs", // run script"build:dev": "tsc -w", // compile in watch mode"build": "tsc", // compile"start": "node dist/index.js", // run"start:dev": "nodemon dist/index.js", // run in watch mode},
  1. 创建dev.mjs脚本文件:
#!/usr/bin/env zx
await $`yarn build`; // prebuild if dist is emptyawait Promise.all([$`yarn start:dev`, $`yarn build:dev`]); // run in parallel

现在,每次您想启动开发服务器时,您只需运行yarn devnpm run dev

它将首先编译ts->js,然后在监视模式下并行运行typecrpt编译器和服务器。当你更改ts文件时->它将被tsc->nodeman重新编译,并重新启动服务器。


高级编程用法

加载env变量,在监视模式下编译ts并在更改时从dist重新运行服务器(dev.mjs):

#!/usr/bin/env zximport nodemon from "nodemon";import dotenv from "dotenv";import path from "path";import { fileURLToPath } from "url";
// load env variablesloadEnvVariables("../env/.env");
await Promise.all([// compile in watch mode (will recompile on changes in .ts files)$`tsc -w`,// wait for tsc to compile for first time and rerun server on any changes (tsc emited .js files)sleep(4000).then(() =>nodemon({script: "dist/index.js",})),]);
function sleep(ms) {return new Promise((resolve) => {setTimeout(resolve, ms);});}
function getDirname() {return path.dirname(fileURLToPath(import.meta.url));}
function loadEnvVariables(relativePath) {const { error, parsed } = dotenv.config({path: path.join(getDirname(), relativePath),});
if (error) {throw error;}
return parsed;}

一个好的老式Makefile怎么样?

这允许您进行大量控制,包括如何管理子shell、脚本之间的依赖关系等。

# run both scriptsstart: server client
# start server and use & to background itserver:npm run serve &
# start the clientclient:npm start

调用这个Makefile,然后你可以输入

make start来启动一切。因为当你ctrl-C时,服务器命令实际上是在start命令的子进程中运行的,所以服务器命令也会停止——这与你自己在shell中反向接地不同。Make还为您提供命令行完成功能,至少在我使用的shell上是这样。奖励-第一个命令将始终运行,因此您实际上可以在这里自己键入make

我总是在我的项目中放入一个makefile,这样我就可以在以后快速扫描每个项目的所有常见命令和参数,因为我在它们之间切换。

我认为最好的方法是使用npm-run-all实现方式如下:

1- npm install -g npm-run-all<---将全局安装
2- npm-run-all --parallel server client

安装npm install concurrently

"scripts": {"start:build": "tsc -w","start:run": "nodemon build/index.js","start": "concurrently  npm:start:*"},

使用同时与共享输出流并行运行命令。为了便于区分哪个输出来自哪个进程,请使用缩短的命令形式,例如npm:wp-server。这会导致同时在每个输出行前加上其命令名称。

package.json中,您的脚本部分将如下所示:

 "scripts": {"start": "concurrently \"npm:start-watch\" \"npm:wp-server\"","start-watch": "nodemon run-babel index.js","wp-server": "webpack-dev-server"}