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使用二進制緩衝區作為輸入。
回應使用回應 body 作為輸入。
請求使用請求 body 作為輸入。
數字從具有給定檔案描述符的檔案中讀取。

"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
數字寫入具有給定檔案描述符的檔案。

退出處理

使用 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 序列化訊息,它支援複製 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";