Skip to content

Eseguire un processo (Bun.spawn())

Fornisci un comando come array di stringhe. Il risultato di Bun.spawn() è un oggetto Bun.Subprocess.

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

Il secondo argomento di Bun.spawn è un oggetto di parametri che può essere usato per configurare il subprocesso.

ts
const proc = Bun.spawn(["bun", "--version"], {
  cwd: "./path/to/subdir", // specifica una directory di lavoro
  env: { ...process.env, FOO: "bar" }, // specifica variabili d'ambiente
  onExit(proc, exitCode, signalCode, error) {
    // handler di uscita
  },
});

proc.pid; // ID del processo del subprocesso

Stream di input

Di default, lo stream di input del subprocesso è undefined; può essere configurato con il parametro 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); ..."
ValoreDescrizione
nullDefault. Non fornire input al subprocesso
"pipe"Restituisce un FileSink per scrittura incrementale veloce
"inherit"Eredita lo stdin del processo genitore
Bun.file()Legge dal file specificato
TypedArray | DataViewUsa un buffer binario come input
ResponseUsa il body della response come input
RequestUsa il body della request come input
ReadableStreamUsa uno stream leggibile come input
BlobUsa un blob come input
numberLegge dal file con un dato file descriptor

L'opzione "pipe" permette di scrivere incrementalmente nello stream di input del subprocesso dal processo genitore.

ts
const proc = Bun.spawn(["cat"], {
  stdin: "pipe", // restituisce un FileSink per scrivere
});

// accoda dati stringa
proc.stdin.write("hello");

// accoda dati binari
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));

// invia dati bufferizzati
proc.stdin.flush();

// chiude lo stream di input
proc.stdin.end();

Passare un ReadableStream a stdin permette di inviare dati da un ReadableStream JavaScript direttamente all'input del 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!"

Stream di output

Puoi leggere i risultati dal subprocesso tramite le proprietà stdout e stderr. Di default queste sono istanze di ReadableStream.

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

Configura lo stream di output passando uno dei seguenti valori a stdout/stderr:

ValoreDescrizione
"pipe"Default per stdout. Invia l'output a un ReadableStream sull'oggetto Subprocess restituito
"inherit"Default per stderr. Eredita dal processo genitore
"ignore"Scarta l'output
Bun.file()Scrive sul file specificato
numberScrive sul file con il dato file descriptor

Gestione dell'uscita

Usa il callback onExit per ascoltare quando il processo esce o viene terminato.

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

Per comodità, la proprietà exited è una Promise che si risolve quando il processo esce.

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

await proc.exited; // si risolve quando il processo esce
proc.killed; // boolean — il processo è stato terminato?
proc.exitCode; // null | number
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...

Per terminare un processo:

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

proc.kill(15); // specifica un codice di segnale
proc.kill("SIGTERM"); // specifica un nome di segnale

Il processo genitore bun non terminerà finché tutti i processi figlio non sono usciti. Usa proc.unref() per scollegare il processo figlio dal genitore.

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

Utilizzo delle risorse

Puoi ottenere informazioni sull'utilizzo delle risorse del processo dopo che è uscito:

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

const usage = proc.resourceUsage();
console.log(`Memoria massima usata: ${usage.maxRSS} byte`);
console.log(`Tempo CPU (user): ${usage.cpuTime.user} µs`);
console.log(`Tempo CPU (system): ${usage.cpuTime.system} µs`);

Usare AbortSignal

Puoi interrompere un subprocesso usando un AbortSignal:

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

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

// Più tardi, per interrompere il processo:
controller.abort();

Usare timeout e killSignal

Puoi impostare un timeout per un subprocesso per terminare automaticamente dopo una durata specifica:

ts
// Termina il processo dopo 5 secondi
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000, // 5 secondi in millisecondi
});

await proc.exited; // Si risolve dopo 5 secondi

Di default, i processi con timeout vengono terminati con il segnale SIGTERM. Puoi specificare un segnale diverso con l'opzione killSignal:

ts
// Termina il processo con SIGKILL dopo 5 secondi
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000,
  killSignal: "SIGKILL", // Può essere nome stringa o numero di segnale
});

L'opzione killSignal controlla anche quale segnale viene inviato quando un AbortSignal viene interrotto.

