Skip to content

Latest commit

 

History

History
174 lines (124 loc) · 5.84 KB

ipc.md

File metadata and controls

174 lines (124 loc) · 5.84 KB
execa logo

📞 Inter-process communication

Exchanging messages

When the ipc option is true, the current process and subprocess can exchange messages. This only works if the subprocess is a Node.js file.

The ipc option defaults to true when using execaNode() or the node option.

The current process sends messages with subprocess.sendMessage(message) and receives them with subprocess.getOneMessage().

The subprocess uses sendMessage(message) and getOneMessage(). Those are the same methods, but imported directly from the 'execa' module.

// parent.js
import {execaNode} from 'execa';

const subprocess = execaNode`child.js`;
await subprocess.sendMessage('Hello from parent');
const message = await subprocess.getOneMessage();
console.log(message); // 'Hello from child'
// child.js
import {getOneMessage, sendMessage} from 'execa';

const message = await getOneMessage(); // 'Hello from parent'
const newMessage = message.replace('parent', 'child'); // 'Hello from child'
await sendMessage(newMessage);

Listening to messages

The methods described above read a single message. On the other hand, subprocess.getEachMessage() and getEachMessage() return an async iterable. This should be preferred when listening to multiple messages.

subprocess.getEachMessage() waits for the subprocess to end (even when using break or return). It throws if the subprocess fails. This means you do not need to await the subprocess' promise.

// parent.js
import {execaNode} from 'execa';

const subprocess = execaNode`child.js`;
await subprocess.sendMessage(0);

// This loop ends when the subprocess exits.
// It throws if the subprocess fails.
for await (const message of subprocess.getEachMessage()) {
	console.log(message); // 1, 3, 5, 7, 9
	await subprocess.sendMessage(message + 1);
}
// child.js
import {sendMessage, getEachMessage} from 'execa';

// The subprocess exits when hitting `break`
for await (const message of getEachMessage()) {
	if (message === 10) {
		break;
	}

	console.log(message); // 0, 2, 4, 6, 8
	await sendMessage(message + 1);
}

Filter messages

import {getOneMessage} from 'execa';

const startMessage = await getOneMessage({
	filter: message => message.type === 'start',
});
import {getEachMessage} from 'execa';

for await (const message of getEachMessage()) {
	if (message.type === 'start') {
		// ...
	}
}

Retrieve all messages

The result.ipcOutput array contains all the messages sent by the subprocess. In many situations, this is simpler than using subprocess.getOneMessage() and subprocess.getEachMessage().

// main.js
import {execaNode} from 'execa';

const {ipcOutput} = await execaNode`build.js`;
console.log(ipcOutput[0]); // {kind: 'start', timestamp: date}
console.log(ipcOutput[1]); // {kind: 'stop', timestamp: date}
// build.js
import {sendMessage} from 'execa';

await sendMessage({kind: 'start', timestamp: new Date()});
await runBuild();
await sendMessage({kind: 'stop', timestamp: new Date()});

Send an initial message

The ipcInput option sends a message to the Node.js subprocess when it starts.

// main.js
import {execaNode} from 'execa';

const ipcInput = [
	{task: 'lint', ignore: /test\.js/},
	{task: 'copy', files: new Set(['main.js', 'index.js']),
}];
await execaNode({ipcInput})`build.js`;
// build.js
import {getOneMessage} from 'execa';

const ipcInput = await getOneMessage();

Message type

By default, messages are serialized using structuredClone(). This supports most types including objects, arrays, Error, Date, RegExp, Map, Set, bigint, Uint8Array, and circular references. This throws when passing functions, symbols or promises (including inside an object or array).

To limit messages to JSON instead, the serialization option can be set to 'json'.

import {execaNode} from 'execa';

const subprocess = execaNode({serialization: 'json'})`child.js`;

Messages order

The messages are always received in the same order they were sent. Even when sent all at once.

import {sendMessage} from 'execa';

await Promise.all([
	sendMessage('first'),
	sendMessage('second'),
	sendMessage('third'),
]);

Debugging

When the verbose option is 'full', the IPC messages sent by the subprocess to the current process are printed on the console.

Also, when the subprocess failed, error.ipcOutput contains all the messages sent by the subprocess. Those are also shown at the end of the error message.


Next: 🐛 Debugging
Previous: ⏳️ Streams
Top: Table of contents