diff options
Diffstat (limited to 'docs/api/spawn.md')
-rw-r--r-- | docs/api/spawn.md | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/docs/api/spawn.md b/docs/api/spawn.md new file mode 100644 index 000000000..876a0577d --- /dev/null +++ b/docs/api/spawn.md @@ -0,0 +1,336 @@ +Spawn child processes with `Bun.spawn` or `Bun.spawnSync`. + +## Spawn a process + +Provide a command as an array of strings. The result of `Bun.spawn()` is a `Bun.Subprocess` object. + +```ts +Bun.spawn(["echo", "hello"]); +``` + +The second argument to `Bun.spawn` is a parameters object that can be used ton configure the subprocess. + +```ts +const proc = Bun.spawn(["echo", "hello"], { + cwd: "./path/to/subdir", // specify a working direcory + 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(["echo", "hello"]); +const text = await new Response(proc.stdout).text(); +console.log(text); // => "hello" +``` + +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(["echo", "hello"], { + 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(["echo", "hello"]); + +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(["echo", "hello"]); +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(["echo", "hello"]); +proc.unref(); +``` + +## Blocking API + +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 0.2.0 (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 + +```ts +interface Bun { + spawn(command: string[], options?: SpawnOptions): Subprocess; + spawnSync(command: string[], options?: SpawnOptions): SyncSubprocess; +} + +interface SpawnOptions { + cwd?: string; + env?: Record<string, string>; + stdin?: + | "pipe" + | "inherit" + | "ignore" + | ReadableStream + | BunFile + | Blob + | Response + | Request + | number + | null; + stdout?: + | "pipe" + | "inherit" + | "ignore" + | BunFile + | TypedArray + | DataView + | null; + stderr?: + | "pipe" + | "inherit" + | "ignore" + | BunFile + | TypedArray + | DataView + | null; + onExit?: ( + proc: Subprocess, + exitCode: number | null, + signalCode: string | null, + error: Error | null, + ) => void; +} + +interface Subprocess { + readonly pid: number; + readonly stdin?: number | ReadableStream | FileSink; + readonly stdout?: number | ReadableStream; + readonly stderr?: number | ReadableStream; + + 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 { + readonly pid: number; + readonly success: boolean; + readonly stdout: Buffer; + readonly stderr: Buffer; +} + +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"; +``` |