使用 Bun.spawn 或 Bun.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!"
输出流
您可以通过 stdout 和 stderr 属性读取子进程的结果。默认情况下,这些是 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 处理程序。
const child = Bun.spawn(["bun", "child.ts"], {
ipc(message) {
/**
* The message received from the sub process
**/
},
});
父进程可以使用返回的 Subprocess 实例上的 .send() 方法向子进程发送消息。在 ipc 处理程序中,对发送子进程的引用也作为第二个参数可用。
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 相同。
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);
});
// send a string
process.send("Hello from child as string");
// send an object
process.send({ message: "Hello from child as object" });
serialization 选项控制两个进程之间的底层通信格式。
advanced:(默认)消息使用 JSCserializeAPI 进行序列化,该 API 支持克隆structuredClone支持的所有内容。这不支持对象所有权的转移。json:消息使用JSON.stringify和JSON.parse进行序列化,支持的对象类型不如advanced多。
要从父进程断开 IPC 通道,请调用
childProc.disconnect();
Bun 与 Node.js 之间的 IPC
要在 bun 进程和 Node.js 进程之间使用 IPC,请在 Bun.spawn 中设置 serialization: "json"。这是因为 Node.js 和 Bun 使用不同的 JavaScript 引擎,具有不同的对象序列化格式。
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 不同。
- 它包含一个
success属性,指示进程是否以零退出码退出。 stdout和stderr属性是Buffer实例,而不是ReadableStream。- 没有
stdin属性。使用Bun.spawn逐步写入子进程的输入流。
const proc = Bun.spawnSync(["echo", "hello"]);
console.log(proc.stdout.toString());
// => "hello\n"
总的来说,异步 Bun.spawn API 更适用于 HTTP 服务器和应用程序,而 Bun.spawnSync 更适用于构建命令行工具。
基准测试
⚡️ 在底层,Bun.spawn 和 Bun.spawnSync 使用 posix_spawn(3)。
Bun 的 spawnSync 比 Node.js child_process 模块快 60% 启动进程。
bun spawn.mjscpu: 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 msnode spawn.node.mjscpu: 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 msReference
下面的 Spawn API 和类型的参考。实际的类型具有复杂的泛型,用于为 Subprocess 流提供与传递给 Bun.spawn 和 Bun.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";