mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
442 lines
11 KiB
Markdown
442 lines
11 KiB
Markdown
Spawn child processes with `Bun.spawn` or `Bun.spawnSync`.
|
|
|
|
## Spawn a process (`Bun.spawn()`)
|
|
|
|
Provide a command as an array of strings. The result of `Bun.spawn()` is a `Bun.Subprocess` object.
|
|
|
|
```ts
|
|
const proc = Bun.spawn(["bun", "--version"]);
|
|
console.log(await proc.exited); // 0
|
|
```
|
|
|
|
The second argument to `Bun.spawn` is a parameters object that can be used to configure the subprocess.
|
|
|
|
```ts
|
|
const proc = Bun.spawn(["bun", "--version"], {
|
|
cwd: "./path/to/subdir", // specify a working directory
|
|
env: { ...process.env, FOO: "bar" }, // specify environment variables
|
|
onExit(proc, exitCode, signalCode, error) {
|
|
// exit handler
|
|
},
|
|
});
|
|
|
|
proc.pid; // process ID of subprocess
|
|
```
|
|
|
|
## Input stream
|
|
|
|
By default, the input stream of the subprocess is undefined; it can be configured with the `stdin` parameter.
|
|
|
|
```ts
|
|
const proc = Bun.spawn(["cat"], {
|
|
stdin: await fetch(
|
|
"https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js",
|
|
),
|
|
});
|
|
|
|
const text = await new Response(proc.stdout).text();
|
|
console.log(text); // "const input = "hello world".repeat(400); ..."
|
|
```
|
|
|
|
{% table %}
|
|
|
|
---
|
|
|
|
- `null`
|
|
- **Default.** Provide no input to the subprocess
|
|
|
|
---
|
|
|
|
- `"pipe"`
|
|
- Return a `FileSink` for fast incremental writing
|
|
|
|
---
|
|
|
|
- `"inherit"`
|
|
- Inherit the `stdin` of the parent process
|
|
|
|
---
|
|
|
|
- `Bun.file()`
|
|
- Read from the specified file.
|
|
|
|
---
|
|
|
|
- `TypedArray | DataView`
|
|
- Use a binary buffer as input.
|
|
|
|
---
|
|
|
|
- `Response`
|
|
- Use the response `body` as input.
|
|
|
|
---
|
|
|
|
- `Request`
|
|
- Use the request `body` as input.
|
|
|
|
---
|
|
|
|
- `number`
|
|
- Read from the file with a given file descriptor.
|
|
|
|
{% /table %}
|
|
|
|
The `"pipe"` option lets incrementally write to the subprocess's input stream from the parent process.
|
|
|
|
```ts
|
|
const proc = Bun.spawn(["cat"], {
|
|
stdin: "pipe", // return a FileSink for writing
|
|
});
|
|
|
|
// enqueue string data
|
|
proc.stdin.write("hello");
|
|
|
|
// enqueue binary data
|
|
const enc = new TextEncoder();
|
|
proc.stdin.write(enc.encode(" world!"));
|
|
|
|
// send buffered data
|
|
proc.stdin.flush();
|
|
|
|
// close the input stream
|
|
proc.stdin.end();
|
|
```
|
|
|
|
## Output streams
|
|
|
|
You can read results from the subprocess via the `stdout` and `stderr` properties. By default these are instances of `ReadableStream`.
|
|
|
|
```ts
|
|
const proc = Bun.spawn(["bun", "--version"]);
|
|
const text = await new Response(proc.stdout).text();
|
|
console.log(text); // => "1.1.7"
|
|
```
|
|
|
|
Configure the output stream by passing one of the following values to `stdout/stderr`:
|
|
|
|
{% table %}
|
|
|
|
---
|
|
|
|
- `"pipe"`
|
|
- **Default for `stdout`.** Pipe the output to a `ReadableStream` on the returned `Subprocess` object.
|
|
|
|
---
|
|
|
|
- `"inherit"`
|
|
- **Default for `stderr`.** Inherit from the parent process.
|
|
|
|
---
|
|
|
|
- `Bun.file()`
|
|
- Write to the specified file.
|
|
|
|
---
|
|
|
|
- `null`
|
|
- Write to `/dev/null`.
|
|
|
|
---
|
|
|
|
- `number`
|
|
- Write to the file with the given file descriptor.
|
|
|
|
{% /table %}
|
|
|
|
## Exit handling
|
|
|
|
Use the `onExit` callback to listen for the process exiting or being killed.
|
|
|
|
```ts
|
|
const proc = Bun.spawn(["bun", "--version"], {
|
|
onExit(proc, exitCode, signalCode, error) {
|
|
// exit handler
|
|
},
|
|
});
|
|
```
|
|
|
|
For convenience, the `exited` property is a `Promise` that resolves when the process exits.
|
|
|
|
```ts
|
|
const proc = Bun.spawn(["bun", "--version"]);
|
|
|
|
await proc.exited; // resolves when process exit
|
|
proc.killed; // boolean — was the process killed?
|
|
proc.exitCode; // null | number
|
|
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...
|
|
```
|
|
|
|
To kill a process:
|
|
|
|
```ts
|
|
const proc = Bun.spawn(["bun", "--version"]);
|
|
proc.kill();
|
|
proc.killed; // true
|
|
|
|
proc.kill(); // specify an exit code
|
|
```
|
|
|
|
The parent `bun` process will not terminate until all child processes have exited. Use `proc.unref()` to detach the child process from the parent.
|
|
|
|
```
|
|
const proc = Bun.spawn(["bun", "--version"]);
|
|
proc.unref();
|
|
```
|
|
|
|
## Inter-process communication (IPC)
|
|
|
|
Bun supports direct inter-process communication channel between two `bun` processes. To receive messages from a spawned Bun subprocess, specify an `ipc` handler.
|
|
|
|
```ts#parent.ts
|
|
const child = Bun.spawn(["bun", "child.ts"], {
|
|
ipc(message) {
|
|
/**
|
|
* The message received from the sub process
|
|
**/
|
|
},
|
|
});
|
|
```
|
|
|
|
The parent process can send messages to the subprocess using the `.send()` method on the returned `Subprocess` instance. A reference to the sending subprocess is also available as the second argument in the `ipc` handler.
|
|
|
|
```ts#parent.ts
|
|
const childProc = Bun.spawn(["bun", "child.ts"], {
|
|
ipc(message, childProc) {
|
|
/**
|
|
* The message received from the sub process
|
|
**/
|
|
childProc.send("Respond to child")
|
|
},
|
|
});
|
|
|
|
childProc.send("I am your father"); // The parent can send messages to the child as well
|
|
```
|
|
|
|
Meanwhile the child process can send messages to its parent using with `process.send()` and receive messages with `process.on("message")`. This is the same API used for `child_process.fork()` in Node.js.
|
|
|
|
```ts#child.ts
|
|
process.send("Hello from child as string");
|
|
process.send({ message: "Hello from child as object" });
|
|
|
|
process.on("message", (message) => {
|
|
// print message from parent
|
|
console.log(message);
|
|
});
|
|
```
|
|
|
|
```ts#child.ts
|
|
// send a string
|
|
process.send("Hello from child as string");
|
|
|
|
// send an object
|
|
process.send({ message: "Hello from child as object" });
|
|
```
|
|
|
|
The `ipcMode` option controls the underlying communication format between the two processes:
|
|
|
|
- `advanced`: (default) Messages are serialized using the JSC `serialize` API, which supports cloning [everything `structuredClone` supports](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). This does not support transferring ownership of objects.
|
|
- `json`: Messages are serialized using `JSON.stringify` and `JSON.parse`, which does not support as many object types as `advanced` does.
|
|
|
|
### IPC between Bun & Node.js
|
|
|
|
To use IPC between a `bun` process and a Node.js process, set `serialization: "json"` in `Bun.spawn`. This is because Node.js and Bun use different JavaScript engines with different object serialization formats.
|
|
|
|
```js#bun-node-ipc.js
|
|
if (typeof Bun !== "undefined") {
|
|
const prefix = `[bun ${process.versions.bun} 🐇]`;
|
|
const node = Bun.spawn({
|
|
cmd: ["node", __filename],
|
|
ipc({ message }) {
|
|
console.log(message);
|
|
node.send({ message: `${prefix} 👋 hey node` });
|
|
node.kill();
|
|
},
|
|
stdio: ["inherit", "inherit", "inherit"],
|
|
serialization: "json",
|
|
});
|
|
|
|
node.send({ message: `${prefix} 👋 hey node` });
|
|
} else {
|
|
const prefix = `[node ${process.version}]`;
|
|
process.on("message", ({ message }) => {
|
|
console.log(message);
|
|
process.send({ message: `${prefix} 👋 hey bun` });
|
|
});
|
|
}
|
|
```
|
|
|
|
## Blocking API (`Bun.spawnSync()`)
|
|
|
|
Bun provides a synchronous equivalent of `Bun.spawn` called `Bun.spawnSync`. This is a blocking API that supports the same inputs and parameters as `Bun.spawn`. It returns a `SyncSubprocess` object, which differs from `Subprocess` in a few ways.
|
|
|
|
1. It contains a `success` property that indicates whether the process exited with a zero exit code.
|
|
2. The `stdout` and `stderr` properties are instances of `Buffer` instead of `ReadableStream`.
|
|
3. There is no `stdin` property. Use `Bun.spawn` to incrementally write to the subprocess's input stream.
|
|
|
|
```ts
|
|
const proc = Bun.spawnSync(["echo", "hello"]);
|
|
|
|
console.log(proc.stdout.toString());
|
|
// => "hello\n"
|
|
```
|
|
|
|
As a rule of thumb, the asynchronous `Bun.spawn` API is better for HTTP servers and apps, and `Bun.spawnSync` is better for building command-line tools.
|
|
|
|
## Benchmarks
|
|
|
|
{%callout%}
|
|
⚡️ Under the hood, `Bun.spawn` and `Bun.spawnSync` use [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html).
|
|
{%/callout%}
|
|
|
|
Bun's `spawnSync` spawns processes 60% faster than the Node.js `child_process` module.
|
|
|
|
```bash
|
|
$ bun spawn.mjs
|
|
cpu: Apple M1 Max
|
|
runtime: bun 1.x (arm64-darwin)
|
|
|
|
benchmark time (avg) (min … max) p75 p99 p995
|
|
--------------------------------------------------------- -----------------------------
|
|
spawnSync echo hi 888.14 µs/iter (821.83 µs … 1.2 ms) 905.92 µs 1 ms 1.03 ms
|
|
$ node spawn.node.mjs
|
|
cpu: Apple M1 Max
|
|
runtime: node v18.9.1 (arm64-darwin)
|
|
|
|
benchmark time (avg) (min … max) p75 p99 p995
|
|
--------------------------------------------------------- -----------------------------
|
|
spawnSync echo hi 1.47 ms/iter (1.14 ms … 2.64 ms) 1.57 ms 2.37 ms 2.52 ms
|
|
```
|
|
|
|
## Reference
|
|
|
|
A simple reference of the Spawn API and types are shown below. The real types have complex generics to strongly type the `Subprocess` streams with the options passed to `Bun.spawn` and `Bun.spawnSync`. For full details, find these types as defined [bun.d.ts](https://github.com/oven-sh/bun/blob/main/packages/bun-types/bun.d.ts).
|
|
|
|
```ts
|
|
interface Bun {
|
|
spawn(command: string[], options?: SpawnOptions.OptionsObject): Subprocess;
|
|
spawnSync(
|
|
command: string[],
|
|
options?: SpawnOptions.OptionsObject,
|
|
): SyncSubprocess;
|
|
|
|
spawn(options: { cmd: string[] } & SpawnOptions.OptionsObject): Subprocess;
|
|
spawnSync(
|
|
options: { cmd: string[] } & SpawnOptions.OptionsObject,
|
|
): SyncSubprocess;
|
|
}
|
|
|
|
namespace SpawnOptions {
|
|
interface OptionsObject {
|
|
cwd?: string;
|
|
env?: Record<string, string>;
|
|
stdin?: SpawnOptions.Readable;
|
|
stdout?: SpawnOptions.Writable;
|
|
stderr?: SpawnOptions.Writable;
|
|
onExit?: (
|
|
proc: Subprocess,
|
|
exitCode: number | null,
|
|
signalCode: string | null,
|
|
error: Error | null,
|
|
) => void;
|
|
}
|
|
|
|
type Readable =
|
|
| "pipe"
|
|
| "inherit"
|
|
| "ignore"
|
|
| null // equivalent to "ignore"
|
|
| undefined // to use default
|
|
| BunFile
|
|
| ArrayBufferView
|
|
| number;
|
|
|
|
type Writable =
|
|
| "pipe"
|
|
| "inherit"
|
|
| "ignore"
|
|
| null // equivalent to "ignore"
|
|
| undefined // to use default
|
|
| BunFile
|
|
| ArrayBufferView
|
|
| number
|
|
| ReadableStream
|
|
| Blob
|
|
| Response
|
|
| Request;
|
|
}
|
|
|
|
interface Subprocess<Stdin, Stdout, Stderr> {
|
|
readonly pid: number;
|
|
// the exact stream types here are derived from the generic parameters
|
|
readonly stdin: number | ReadableStream | FileSink | undefined;
|
|
readonly stdout: number | ReadableStream | undefined;
|
|
readonly stderr: number | ReadableStream | undefined;
|
|
|
|
readonly exited: Promise<number>;
|
|
|
|
readonly exitCode: number | undefined;
|
|
readonly signalCode: Signal | null;
|
|
readonly killed: boolean;
|
|
|
|
ref(): void;
|
|
unref(): void;
|
|
kill(code?: number): void;
|
|
}
|
|
|
|
interface SyncSubprocess<Stdout, Stderr> {
|
|
readonly pid: number;
|
|
readonly success: boolean;
|
|
// the exact buffer types here are derived from the generic parameters
|
|
readonly stdout: Buffer | undefined;
|
|
readonly stderr: Buffer | undefined;
|
|
}
|
|
|
|
type ReadableSubprocess = Subprocess<any, "pipe", "pipe">;
|
|
type WritableSubprocess = Subprocess<"pipe", any, any>;
|
|
type PipedSubprocess = Subprocess<"pipe", "pipe", "pipe">;
|
|
type NullSubprocess = Subprocess<null, null, null>;
|
|
|
|
type ReadableSyncSubprocess = SyncSubprocess<"pipe", "pipe">;
|
|
type NullSyncSubprocess = SyncSubprocess<null, null>;
|
|
|
|
type Signal =
|
|
| "SIGABRT"
|
|
| "SIGALRM"
|
|
| "SIGBUS"
|
|
| "SIGCHLD"
|
|
| "SIGCONT"
|
|
| "SIGFPE"
|
|
| "SIGHUP"
|
|
| "SIGILL"
|
|
| "SIGINT"
|
|
| "SIGIO"
|
|
| "SIGIOT"
|
|
| "SIGKILL"
|
|
| "SIGPIPE"
|
|
| "SIGPOLL"
|
|
| "SIGPROF"
|
|
| "SIGPWR"
|
|
| "SIGQUIT"
|
|
| "SIGSEGV"
|
|
| "SIGSTKFLT"
|
|
| "SIGSTOP"
|
|
| "SIGSYS"
|
|
| "SIGTERM"
|
|
| "SIGTRAP"
|
|
| "SIGTSTP"
|
|
| "SIGTTIN"
|
|
| "SIGTTOU"
|
|
| "SIGUNUSED"
|
|
| "SIGURG"
|
|
| "SIGUSR1"
|
|
| "SIGUSR2"
|
|
| "SIGVTALRM"
|
|
| "SIGWINCH"
|
|
| "SIGXCPU"
|
|
| "SIGXFSZ"
|
|
| "SIGBREAK"
|
|
| "SIGLOST"
|
|
| "SIGINFO";
|
|
```
|