Skip to content

プロセスの生成(Bun.spawn()

コマンドを文字列の配列として提供します。Bun.spawn() の結果は Bun.Subprocess オブジェクトです。

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

Bun.spawn の 2 番目の引数は、サブプロセスを設定するために使用できるパラメーターオブジェクトです。

ts
const proc = Bun.spawn(["bun", "--version"], {
  cwd: "./path/to/subdir", // 作業ディレクトリを指定
  env: { ...process.env, FOO: "bar" }, // 環境変数を指定
  onExit(proc, exitCode, signalCode, error) {
    // 終了ハンドラー
  },
});

proc.pid; // サブプロセスのプロセス ID

入力ストリーム

デフォルトでは、サブプロセスの入力ストリームは未定義です。stdin パラメーターで設定できます。

ts
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ブロブを入力として使用する
number指定されたファイルディスクリプターでファイルから読み取る

"pipe" オプションを使用すると、親プロセスからサブプロセスの入力ストリームに増分的に書き込むことができます。

ts
const proc = Bun.spawn(["cat"], {
  stdin: "pipe", // 書き込み用の FileSink を返す
});

// 文字列データをエンキュー
proc.stdin.write("hello");

// バイナリデータをエンキュー
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));

// バッファデータを送信
proc.stdin.flush();

// 入力ストリームを閉じる
proc.stdin.end();

ReadableStreamstdin に渡すと、JavaScript の ReadableStream からサブプロセスの入力に直接データをパイプできます。

ts
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 proc.stdout.text();
console.log(output); // "Hello from ReadableStream!"

出力ストリーム

stdout および stderr プロパティを介してサブプロセスから結果を読み取ることができます。デフォルトでは、これらは ReadableStream のインスタンスです。

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

以下の値のいずれかを stdout/stderr に渡して出力ストリームを設定します。

説明
"pipe"stdout のデフォルト。 返された Subprocess オブジェクトの ReadableStream に出力をパイプ
"inherit"stderr のデフォルト。 親プロセスから継承
"ignore"出力を破棄
Bun.file()指定されたファイルに書き込む
number指定されたファイルディスクリプターを持つファイルに書き込む

終了ハンドリング

onExit コールバックを使用して、プロセスが終了または強制終了されたときにリッスンします。

ts
const proc = Bun.spawn(["bun", "--version"], {
  onExit(proc, exitCode, signalCode, error) {
    // 終了ハンドラー
  },
});

便宜上、exited プロパティはプロセスが終了したときに解決される Promise です。

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

await proc.exited; // プロセスが終了したときに解決
proc.killed; // ブール値 — プロセスは強制終了されましたか?
proc.exitCode; // null | 数値
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...

プロセスを強制終了するには:

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

proc.kill(15); // シグナルコードを指定
proc.kill("SIGTERM"); // シグナル名を指定

すべての子プロセスが終了するまで、親 bun プロセスは終了しません。proc.unref() を使用して、子プロセスを親からデタッチします。

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

リソース使用量

プロセスが終了した後、プロセスのリソース使用量に関する情報を取得できます。

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

const usage = proc.resourceUsage();
console.log(`使用された最大メモリ:${usage.maxRSS} バイト`);
console.log(`CPU 時間(ユーザー):${usage.cpuTime.user} µs`);
console.log(`CPU 時間(システム):${usage.cpuTime.system} µs`);

AbortSignal の使用

AbortSignal を使用してサブプロセスを中止できます。

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

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

// 後で、プロセスを中止するには:
controller.abort();

timeout と killSignal の使用

特定の期間後に自動的に終了するようにサブプロセスのタイムアウトを設定できます。

ts
// 5 秒後にプロセスを強制終了
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000, // ミリ秒単位で 5 秒
});

await proc.exited; // 5 秒後に解決されます

デフォルトでは、タイムアウトしたプロセスは SIGTERM シグナルで強制終了されます。killSignal オプションで別のシグナルを指定できます。

