Skip to content

Exécuter un processus (Bun.spawn())

Fournissez une commande sous forme de tableau de chaînes. Le résultat de Bun.spawn() est un objet Bun.Subprocess.

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

Le deuxième argument de Bun.spawn est un objet de paramètres qui peut être utilisé pour configurer le sous-processus.

ts
const proc = Bun.spawn(["bun", "--version"], {
  cwd: "./path/to/subdir", // spécifier un répertoire de travail
  env: { ...process.env, FOO: "bar" }, // spécifier des variables d'environnement
  onExit(proc, exitCode, signalCode, error) {
    // gestionnaire de sortie
  },
});

proc.pid; // ID du processus du sous-processus

Flux d'entrée

Par défaut, le flux d'entrée du sous-processus est indéfini ; il peut être configuré avec le paramètre 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); ..."
ValeurDescription
nullPar défaut. Ne fournir aucune entrée au sous-processus
"pipe"Retourne un FileSink pour une écriture incrémentielle rapide
"inherit"Hérite du stdin du processus parent
Bun.file()Lire depuis le fichier spécifié
TypedArray | DataViewUtiliser un tampon binaire comme entrée
ResponseUtiliser le body de la réponse comme entrée
RequestUtiliser le body de la requête comme entrée
ReadableStreamUtiliser un flux lisible comme entrée
BlobUtiliser un blob comme entrée
numberLire depuis le fichier avec un descripteur de fichier donné

L'option "pipe" permet d'écrire de manière incrémentielle dans le flux d'entrée du sous-processus depuis le processus parent.

ts
const proc = Bun.spawn(["cat"], {
  stdin: "pipe", // retourne un FileSink pour l'écriture
});

// mettre en file d'attente des données de chaîne
proc.stdin.write("hello");

// mettre en file d'attente des données binaires
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));

// envoyer les données tamponnées
proc.stdin.flush();

// fermer le flux d'entrée
proc.stdin.end();

Passer un ReadableStream à stdin vous permet de transférer des données depuis un ReadableStream JavaScript directement vers l'entrée du sous-processus :

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

Flux de sortie

Vous pouvez lire les résultats du sous-processus via les propriétés stdout et stderr. Par défaut, ce sont des instances de ReadableStream.

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

Configurez le flux de sortie en passant l'une des valeurs suivantes à stdout/stderr :

ValeurDescription
"pipe"Par défaut pour stdout. Transfère la sortie vers un ReadableStream sur l'objet Subprocess
"inherit"Par défaut pour stderr. Hérite du processus parent
"ignore"Ignore la sortie
Bun.file()Écrit dans le fichier spécifié
numberÉcrit dans le fichier avec le descripteur de fichier donné

Gestion de la sortie

Utilisez le callback onExit pour écouter la sortie ou la fin du processus.

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

Par commodité, la propriété exited est une Promise qui se résout lorsque le processus se termine.

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

await proc.exited; // se résout lorsque le processus se termine
proc.killed; // booléen — le processus a-t-il été tué ?
proc.exitCode; // null | number
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...

Pour tuer un processus :

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

proc.kill(15); // spécifier un code de signal
proc.kill("SIGTERM"); // spécifier un nom de signal

Le processus parent bun ne se terminera pas tant que tous les processus enfants ne se seront pas terminés. Utilisez proc.unref() pour détacher le processus enfant du parent.

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

Utilisation des ressources

Vous pouvez obtenir des informations sur l'utilisation des ressources du processus après sa terminaison :

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

const usage = proc.resourceUsage();
console.log(`Mémoire max utilisée : ${usage.maxRSS} octets`);
console.log(`Temps CPU (utilisateur) : ${usage.cpuTime.user} µs`);
console.log(`Temps CPU (système) : ${usage.cpuTime.system} µs`);

Utilisation d'AbortSignal

Vous pouvez abandonner un sous-processus en utilisant un AbortSignal :

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

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

// Plus tard, pour abandonner le processus :
controller.abort();

Utilisation de timeout et killSignal

Vous pouvez définir un délai d'attente pour qu'un sous-processus se termine automatiquement après une durée spécifique :

ts
// Tuer le processus après 5 secondes
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000, // 5 secondes en millisecondes
});

await proc.exited; // Se résoudra après 5 secondes

Par défaut, les processus expirés sont tués avec le signal SIGTERM. Vous pouvez spécifier un signal différent avec l'option killSignal :

ts
// Tuer le processus avec SIGKILL après 5 secondes
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000,
  killSignal: "SIGKILL", // Peut être un nom de signal ou un numéro
});