Usare maxBuffer

Per spawnSync, puoi limitare il numero massimo di byte di output prima che il processo venga terminato:

ts
// Termina 'yes' dopo che emette oltre 100 byte di output
const result = Bun.spawnSync({
  cmd: ["yes"], // o ["bun", "exec", "yes"] su Windows
  maxBuffer: 100,
});
// il processo esce

Comunicazione inter-processo (IPC)

Bun supporta un canale di comunicazione inter-processo diretto tra due processi bun. Per ricevere messaggi da un subprocesso Bun spawnato, specifica un handler ipc.

ts
const child = Bun.spawn(["bun", "child.ts"], {
  ipc(message) {
    /**
     * Il messaggio ricevuto dal subprocesso
     **/
  },
});

Il processo genitore può inviare messaggi al subprocesso usando il metodo .send() sull'istanza Subprocess restituita. Un riferimento al subprocesso mittente è anche disponibile come secondo argomento nell'handler ipc.

ts
const childProc = Bun.spawn(["bun", "child.ts"], {
  ipc(message, childProc) {
    /**
     * Il messaggio ricevuto dal subprocesso
     **/
    childProc.send("Rispondi al figlio");
  },
});

childProc.send("Sono tuo padre"); // Il genitore può anche inviare messaggi al figlio

Nel frattempo il processo figlio può inviare messaggi al suo genitore usando process.send() e ricevere messaggi con process.on("message"). Questa è la stessa API usata per child_process.fork() in Node.js.

ts
process.send("Ciao dal figlio come stringa");
process.send({ message: "Ciao dal figlio come oggetto" });

process.on("message", message => {
  // stampa messaggio dal genitore
  console.log(message);
});
ts
// invia una stringa
process.send("Ciao dal figlio come stringa");

// invia un oggetto
process.send({ message: "Ciao dal figlio come oggetto" });

L'opzione serialization controlla il formato di comunicazione sottostante tra i due processi:

  • advanced: (default) I messaggi vengono serializzati usando l'API serialize di JSC, che supporta la clonazione di tutto ciò che structuredClone supporta. Questo non supporta il trasferimento di proprietà di oggetti.
  • json: I messaggi vengono serializzati usando JSON.stringify e JSON.parse, che non supporta tanti tipi di oggetti quanto advanced.

Per disconnettere il canale IPC dal processo genitore, chiama:

ts
childProc.disconnect();

IPC tra Bun e Node.js

Per usare IPC tra un processo bun e un processo Node.js, imposta serialization: "json" in Bun.spawn. Questo perché Node.js e Bun usano motori JavaScript diversi con formati di serializzazione degli oggetti diversi.

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

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

API Bloccante (Bun.spawnSync())

Bun fornisce un equivalente sincrono di Bun.spawn chiamato Bun.spawnSync. Questa è un'API bloccante che supporta gli stessi input e parametri di Bun.spawn. Restituisce un oggetto SyncSubprocess, che differisce da Subprocess in alcuni modi.

  1. Contiene una proprietà success che indica se il processo è uscito con codice di uscita zero.
  2. Le proprietà stdout e stderr sono istanze di Buffer invece di ReadableStream.
  3. Non c'è proprietà stdin. Usa Bun.spawn per scrivere incrementalmente nello stream di input del subprocesso.
ts
const proc = Bun.spawnSync(["echo", "hello"]);

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

Come regola generale, l'API asincrona Bun.spawn è migliore per server HTTP e app, e Bun.spawnSync è migliore per costruire strumenti da riga di comando.


Benchmark

NOTE

⚡️ Sotto il cofano, `Bun.spawn` e `Bun.spawnSync` usano [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html).

Lo spawnSync di Bun esegue processi il 60% più velocemente del modulo child_process di 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

Riferimento

Un riferimento dell'API Spawn e dei tipi è mostrato sotto. I tipi reali hanno generici complessi per tipizzare fortemente gli stream Subprocess con le opzioni passate a Bun.spawn e Bun.spawnSync. Per i dettagli completi, trova questi tipi come definiti in 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 // per usare il default
    | BunFile
    | ArrayBufferView
    | number;

  type Writable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // equivalente a "ignore"
    | undefined // per usare il 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";

Bun a cura di www.bunjs.com.cn