karawaci.kode

2026-04-15 · 5 min

Bun Shell vs Node child_process — kapan pakai yang mana

Bun 1.0+ ship dengan $ Shell API yang membuat shell scripting di JavaScript jauh lebih bersih:

import { $ } from 'bun';
await $`ls -la`;
await $`echo "hello" > greeting.txt`;
const output = await $`cat greeting.txt`.text();

Compare dengan Node child_process equivalent:

import { exec } from 'child_process';
import { promisify } from 'util';
const execP = promisify(exec);

await execP('ls -la');
const { stdout } = await execP('cat greeting.txt');

Bun Shell jauh lebih ringkas. Tetapi ada beberapa scenario di mana Node child_process tetap pilihan terbaik. Ini breakdown-nya.

Pakai Bun Shell ketika…

1. Script lokal yang Anda jalankan sendiri — task automation, build scripts, CI tooling. Syntax bersih, error handling built-in (await $ throws kalau exit non-zero).

2. Anda butuh stream stdout secara real-time — Bun Shell support await $.lines() yang yields per-line, jauh lebih mudah daripada Node stream parsing.

3. Multi-command pipelines — Bun Shell support glob expansion, env variable interpolation, dan pipe dengan syntax yang lebih intuitif:

await $`find . -name "*.ts" | wc -l`;

4. Anda comfortable dengan Bun runtime — Bun Shell tidak akan portable ke pure Node project. Jadi pertimbangkan dependency lock.

Pakai Node child_process ketika…

1. Code Anda harus run di Node atau Deno juga — child_process di Node, mirip API di Deno (lewat compat). Bun Shell tidak portable.

2. Anda perlu fine control atas stdin/stdout/stderr — beberapa use case (interactive prompt, custom protocol over pipes) lebih mudah dengan spawn() API yang lebih granular daripada Bun Shell.

3. Long-running child process dengan event-driven communication — Bun Shell optimized untuk one-shot commands. Untuk daemon, worker pool, atau supervised process yang butuh restart, spawn() dengan event listeners lebih appropriate.

4. Cross-platform shell semantic — Node child_process dengan shell: false punya predictable behavior across OS. Bun Shell mengasumsikan POSIX-ish behavior; di Windows native bisa surprise.

Performance comparison

Saya benchmark sederhana — execute echo "test" 1000 kali di loop:

MethodTotal timePer-op
Bun Shell $2.1s2.1ms
Bun spawn (Bun-native)1.4s1.4ms
Node exec (promisified)4.8s4.8ms
Node spawn2.7s2.7ms

Bun Shell adalah middle ground — lebih cepat dari Node exec, tapi 50% slower dari Bun native spawn. Untuk script yang call sub-process puluhan kali, prefer Bun native API atau Node spawn.

Real-world example

Saya migrate satu deploy script saya dari Node child_process ke Bun Shell awal tahun ini:

Before (Node, ~25 lines):

import { exec } from 'child_process';
import { promisify } from 'util';
const execP = promisify(exec);

async function deploy() {
  const { stdout: branch } = await execP('git branch --show-current');
  if (branch.trim() !== 'main') throw new Error('not on main');
  await execP('git pull');
  await execP('bun run build');
  await execP('wrangler deploy');
}

After (Bun, ~10 lines):

import { $ } from 'bun';

async function deploy() {
  const branch = (await $`git branch --show-current`.text()).trim();
  if (branch !== 'main') throw new Error('not on main');
  await $`git pull && bun run build && wrangler deploy`;
}

Lebih readable, less imports, error handling lebih built-in. Tetapi untuk script ini sustainable karena cuma jalan di lokal saya dengan Bun installed.

Verdict

Untuk most script local automation: Bun Shell. Untuk production code yang harus portable: Node child_process. Trade-off antara DX dan compatibility.


Benchmark dilakukan di MacBook Air M2 (16GB), macOS 14. Per-op timing dapat berbeda di setup berbeda.