Skip to content

Spawn de um processo (Bun.spawn())

Forneça um comando como um array de strings. O resultado de Bun.spawn() é um objeto Bun.Subprocess.

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

O segundo argumento para Bun.spawn é um objeto de parâmetros que pode ser usado para configurar o subprocesso.

ts
const proc = Bun.spawn(["bun", "--version"], {
  cwd: "./path/to/subdir", // especifica um diretório de trabalho
  env: { ...process.env, FOO: "bar" }, // especifica variáveis de ambiente
  onExit(proc, exitCode, signalCode, error) {
    // handler de saída
  },
});

proc.pid; // ID do processo do subprocesso

Stream de entrada

Por padrão, o stream de entrada do subprocesso é undefined; pode ser configurado com o parâmetro 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); ..."
ValorDescrição
nullPadrão. Não fornece entrada para o subprocesso
"pipe"Retorna um FileSink para escrita incremental rápida
"inherit"Herda o stdin do processo pai
Bun.file()Lê do arquivo especificado
TypedArray | DataViewUsa um buffer binário como entrada
ResponseUsa o body da response como entrada
RequestUsa o body da request como entrada
ReadableStreamUsa um readable stream como entrada
BlobUsa um blob como entrada
numberLê do arquivo com um dado file descriptor

A opção "pipe" permite escrever incrementalmente no stream de entrada do subprocesso a partir do processo pai.

ts
const proc = Bun.spawn(["cat"], {
  stdin: "pipe", // retorna um FileSink para escrita
});

// enfileira dados de string
proc.stdin.write("hello");

// enfileira dados binários
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));

// envia dados em buffer
proc.stdin.flush();

// fecha o stream de entrada
proc.stdin.end();

Passar um ReadableStream para stdin permite canalizar dados de um ReadableStream JavaScript diretamente para a entrada do subprocesso:

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

Streams de saída

Você pode ler resultados do subprocesso através das propriedades stdout e stderr. Por padrão, estas são instâncias de ReadableStream.

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

Configure o stream de saída passando um dos seguintes valores para stdout/stderr:

ValorDescrição
"pipe"Padrão para stdout. Canaliza a saída para um ReadableStream no objeto Subprocess retornado
"inherit"Padrão para stderr. Herda do processo pai
"ignore"Descarta a saída
Bun.file()Escreve no arquivo especificado
numberEscreve no arquivo com o dado file descriptor

Handler de saída

Use o callback onExit para ouvir quando o processo sair ou for encerrado.

ts
const proc = Bun.spawn(["bun", "--version"], {
  onExit(proc, exitCode, signalCode, error) {
    // handler de saída
  },
});

Por conveniência, a propriedade exited é uma Promise que resolve quando o processo sai.

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

await proc.exited; // resolve quando o processo sai
proc.killed; // boolean — o processo foi encerrado?
proc.exitCode; // null | number
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...

Para encerrar um processo:

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

proc.kill(15); // especifica um código de sinal
proc.kill("SIGTERM"); // especifica um nome de sinal

O processo pai bun não terminará até que todos os processos filhos tenham saído. Use proc.unref() para desanexar o processo filho do pai.

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

Uso de recursos

Você pode obter informações sobre o uso de recursos do processo após ele ter saído:

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

const usage = proc.resourceUsage();
console.log(`Memória máxima usada: ${usage.maxRSS} bytes`);
console.log(`Tempo de CPU (user): ${usage.cpuTime.user} µs`);
console.log(`Tempo de CPU (system): ${usage.cpuTime.system} µs`);

Usando AbortSignal

Você pode abortar um subprocesso usando um AbortSignal:

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

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

// Depois, para abortar o processo:
controller.abort();

Usando timeout e killSignal

Você pode definir um timeout para um subprocesso terminar automaticamente após uma duração específica:

ts
// Encerra o processo após 5 segundos
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000, // 5 segundos em milissegundos
});

await proc.exited; // Será resolvido após 5 segundos

Por padrão, processos com timeout são encerrados com o sinal SIGTERM. Você pode especificar um sinal diferente com a opção killSignal:

ts
// Encerra o processo com SIGKILL após 5 segundos
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000,
  killSignal: "SIGKILL", // Pode ser nome da string ou número do sinal
});

A opção killSignal também controla qual sinal é enviado quando um AbortSignal é abortado.

Usando maxBuffer