L'option killSignal contrôle également quel signal est envoyé lorsqu'un AbortSignal est abandonné.

Utilisation de maxBuffer

Pour spawnSync, vous pouvez limiter le nombre maximal d'octets de sortie avant que le processus ne soit tué :

ts
// Tuer 'yes' après qu'il ait émis plus de 100 octets de sortie
const result = Bun.spawnSync({
  cmd: ["yes"], // ou ["bun", "exec", "yes"] sur Windows
  maxBuffer: 100,
});
// le processus se termine

Communication inter-processus (IPC)

Bun prend en charge un canal de communication inter-processus direct entre deux processus bun. Pour recevoir des messages d'un sous-processus Bun, spécifiez un gestionnaire ipc.

ts
const child = Bun.spawn(["bun", "child.ts"], {
  ipc(message) {
    /**
     * Le message reçu du sous-processus
     **/
  },
});

Le processus parent peut envoyer des messages au sous-processus en utilisant la méthode .send() sur l'instance Subprocess retournée. Une référence au sous-processus envoyeur est également disponible comme deuxième argument dans le gestionnaire ipc.

ts
const childProc = Bun.spawn(["bun", "child.ts"], {
  ipc(message, childProc) {
    /**
     * Le message reçu du sous-processus
     **/
    childProc.send("Répondre à l'enfant");
  },
});

childProc.send("Je suis ton père"); // Le parent peut également envoyer des messages à l'enfant

Pendant ce temps, le processus enfant peut envoyer des messages à son parent en utilisant process.send() et recevoir des messages avec process.on("message"). C'est la même API utilisée pour child_process.fork() dans Node.js.

ts
process.send("Hello depuis l'enfant en tant que chaîne");
process.send({ message: "Hello depuis l'enfant en tant qu'objet" });

process.on("message", message => {
  // afficher le message du parent
  console.log(message);
});
ts
// envoyer une chaîne
process.send("Hello depuis l'enfant en tant que chaîne");

// envoyer un objet
process.send({ message: "Hello depuis l'enfant en tant qu'objet" });

L'option serialization contrôle le format de communication sous-jacent entre les deux processus :

  • advanced: (par défaut) Les messages sont sérialisés en utilisant l'API serialize de JSC, qui prend en charge le clonage de tout ce que structuredClone prend en charge. Cela ne prend pas en charge le transfert de propriété d'objets.
  • json: Les messages sont sérialisés en utilisant JSON.stringify et JSON.parse, qui ne prennent pas en charge autant de types d'objets que advanced.

Pour déconnecter le canal IPC du processus parent, appelez :

ts
childProc.disconnect();

IPC entre Bun et Node.js

Pour utiliser IPC entre un processus bun et un processus Node.js, définissez serialization: "json" dans Bun.spawn. En effet, Node.js et Bun utilisent des moteurs JavaScript différents avec des formats de sérialisation d'objets différents.

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 bloquante (Bun.spawnSync())

Bun fournit un équivalent synchrone de Bun.spawn appelé Bun.spawnSync. C'est une API bloquante qui prend en charge les mêmes entrées et paramètres que Bun.spawn. Elle retourne un objet SyncSubprocess, qui diffère de Subprocess à quelques égards.

  1. Il contient une propriété success qui indique si le processus s'est terminé avec un code de sortie zéro.
  2. Les propriétés stdout et stderr sont des instances de Buffer au lieu de ReadableStream.
  3. Il n'y a pas de propriété stdin. Utilisez Bun.spawn pour écrire de manière incrémentielle dans le flux d'entrée du sous-processus.
ts
const proc = Bun.spawnSync(["echo", "hello"]);

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

En règle générale, l'API asynchrone Bun.spawn est meilleure pour les serveurs HTTP et les applications, et Bun.spawnSync est meilleure pour créer des outils en ligne de commande.


Benchmarks

NOTE

⚡️ Sous le capot, `Bun.spawn` et `Bun.spawnSync` utilisent [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html).

Le spawnSync de Bun exécute les processus 60 % plus rapidement que le module child_process de 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

Référence

Une référence de l'API Spawn et des types est présentée ci-dessous. Les vrais types ont des génériques complexes pour typer fortement les flux Subprocess avec les options passées à Bun.spawn et Bun.spawnSync. Pour plus de détails, trouvez ces types tels que définis dans 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 // équivalent à "ignore"
    | undefined // pour utiliser la valeur par défaut
    | BunFile
    | ArrayBufferView
    | number;

  type Writable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // équivalent à "ignore"
    | undefined // pour utiliser la valeur par défaut
    | 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 édité par www.bunjs.com.cn