Bun

子进程

使用 Bun.spawnBun.spawnSync 启动子进程。

启动进程 (Bun.spawn())

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

const proc = Bun.spawn(["bun", "--version"]);
console.log(await proc.exited); // 0

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

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

输入流

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

const proc = Bun.spawn(["cat"], {
  stdin: await fetch(
    "https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js",
  ),
});

const text = await 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 作为输入。
ReadableStream使用可读流作为输入。
Blob使用 blob 作为输入。
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();

ReadableStream 传递给 stdin 可以将数据从 JavaScript ReadableStream 直接管道传输到子进程的输入。

const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello from ");
    controller.enqueue("ReadableStream!");
    controller.close();
  },
});

const proc = Bun.spawn(["cat"], {
  stdin: stream,
  stdout: "pipe",
});

const output = await new Response(proc.stdout).text();
console.log(output); // "Hello from ReadableStream!"

输出流

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

const proc = Bun.spawn(["bun", "--version"]);
const text = await proc.stdout.text();
console.log(text); // => "1.3.0\n"

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

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

退出处理

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

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

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

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

杀死进程

const proc = Bun.spawn(["bun", "--version"]);
proc.kill();
proc.killed; // true

proc.kill(15); // specify a signal code
proc.kill("SIGTERM"); // specify a signal name

bun 进程在所有子进程退出之前不会终止。使用 proc.unref() 将子进程与父进程分离。

const proc = Bun.spawn(["bun", "--version"]);
proc.unref();

资源使用

退出后,您可以获取有关进程资源使用情况的信息。

const proc = Bun.spawn(["bun", "--version"]);
await proc.exited;

const usage = proc.resourceUsage();
console.log(`Max memory used: ${usage.maxRSS} bytes`);
console.log(`CPU time (user): ${usage.cpuTime.user} µs`);
console.log(`CPU time (system): ${usage.cpuTime.system} µs`);

使用 AbortSignal

您可以使用 AbortSignal 来中止子进程。

const controller = new AbortController();
const { signal } = controller;

const proc = Bun.spawn({
  cmd: ["sleep", "100"],
  signal,
});

// Later, to abort the process:
controller.abort();

使用 timeout 和 killSignal

您可以为子进程设置超时,使其在特定持续时间后自动终止。

// Kill the process after 5 seconds
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000, // 5 seconds in milliseconds
});

await proc.exited; // Will resolve after 5 seconds

默认情况下,超时进程会使用 SIGTERM 信号杀死。您可以使用 killSignal 选项指定不同的信号。

// Kill the process with SIGKILL after 5 seconds
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000,
  killSignal: "SIGKILL", // Can be string name or signal number
});

killSignal 选项还控制中止 AbortSignal 时发送的信号。

使用 maxBuffer

对于 spawnSync,您可以限制进程在被杀死之前允许的最大输出字节数。

// KIll 'yes' after it emits over 100 bytes of output
const result = Bun.spawnSync({
  cmd: ["yes"], // or ["bun", "exec", "yes"] on windows
  maxBuffer: 100,
});
// process exits

进程间通信 (IPC)

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

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" });

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

  • advanced:(默认)消息使用 JSC serialize API 进行序列化,该 API 支持克隆 structuredClone 支持的所有内容。这不支持对象所有权的转移。
  • json:消息使用 JSON.stringifyJSON.parse 进行序列化,支持的对象类型不如 advanced 多。

要从父进程断开 IPC 通道,请调用

childProc.disconnect();

Bun 与 Node.js 之间的 IPC

要在 bun 进程和 Node.js 进程之间使用 IPC,请在 Bun.spawn 中设置 serialization: "json"。这是因为 Node.js 和 Bun 使用不同的 JavaScript 引擎,具有不同的对象序列化格式。

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` });
  });
}

阻塞 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

Reference

下面的 Spawn API 和类型的参考。实际的类型具有复杂的泛型,用于为 Subprocess 流提供与传递给 Bun.spawnBun.spawnSync 的选项相匹配的强类型。有关完整详细信息,请在 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 | undefined>;
    stdio?: [Writable, Readable, Readable];
    stdin?: Writable;
    stdout?: Readable;
    stderr?: Readable;
    onExit?(
      subprocess: Subprocess,
      exitCode: number | null,
      signalCode: number | null,
      error?: ErrorLike,
    ): void | Promise<void>;
    ipc?(message: any, subprocess: Subprocess): void;
    serialization?: "json" | "advanced";
    windowsHide?: boolean;
    windowsVerbatimArguments?: boolean;
    argv0?: string;
    signal?: AbortSignal;
    timeout?: number;
    killSignal?: string | number;
    maxBuffer?: number;
  }

  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 extends AsyncDisposable {
  readonly stdin: FileSink | number | undefined;
  readonly stdout: ReadableStream<Uint8Array> | number | undefined;
  readonly stderr: ReadableStream<Uint8Array> | number | undefined;
  readonly readable: ReadableStream<Uint8Array> | number | undefined;
  readonly pid: number;
  readonly exited: Promise<number>;
  readonly exitCode: number | null;
  readonly signalCode: NodeJS.Signals | null;
  readonly killed: boolean;

  kill(exitCode?: number | NodeJS.Signals): void;
  ref(): void;
  unref(): void;

  send(message: any): void;
  disconnect(): void;
  resourceUsage(): ResourceUsage | undefined;
}

interface SyncSubprocess {
  stdout: Buffer | undefined;
  stderr: Buffer | undefined;
  exitCode: number;
  success: boolean;
  resourceUsage: ResourceUsage;
  signalCode?: string;
  exitedDueToTimeout?: true;
  pid: number;
}

interface ResourceUsage {
  contextSwitches: {
    voluntary: number;
    involuntary: number;
  };

  cpuTime: {
    user: number;
    system: number;
    total: number;
  };
  maxRSS: number;

  messages: {
    sent: number;
    received: number;
  };
  ops: {
    in: number;
    out: number;
  };
  shmSize: number;
  signalCount: number;
  swapCount: number;
}

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