Para spawnSync, você pode limitar o número máximo de bytes de saída antes que o processo seja encerrado:

ts
// Encerra 'yes' após emitir mais de 100 bytes de saída
const result = Bun.spawnSync({
  cmd: ["yes"], // ou ["bun", "exec", "yes"] no Windows
  maxBuffer: 100,
});
// processo sai

Comunicação inter-processo (IPC)

O Bun suporta canal de comunicação inter-processo direto entre dois processos bun. Para receber mensagens de um subprocesso Bun spawnado, especifique um handler ipc.

ts
const child = Bun.spawn(["bun", "child.ts"], {
  ipc(message) {
    /**
     * A mensagem recebida do subprocesso
     **/
  },
});

O processo pai pode enviar mensagens para o subprocesso usando o método .send() na instância Subprocess retornada. Uma referência ao subprocesso de envio também está disponível como o segundo argumento no handler ipc.

ts
const childProc = Bun.spawn(["bun", "child.ts"], {
  ipc(message, childProc) {
    /**
     * A mensagem recebida do subprocesso
     **/
    childProc.send("Responder ao filho");
  },
});

childProc.send("Eu sou seu pai"); // O pai também pode enviar mensagens para o filho

Enquanto isso, o processo filho pode enviar mensagens para seu pai usando process.send() e receber mensagens com process.on("message"). Esta é a mesma API usada para child_process.fork() no Node.js.

ts
process.send("Olá do filho como string");
process.send({ message: "Olá do filho como objeto" });

process.on("message", message => {
  // imprime mensagem do pai
  console.log(message);
});
ts
// envia uma string
process.send("Olá do filho como string");

// envia um objeto
process.send({ message: "Olá do filho como objeto" });

A opção serialization controla o formato de comunicação subjacente entre os dois processos:

  • advanced: (padrão) Mensagens são serializadas usando a API serialize do JSC, que suporta clonar tudo que structuredClone suporta. Isso não suporta transferência de propriedade de objetos.
  • json: Mensagens são serializadas usando JSON.stringify e JSON.parse, que não suporta tantos tipos de objetos quanto advanced.

Para desconectar o canal IPC do processo pai, chame:

ts
childProc.disconnect();

IPC entre Bun e Node.js

Para usar IPC entre um processo bun e um processo Node.js, defina serialization: "json" em Bun.spawn. Isso ocorre porque Node.js e Bun usam motores JavaScript diferentes com formatos de serialização de objetos diferentes.

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} 👋 olá node` });
      node.kill();
    },
    stdio: ["inherit", "inherit", "inherit"],
    serialization: "json",
  });

  node.send({ message: `${prefix} 👋 olá node` });
} else {
  const prefix = `[node ${process.version}]`;
  process.on("message", ({ message }) => {
    console.log(message);
    process.send({ message: `${prefix} 👋 olá bun` });
  });
}

API Bloqueante (Bun.spawnSync())

O Bun fornece um equivalente síncrono de Bun.spawn chamado Bun.spawnSync. Esta é uma API bloqueante que suporta as mesmas entradas e parâmetros que Bun.spawn. Retorna um objeto SyncSubprocess, que difere de Subprocess de algumas formas.

  1. Contém uma propriedade success que indica se o processo saiu com código de saída zero.
  2. As propriedades stdout e stderr são instâncias de Buffer em vez de ReadableStream.
  3. Não há propriedade stdin. Use Bun.spawn para escrever incrementalmente no stream de entrada do subprocesso.
ts
const proc = Bun.spawnSync(["echo", "hello"]);

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

Como regra geral, a API assíncrona Bun.spawn é melhor para servidores HTTP e apps, e Bun.spawnSync é melhor para construir ferramentas de linha de comando.


Benchmarks

NOTE

Internamente, `Bun.spawn` e `Bun.spawnSync` usam [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html).

O spawnSync do Bun spawniza processos 60% mais rápido que o módulo child_process do Node.js.

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

Referência

Uma referência da Spawn API e tipos é mostrada abaixo. Os tipos reais têm generics complexos para tipar fortemente os streams Subprocess com as opções passadas para Bun.spawn e Bun.spawnSync. Para detalhes completos, encontre estes tipos conforme definidos em 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 // equivalente a "ignore"
    | undefined // para usar padrão
    | BunFile
    | ArrayBufferView
    | number;

  type Writable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // equivalente a "ignore"
    | undefined // para usar padrão
    | 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 edit