Запуск процесса (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", // указать рабочий каталог
env: { ...process.env, FOO: "bar" }, // указать переменные окружения
onExit(proc, exitCode, signalCode, error) {
// обработчик выхода
},
});
proc.pid; // идентификатор процесса подпроцессаВходной поток
По умолчанию входной поток подпроцесса не определён; его можно настроить с помощью параметра 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", // вернуть FileSink для записи
});
// поставить в очередь строковые данные
proc.stdin.write("hello");
// поставить в очередь двоичные данные
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));
// отправить буферизованные данные
proc.stdin.flush();
// закрыть входной поток
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 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.3\n"Настройте выходной поток, передав одно из следующих значений в stdout/stderr:
| Значение | Описание |
|---|---|
"pipe" | По умолчанию для stdout. Направить вывод в ReadableStream возвращённого объекта Subprocess |
"inherit" | По умолчанию для stderr. Унаследовать от родительского процесса |
"ignore" | Отбросить вывод |
Bun.file() | Записать в указанный файл |
number | Записать в файл с данным дескриптором файла |
Обработка выхода
Используйте обратный вызов onExit для прослушивания выхода процесса или его завершения.
const proc = Bun.spawn(["bun", "--version"], {
onExit(proc, exitCode, signalCode, error) {
// обработчик выхода
},
});Для удобства свойство exited — это Promise, который разрешается при выходе процесса.
const proc = Bun.spawn(["bun", "--version"]);
await proc.exited; // разрешается при выходе процесса
proc.killed; // булево — был ли процесс завершён?
proc.exitCode; // null | number
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...Для завершения процесса:
const proc = Bun.spawn(["bun", "--version"]);
proc.kill();
proc.killed; // true
proc.kill(15); // указать код сигнала
proc.kill("SIGTERM"); // указать имя сигналаРодительский процесс 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(`Максимальное использование памяти: ${usage.maxRSS} байт`);
console.log(`Время CPU (пользователь): ${usage.cpuTime.user} мкс`);
console.log(`Время CPU (система): ${usage.cpuTime.system} мкс`);Использование AbortSignal
Вы можете прервать подпроцесс с помощью AbortSignal:
const controller = new AbortController();
const { signal } = controller;
const proc = Bun.spawn({
cmd: ["sleep", "100"],
signal,
});
// Позже, для прерывания процесса:
controller.abort();Использование timeout и killSignal
Вы можете установить таймаут для подпроцесса для автоматического завершения после определённой продолжительности:
// Завершить процесс через 5 секунд
const proc = Bun.spawn({
cmd: ["sleep", "10"],
timeout: 5000, // 5 секунд в миллисекундах
});
await proc.exited; // Разрешится через 5 секундПо умолчанию процессы с истёкшим временем завершаются сигналом SIGTERM. Вы можете указать другой сигнал с помощью опции killSignal:
// Завершить процесс с помощью SIGKILL через 5 секунд
const proc = Bun.spawn({
cmd: ["sleep", "10"],
timeout: 5000,
killSignal: "SIGKILL", // Может быть именем сигнала или номером сигнала
});Опция killSignal также управляет тем, какой сигнал отправляется при прерывании AbortSignal.
Использование maxBuffer
Для spawnSync вы можете ограничить максимальное количество байтов вывода перед завершением процесса:
// Завершить 'yes' после того, как он выдаст более 100 байт вывода
const result = Bun.spawnSync({
cmd: ["yes"], // или ["bun", "exec", "yes"] на Windows
maxBuffer: 100,
});
// процесс завершаетсяМежпроцессное взаимодействие (IPC)
Bun поддерживает прямой канал межпроцессного взаимодействия между двумя процессами bun. Для получения сообщений от порождённого подпроцесса Bun укажите обработчик ipc.
const child = Bun.spawn(["bun", "child.ts"], {
ipc(message) {
/**
* Сообщение, полученное от подпроцесса
**/
},
});Родительский процесс может отправлять сообщения подпроцессу с помощью метода .send() в возвращённом экземпляре Subprocess. Ссылка на отправляющий подпроцесс также доступна как второй аргумент в обработчике ipc.
const childProc = Bun.spawn(["bun", "child.ts"], {
ipc(message, childProc) {
/**
* Сообщение, полученное от подпроцесса
**/
childProc.send("Ответ дочернему процессу");
},
});
childProc.send("Я твой отец"); // Родитель также может отправлять сообщения дочернему процессуТем временем дочерний процесс может отправлять сообщения своему родителю с помощью process.send() и получать сообщения с помощью process.on("message"). Это тот же API, который используется для child_process.fork() в Node.js.
process.send("Привет от дочернего процесса как строка");
process.send({ message: "Привет от дочернего процесса как объект" });
process.on("message", message => {
// напечатать сообщение от родителя
console.log(message);
});// отправить строку
process.send("Привет от дочернего процесса как строка");
// отправить объект
process.send({ message: "Привет от дочернего процесса как объект" });Опция serialization управляет основным форматом взаимодействия между двумя процессами:
advanced: (по умолчанию) Сообщения сериализуются с помощью JSCserializeAPI, который поддерживает клонирование всего, что поддерживаетstructuredClone. Это не поддерживает передачу владения объектами.json: Сообщения сериализуются с помощьюJSON.stringifyиJSON.parse, что не поддерживает столько типов объектов, сколькоadvanced.
Для отключения канала IPC из родительского процесса вызовите:
childProc.disconnect();IPC между Bun и Node.js
Для использования IPC между процессом bun и процессом Node.js установите serialization: "json" в Bun.spawn. Это связано с тем, что 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} 👋 привет node` });
node.kill();
},
stdio: ["inherit", "inherit", "inherit"],
serialization: "json",
});
node.send({ message: `${prefix} 👋 привет node` });
} else {
const prefix = `[node ${process.version}]`;
process.on("message", ({ message }) => {
console.log(message);
process.send({ message: `${prefix} 👋 привет 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"Как правило, асинхронный API Bun.spawn лучше подходит для HTTP-серверов и приложений, а Bun.spawnSync лучше подходит для создания инструментов командной строки.
Бенчмарки
NOTE
⚡️ Под капотом `Bun.spawn` и `Bun.spawnSync` используют [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html).spawnSync от Bun запускает процессы на 60% быстрее, чем модуль child_process Node.js.
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 msСправочник
Справочник 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 // эквивалентно "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";