ts
// 5 秒後に SIGKILL でプロセスを強制終了
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000,
  killSignal: "SIGKILL", // 文字列名またはシグナル番号を指定可能
});

killSignal オプションは、AbortSignal が中止されたときに送信されるシグナルも制御します。

maxBuffer の使用

spawnSync では、プロセスが強制終了される前に出力される最大バイト数を制限できます。

ts
// 100 バイトを超える出力を生成した後に 'yes' を強制終了
const result = Bun.spawnSync({
  cmd: ["yes"], // または Windows では ["bun", "exec", "yes"]
  maxBuffer: 100,
});
// プロセスは終了します

プロセス間通信(IPC)

Bun は 2 つの bun プロセス間の直接のプロセス間通信チャネルをサポートしています。生成された Bun サブプロセスからメッセージを受信するには、ipc ハンドラーを指定します。

ts
const child = Bun.spawn(["bun", "child.ts"], {
  ipc(message) {
    /**
     * サブプロセスから受信したメッセージ
     **/
  },
});

親プロセスは、返された Subprocess インスタンスの .send() メソッドを使用してサブプロセスにメッセージを送信できます。送信元のサブプロセスへの参照も、ipc ハンドラーの 2 番目の引数として利用可能です。

ts
const childProc = Bun.spawn(["bun", "child.ts"], {
  ipc(message, childProc) {
    /**
     * サブプロセスから受信したメッセージ
     **/
    childProc.send("子プロセスに応答");
  },
});

childProc.send("I am your father"); // 親も子にメッセージを送信できます

一方、子プロセスは process.send() を使用して親にメッセージを送信し、process.on("message") でメッセージを受信できます。これは Node.js の child_process.fork() で使用されるのと同じ API です。

ts
process.send("文字列としての子プロセスからこんにちは");
process.send({ message: "オブジェクトとしての子プロセスからこんにちは" });

process.on("message", message => {
  // 親からのメッセージを出力
  console.log(message);
});
ts
// 文字列を送信
process.send("文字列としての子プロセスからこんにちは");

// オブジェクトを送信
process.send({ message: "オブジェクトとしての子プロセスからこんにちは" });

serialization オプションは、2 つのプロセス間の基盤となる通信形式を制御します。

  • advanced:(デフォルト)メッセージは JSC の serialize API を使用してシリアライズされます。これは structuredClone がサポートするすべて のクローンをサポートします。これはオブジェクトの所有権の転送をサポートしていません。
  • json:メッセージは JSON.stringifyJSON.parse を使用してシリアライズされます。これは advanced ほど多くのオブジェクト型をサポートしていません。

親プロセスから IPC チャネルを切断するには、以下を呼び出します。

ts
childProc.disconnect();

Bun と Node.js 間の IPC

bun プロセスと Node.js プロセス間で IPC を使用するには、Bun.spawnserialization: "json" を設定します。これは、Node.js と Bun が異なるオブジェクトシリアライゼーション形式を持つ異なる JavaScript エンジンを使用しているためです。

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 プロパティは ReadableStream ではなく Buffer のインスタンスです。
  3. stdin プロパティはありません。サブプロセスの入力ストリームに増分的に書き込むには Bun.spawn を使用します。
ts
const proc = Bun.spawnSync(["echo", "hello"]);

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

経験則として、非同期の Bun.spawn API は HTTP サーバーとアプリに適しており、Bun.spawnSync はコマンドラインツールの構築に適しています。


ベンチマーク

NOTE

⚡️ 内部では、`Bun.spawn` と `Bun.spawnSync` は [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html) を使用しています。

Bun の spawnSync は、Node.js の child_process モジュールよりも 60% 高速にプロセスを生成します。

bash
bun spawn.mjs
txt
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
sh
node spawn.node.mjs
txt
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 で定義されたこれらの型を見つけてください。

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 // "ignore" と同等
    | undefined // デフォルトを使用
    | BunFile
    | ArrayBufferView
    | number;

  type Writable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // "ignore" と同等
    | undefined // デフォルトを使用
    | 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";

Bun by www.bunjs.com.cn 編集