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 new Response(proc.stdout).text();
console.log(text); // "const input = "hello world".repeat(400); ..."
null預設。 不向子程序提供任何輸入
"pipe"傳回 FileSink 以進行快速增量寫入
"inherit"繼承父進程的 stdin
Bun.file()從指定檔案讀取。
TypedArray | DataView使用二進制緩衝區作為輸入。
Response使用 response 的 body 作為輸入。
Request使用 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();

輸出串流

您可以透過 stdoutstderr 屬性從子進程讀取結果。預設情況下,這些是 ReadableStream 的實例。

const proc = Bun.spawn(["bun", "--version"]);
const text = await new Response(proc.stdout).text();
console.log(text); // => "1.2.5"

透過將下列其中一個值傳遞給 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

您可以為子進程設定 timeout,以便在特定持續時間後自動終止

// 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

預設情況下,timeout 的進程會以 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
});

AbortSignal 被中止時,killSignal 選項也控制發送哪個信號。

進程間通訊 (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

參考

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

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