Bun

子进程

使用 Bun.spawnBun.spawnSync 生成子进程。

生成一个进程 (Bun.spawn())

将命令作为字符串数组提供。Bun.spawn() 的结果是一个 Bun.Subprocess 对象。

Bun.spawn(["echo", "hello"]);

Bun.spawn 的第二个参数是一个参数对象,可用于配置子进程。

const proc = Bun.spawn(["echo", "hello"], {
  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

输入流

默认情况下,子进程的输入流未定义;可以使用 stdin 参数进行配置。

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); ..."
null默认。不向子进程提供输入
"pipe"返回一个 FileSink 以进行快速增量写入
"inherit"继承父进程的 stdin
Bun.file()从指定的文件中读取。
TypedArray | DataView使用二进制缓冲区作为输入。
Response使用响应 body 作为输入。
Request使用请求 body 作为输入。
number使用给定的文件描述符从文件中读取。

"pipe" 选项允许从父进程增量写入子进程的输入流。

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();

输出流

你可以通过 stdoutstderr 属性从子进程中读取结果。默认情况下,这些是 ReadableStream 的实例。

const proc = Bun.spawn(["echo", "hello"]);
const text = await new Response(proc.stdout).text();
console.log(text); // => "hello"

通过将以下值之一传递给 stdout/stderr 来配置输出流

"pipe"stdout 的默认值。将输出管道传输到返回的 Subprocess 对象上的 ReadableStream
"inherit"stderr 的默认值。从父进程继承。
Bun.file()写入指定的文件。
null写入 /dev/null
number使用给定的文件描述符写入文件。

退出处理

使用 onExit 回调来监听进程退出或被杀死。

const proc = Bun.spawn(["echo", "hello"], {
  onExit(proc, exitCode, signalCode, error) {
    // exit handler
  },
});

为方便起见,exited 属性是一个 Promise,当进程退出时解析。

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" | ...

杀死一个进程

const proc = Bun.spawn(["echo", "hello"]);
proc.kill();
proc.killed; // true

proc.kill(); // specify an exit code

bun 进程不会终止,直到所有子进程退出。使用 proc.unref() 将子进程从父进程分离。

const proc = Bun.spawn(["echo", "hello"]);
proc.unref();

进程间通信 (IPC)

Bun 支持两个 bun 进程之间的直接进程间通信通道。要接收来自已生成的 Bun 子进程的消息,请指定一个 ipc 处理程序。

注意 — 此 API 仅与其他 bun 进程兼容。使用 process.execPath 获取当前正在运行的 bun 可执行文件的路径。

parent.ts
const child = Bun.spawn(["bun", "child.ts"], {
  ipc(message) {
    /**
     * The message received from the sub process
     **/
  },
});

父进程可以使用已返回 Subprocess 实例上的 .send() 方法向子进程发送消息。发送子进程的引用也可用作 ipc 处理程序中的第二个参数。

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

同时,子进程可以使用 process.send() 向其父进程发送消息,并使用 process.on("message") 接收消息。这是 Node.js 中 child_process.fork() 使用的相同 API。

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);
});
child.ts
// send a string
process.send("Hello from child as string");

// send an object
process.send({ message: "Hello from child as object" });

ipcMode 选项控制两个进程之间的底层通信格式

  • advanced:(默认)使用 JSC serialize API 序列化消息,该 API 支持克隆 structuredClone 支持的所有内容。不支持传输对象的归属权。
  • json: 使用 JSON.stringifyJSON.parse 序列化消息,与 advanced 相比,它不支持那么多的对象类型。

阻塞 API (Bun.spawnSync())

Bun 提供了 Bun.spawn 的同步等效项,称为 Bun.spawnSync。这是一个阻塞 API,支持与 Bun.spawn 相同的输入和参数。它返回一个 SyncSubprocess 对象,该对象在某些方面与 Subprocess 不同。

  1. 它包含一个 success 属性,指示进程是否以零退出代码退出。
  2. stdoutstderr 属性是 Buffer 的实例,而不是 ReadableStream
  3. 没有 stdin 属性。使用 Bun.spawn 逐步写入子进程的输入流。
const proc = Bun.spawnSync(["echo", "hello"]);

console.log(proc.stdout.toString());
// => "hello\n"

根据经验,异步 Bun.spawn API 更适合 HTTP 服务器和应用程序,而 Bun.spawnSync 更适合构建命令行工具。

基准

⚡️ 在底层,Bun.spawnBun.spawnSync 使用 posix_spawn(3)

Bun 的 spawnSync 比 Node.js child_process 模块快 60% 地生成进程。

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

参考

Spawn API 和类型的简单参考如下所示。实际类型具有复杂的泛型,可以根据传递给 Bun.spawnBun.spawnSync 的选项对 Subprocess 流进行强类型化。有关完整详细信息,请在 bun.d.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";