تخطى إلى المحتوى
22 دقيقة قراءة

Bun في الإنتاج: ما يعمل، وما لا يعمل، وما فاجأني

Bun كبيئة تشغيل، ومدير حزم، ومُحزم، ومُشغّل اختبارات. مقاييس أداء حقيقية، وفجوات توافق Node.js، وأنماط الترحيل، وأين أستخدم Bun في الإنتاج اليوم.

مشاركة:X / TwitterLinkedIn

كل بضع سنوات، يحصل نظام JavaScript البيئي على بيئة تشغيل جديدة والخطاب يتبع مساراً متوقعاً. حماس. مقاييس أداء. "X ماتت." فحص واقعي. الاستقرار في حالات الاستخدام الفعلية حيث تتألق الأداة الجديدة حقاً.

Bun في منتصف ذلك المسار الآن. وعلى عكس معظم المنافسين، إنها باقية. ليس لأنها "أسرع" (رغم أنها كذلك في الغالب)، بل لأنها تحل مشكلة مختلفة حقاً: سلسلة أدوات JavaScript بها الكثير من الأجزاء المتحركة، وBun تدمجها في واحدة.

أستخدم Bun بقدرات مختلفة منذ أكثر من سنة الآن. بعضها في الإنتاج. بعضها يحل محل أدوات اعتقدت أنني لن أستبدلها أبداً. هذا المقال هو تقييم صادق لما يعمل، وما لا يعمل، وأين لا تزال الفجوات مهمة.

ما هو Bun فعلاً#

أول مفهوم خاطئ يجب توضيحه: Bun ليس "Node.js أسرع." هذا التأطير يقلل من قيمته.

Bun هو أربع أدوات في ملف ثنائي واحد:

  1. بيئة تشغيل JavaScript/TypeScript — تشغّل كودك، مثل Node.js أو Deno
  2. مدير حزم — يحل محل npm أو yarn أو pnpm
  3. مُحزم — يحل محل esbuild أو webpack أو Rollup لبعض حالات الاستخدام
  4. مُشغّل اختبارات — يحل محل Jest أو Vitest لمعظم مجموعات الاختبارات

الفلسفة الأساسية هي: توحيد الأدوات. بدلاً من تشغيل Node.js مع npm مع esbuild مع Jest — كلها أدوات منفصلة بتهيئات منفصلة — تشغّل Bun. أمر واحد. تهيئة صفرية لمعظم المهام.

إنه مبني على محرك JavaScriptCore (محرك Safari) بدلاً من V8 (محرك Chrome الذي يشغّل Node.js). يفسر هذا بعض خصائص الأداء ومفارقات التوافق.

مدير الحزم: حيث يفوز Bun بوضوح#

هذا هو الاعتماد الأسهل لأنه صفر مخاطر. يمكنك استخدام Bun فقط كمدير حزم والاستمرار في تشغيل تطبيقك على Node.js.

السرعة حقيقية#

bash
# حذف node_modules وإعادة التثبيت من الصفر
 
# npm (v10):
time npm install
# real 34.2s
 
# pnpm (v9):
time pnpm install
# real 12.1s
 
# bun (v1.1):
time bun install
# real 4.3s

هذه أرقام حقيقية من مشروعي (تطبيق Next.js مع ~180 تبعية). سرعة bun install ليست تحسيناً طفيفاً — إنها تثبيت بارد أسرع بـ 3x من pnpm و8x أسرع من npm.

الاختلاف يصبح أكثر دراماتيكية مع عمليات التثبيت الدافئة (حيث الحزم موجودة بالفعل في المخزن المؤقت):

bash
# التثبيت الدافئ (node_modules محذوفة، المخزن المؤقت يبقى)
 
# npm:
time npm install
# real 18.7s
 
# pnpm:
time pnpm install
# real 4.8s
 
# bun:
time bun install
# real 0.9s

أقل من ثانية. ليس لأن Bun يتخطى العمل — بل يربط الحزم فعلاً في node_modules. الأمر أنه يفعل ذلك بمحلل مكتوب بالأساس، وتحميل فوري متوازٍ لأقصى حد.

ملف القفل الثنائي#

يولّد Bun ملف bun.lockb بدلاً من package-lock.json أو pnpm-lock.yaml. إنه ثنائي.

الجيد: إنه أسرع بشكل كبير في القراءة والكتابة. الصيغة الثنائية تعني أن Bun يمكنه تحليل ملف القفل في ميكروثوانٍ، وليس مئات الميلي ثانية التي يقضيها npm في تحليل package-lock.json.

السيء: لا يمكنك مراجعته في فرق. إذا كنت في فريق وأحدهم حدّث تبعية، لا يمكنك النظر في فرق ملف القفل في PR ورؤية ما تغيّر. هذا يهم أكثر مما يريد مناصرو السرعة الاعتراف به.

bash
# يمكنك تفريغ ملف القفل بصيغة قابلة للقراءة
bun bun.lockb > lockfile-dump.txt
 
# أو استخدام الإخراج النصي المدمج
bun install --yarn
# هذا يولّد yarn.lock بجانب bun.lockb

نهجي: أودع bun.lockb في المستودع وأولّد أيضاً yarn.lock أو package-lock.json كبديل قابل للقراءة. حزام وحمالات.

دعم مساحات العمل#

يدعم Bun مساحات عمل بنمط npm/yarn:

json
{
  "name": "my-monorepo",
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}
bash
# تثبيت التبعيات لجميع مساحات العمل
bun install
 
# تشغيل سكربت في مساحة عمل محددة
bun run --filter packages/shared build
 
# إضافة تبعية لمساحة عمل محددة
bun add react --filter apps/web

دعم مساحات العمل متين وتحسّن بشكل كبير. الفجوة الرئيسية مقارنة بـ pnpm هي أن حل تبعيات مساحة عمل Bun أقل صرامة — صرامة pnpm ميزة للمستودعات الأحادية لأنها تلتقط التبعيات الوهمية.

التوافق مع المشاريع الحالية#

يمكنك إسقاط bun install في أي مشروع Node.js موجود تقريباً. يقرأ package.json، يحترم .npmrc لتهيئة السجل، ويتعامل مع peerDependencies بشكل صحيح. الانتقال عادة:

bash
# الخطوة 1: حذف ملف القفل و node_modules الحالية
rm -rf node_modules package-lock.json yarn.lock pnpm-lock.yaml
 
# الخطوة 2: التثبيت باستخدام Bun
bun install
 
# الخطوة 3: التحقق من أن تطبيقك لا يزال يعمل
bun run dev
# أو: node dist/server.js  (مدير حزم Bun، بيئة تشغيل Node)

فعلت هذا في عشرات المشاريع ولم تكن هناك أي مشاكل مع مدير الحزم نفسه. المحذور الوحيد هو إذا كان خط أنابيب CI يبحث تحديداً عن package-lock.json — ستحتاج تحديثه للتعامل مع bun.lockb.

توافق Node.js#

هذا هو القسم الذي يجب أن أكون فيه الأكثر حذراً، لأن الوضع يتغير كل شهر. اعتباراً من أوائل 2026، إليك الصورة الصادقة.

ما يعمل#

الغالبية العظمى من حزم npm تعمل بدون تعديل. ينفّذ Bun معظم وحدات Node.js المدمجة:

typescript
// كل هذه تعمل كما هو متوقع في Bun
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import { Buffer } from "node:buffer";
import { EventEmitter } from "node:events";
import { Readable, Writable } from "node:stream";
import http from "node:http";
import https from "node:https";
import { URL, URLSearchParams } from "node:url";
import os from "node:os";
import child_process from "node:child_process";

كل من CommonJS وESM يعملان. require() وimport يمكنهما التعايش. TypeScript يعمل بدون أي خطوة تجميع — يزيل Bun الأنواع عند التحليل.

الأُطر التي تعمل:

  • Express — يعمل، بما في ذلك نظام الوسيط البيئي
  • Fastify — يعمل
  • Hono — يعمل (وممتاز مع Bun)
  • Next.js — يعمل مع تحفظات (المزيد عن هذا لاحقاً)
  • Prisma — يعمل
  • Drizzle ORM — يعمل
  • Socket.io — يعمل

ما لا يعمل (أو به مشاكل)#

الفجوات تميل للوقوع في فئات قليلة:

الإضافات الأصلية (node-gyp): إذا كانت حزمة تستخدم إضافات C++ مُجمّعة بـ node-gyp، قد لا تعمل مع Bun. لدى Bun نظام FFI خاص ويدعم العديد من الوحدات الأصلية، لكن التغطية ليست 100%. على سبيل المثال، bcrypt (النسخة الأصلية) واجهت مشاكل — استخدم bcryptjs بدلاً منها.

bash
# تحقق إذا كانت الحزمة تستخدم إضافات أصلية
ls node_modules/your-package/binding.gyp  # إذا كان هذا موجوداً، إنها أصلية

أجزاء Node.js الداخلية المحددة: بعض الحزم تصل لأجزاء Node.js الداخلية مثل process.binding() أو تستخدم واجهات V8 المحددة. هذه لن تعمل في Bun لأنه يعمل على JavaScriptCore.

typescript
// هذا لن يعمل في Bun — خاص بـ V8
const v8 = require("v8");
v8.serialize({ data: "test" });
 
// هذا سيعمل — استخدم مكافئ Bun أو نهج عابر لبيئات التشغيل
const encoded = new TextEncoder().encode(JSON.stringify({ data: "test" }));

خيوط العمال: يدعم Bun Web Workers وnode:worker_threads، لكن هناك حالات حدية. بعض أنماط الاستخدام المتقدمة — خاصة حول SharedArrayBuffer وAtomics — يمكن أن تتصرف بشكل مختلف.

وحدة vm: node:vm لديها دعم جزئي. إذا كان كودك أو تبعية تستخدم vm.createContext() بكثرة (بعض محركات القوالب تفعل)، اختبر بدقة.

مُتتبّع التوافق#

يحتفظ Bun بمُتتبّع توافق رسمي. تحقق منه قبل الالتزام بـ Bun لمشروع:

bash
# شغّل فحص التوافق المدمج في Bun على مشروعك
bun --bun node_modules/.bin/your-tool
 
# علامة --bun تفرض بيئة تشغيل Bun حتى لسكربتات node_modules

توصيتي: لا تفترض التوافق. شغّل مجموعة اختباراتك تحت Bun قبل أن تقرر. يستغرق خمس دقائق ويوفر ساعات من التصحيح.

bash
# فحص توافق سريع — شغّل مجموعة الاختبارات الكاملة تحت Bun
bun test  # إذا كنت تستخدم مُشغّل اختبارات bun
# أو
bun run vitest  # إذا كنت تستخدم vitest

واجهات Bun المدمجة#

هنا يصبح Bun مثيراً للاهتمام. بدلاً من مجرد إعادة تنفيذ واجهات Node.js، يوفّر Bun واجهاته الخاصة المصممة لتكون أبسط وأسرع.

Bun.serve() — خادم HTTP المدمج#

هذه الواجهة الأكثر استخداماً لديّ. نظيفة، سريعة، ودعم WebSocket مدمج مباشرة.

typescript
const server = Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);
 
    if (url.pathname === "/") {
      return new Response("مرحباً من Bun!", {
        headers: { "Content-Type": "text/plain" },
      });
    }
 
    if (url.pathname === "/api/users") {
      const users = [
        { id: 1, name: "أليس" },
        { id: 2, name: "بوب" },
      ];
      return Response.json(users);
    }
 
    return new Response("غير موجود", { status: 404 });
  },
});
 
console.log(`الخادم يعمل على http://localhost:${server.port}`);

بضعة أشياء يجب ملاحظتها:

  1. Request/Response معيارية الويب — لا واجهة مملوكة. معالج fetch يستقبل Request قياسي ويُرجع Response قياسياً. إذا كتبت Cloudflare Worker، هذا يبدو متطابقاً.
  2. Response.json() — مساعد استجابة JSON مدمج.
  3. لا حاجة لاستيرادBun.serve عام. لا require("http").

إليك مثال أكثر واقعية مع توجيه وتحليل جسم JSON ومعالجة أخطاء:

typescript
import { Database } from "bun:sqlite";
 
const db = new Database("app.db");
db.run(`
  CREATE TABLE IF NOT EXISTS todos (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    completed INTEGER DEFAULT 0,
    created_at TEXT DEFAULT (datetime('now'))
  )
`);
 
const server = Bun.serve({
  port: process.env.PORT || 3000,
 
  async fetch(req) {
    const url = new URL(req.url);
    const method = req.method;
 
    try {
      // GET /api/todos
      if (url.pathname === "/api/todos" && method === "GET") {
        const todos = db.query("SELECT * FROM todos ORDER BY created_at DESC").all();
        return Response.json(todos);
      }
 
      // POST /api/todos
      if (url.pathname === "/api/todos" && method === "POST") {
        const body = await req.json();
 
        if (!body.title || typeof body.title !== "string") {
          return Response.json({ error: "العنوان مطلوب" }, { status: 400 });
        }
 
        const stmt = db.prepare("INSERT INTO todos (title) VALUES (?) RETURNING *");
        const todo = stmt.get(body.title);
        return Response.json(todo, { status: 201 });
      }
 
      // DELETE /api/todos/:id
      const deleteMatch = url.pathname.match(/^\/api\/todos\/(\d+)$/);
      if (deleteMatch && method === "DELETE") {
        const id = parseInt(deleteMatch[1], 10);
        db.run("DELETE FROM todos WHERE id = ?", [id]);
        return new Response(null, { status: 204 });
      }
 
      return Response.json({ error: "غير موجود" }, { status: 404 });
    } catch (error) {
      console.error("خطأ في الطلب:", error);
      return Response.json({ error: "خطأ داخلي في الخادم" }, { status: 500 });
    }
  },
});
 
console.log(`الخادم يعمل على المنفذ ${server.port}`);

هذا واجهة CRUD كاملة مع SQLite في حوالي 50 سطراً. بدون Express، بدون ORM، بدون سلسلة وسيط. للواجهات الصغيرة والأدوات الداخلية، هذا إعدادي المفضل الآن.

Bun.file() و Bun.write() — إدخال/إخراج الملفات#

واجهة ملفات Bun بسيطة بشكل منعش مقارنة بـ fs.readFile():

typescript
// قراءة الملفات
const file = Bun.file("./config.json");
const text = await file.text();       // قراءة كنص
const json = await file.json();       // تحليل كـ JSON مباشرة
const bytes = await file.arrayBuffer(); // قراءة كـ ArrayBuffer
const stream = file.stream();          // قراءة كـ ReadableStream
 
// بيانات تعريف الملف
console.log(file.size);  // الحجم بالبايت
console.log(file.type);  // نوع MIME (مثلاً "application/json")
 
// كتابة الملفات
await Bun.write("./output.txt", "مرحباً بالعالم!");
await Bun.write("./data.json", JSON.stringify({ key: "value" }));
await Bun.write("./copy.png", Bun.file("./original.png"));
 
// كتابة جسم Response لملف
const response = await fetch("https://example.com/data.json");
await Bun.write("./downloaded.json", response);

واجهة Bun.file() كسولة — لا تقرأ الملف حتى تستدعي .text() أو .json() إلخ. هذا يعني يمكنك تمرير مراجع Bun.file() بدون تكبّد تكاليف I/O حتى تحتاج البيانات فعلاً.

دعم WebSocket المدمج#

WebSockets من الدرجة الأولى في Bun.serve():

typescript
const server = Bun.serve({
  port: 3000,
 
  fetch(req, server) {
    const url = new URL(req.url);
 
    if (url.pathname === "/ws") {
      const upgraded = server.upgrade(req, {
        data: {
          userId: url.searchParams.get("userId"),
          joinedAt: Date.now(),
        },
      });
 
      if (!upgraded) {
        return new Response("فشل ترقية WebSocket", { status: 400 });
      }
      return undefined;
    }
 
    return new Response("استخدم /ws لاتصالات WebSocket");
  },
 
  websocket: {
    open(ws) {
      console.log(`عميل متصل: ${ws.data.userId}`);
      ws.subscribe("chat");
    },
 
    message(ws, message) {
      // البث لجميع المشتركين
      server.publish("chat", `${ws.data.userId}: ${message}`);
    },
 
    close(ws) {
      console.log(`عميل انفصل: ${ws.data.userId}`);
      ws.unsubscribe("chat");
    },
  },
});

نمط server.publish() وws.subscribe() هو نشر/اشتراك مدمج. بدون Redis، بدون مكتبة WebSocket منفصلة. للميزات البسيطة في الوقت الحقيقي، هذا مريح بشكل لا يُصدّق.

SQLite مدمج مع bun:sqlite#

هذا فاجأني أكثر شيء. يأتي Bun مع SQLite مدمجة مباشرة في بيئة التشغيل:

typescript
import { Database } from "bun:sqlite";
 
// فتح أو إنشاء قاعدة بيانات
const db = new Database("myapp.db");
 
// وضع WAL لأداء قراءة متزامن أفضل
db.exec("PRAGMA journal_mode = WAL");
 
// إنشاء جداول
db.run(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT UNIQUE NOT NULL,
    name TEXT NOT NULL,
    created_at TEXT DEFAULT (datetime('now'))
  )
`);
 
// عبارات مُعدّة (قابلة لإعادة الاستخدام، أسرع للاستعلامات المتكررة)
const insertUser = db.prepare(
  "INSERT INTO users (email, name) VALUES ($email, $name) RETURNING *"
);
 
const findByEmail = db.prepare(
  "SELECT * FROM users WHERE email = $email"
);
 
// الاستخدام
const user = insertUser.get({
  $email: "alice@example.com",
  $name: "Alice",
});
console.log(user); // { id: 1, email: "alice@example.com", name: "Alice", ... }
 
// المعاملات
const insertMany = db.transaction((users: { email: string; name: string }[]) => {
  for (const user of users) {
    insertUser.run({ $email: user.email, $name: user.name });
  }
  return users.length;
});
 
const count = insertMany([
  { email: "bob@example.com", name: "Bob" },
  { email: "carol@example.com", name: "Carol" },
]);
console.log(`تم إدراج ${count} مستخدمين`);

هذا SQLite متزامن بأداء مكتبة C (لأنه كذلك — يُضمّن Bun مكتبة libsqlite3 مباشرة). لأدوات سطر الأوامر والتطبيقات المحلية أولاً والخدمات الصغيرة، SQLite المدمجة تعني صفر تبعيات خارجية لطبقة البيانات.

مُشغّل اختبارات Bun#

bun test هو بديل مباشر لـ Jest في معظم الحالات. يستخدم نفس واجهة describe/it/expect ويدعم معظم مُطابقات Jest.

الاستخدام الأساسي#

typescript
// math.test.ts
import { describe, it, expect } from "bun:test";
 
describe("أدوات الرياضيات", () => {
  it("يجمع الأرقام بشكل صحيح", () => {
    expect(1 + 2).toBe(3);
  });
 
  it("يتعامل مع الأعداد العشرية", () => {
    expect(0.1 + 0.2).toBeCloseTo(0.3);
  });
});
bash
# تشغيل جميع الاختبارات
bun test
 
# تشغيل ملف محدد
bun test math.test.ts
 
# تشغيل اختبارات تطابق نمطاً
bun test --test-name-pattern "يجمع الأرقام"
 
# وضع المراقبة
bun test --watch
 
# التغطية
bun test --coverage

المحاكاة#

يدعم Bun محاكاة متوافقة مع Jest:

typescript
import { describe, it, expect, mock, spyOn } from "bun:test";
import { fetchUsers } from "./api";
 
// محاكاة وحدة
mock.module("./database", () => ({
  query: mock(() => [{ id: 1, name: "Alice" }]),
}));
 
describe("fetchUsers", () => {
  it("يُرجع المستخدمين من قاعدة البيانات", async () => {
    const users = await fetchUsers();
    expect(users).toHaveLength(1);
    expect(users[0].name).toBe("Alice");
  });
});
 
// التجسس على طريقة كائن
describe("console", () => {
  it("يتتبع استدعاءات console.log", () => {
    const logSpy = spyOn(console, "log");
    console.log("رسالة اختبار");
    expect(logSpy).toHaveBeenCalledWith("رسالة اختبار");
    logSpy.mockRestore();
  });
});

bun test مقابل Vitest — مقارنتي الصادقة#

أستخدم Vitest لهذا المشروع (ومعظم مشاريعي). إليك لماذا لم أنتقل بالكامل:

حيث يفوز bun test:

  • سرعة البدء. bun test يبدأ تنفيذ الاختبارات أسرع مما يمكن لـ Vitest الانتهاء من تحميل تهيئته.
  • صفر تهيئة. لا حاجة لـ vitest.config.ts للإعدادات الأساسية.
  • TypeScript مدمج. لا خطوة تحويل.

حيث لا يزال Vitest يفوز:

  • النظام البيئي: لدى Vitest إضافات أكثر، تكامل أفضل مع IDE، ومجتمع أكبر.
  • التهيئة: نظام تهيئة Vitest أكثر مرونة. مُبلّغون مخصصون، ملفات إعداد معقدة، بيئات اختبار متعددة.
  • وضع المتصفح: يمكن لـ Vitest تشغيل الاختبارات في متصفح حقيقي. Bun لا يستطيع.
  • التوافق: بعض مكتبات الاختبار (Testing Library، MSW) تم اختبارها بشكل أكثر شمولاً مع Vitest/Jest.
  • اختبار اللقطات: كلاهما يدعمه، لكن تنفيذ Vitest أكثر نضجاً مع إخراج فرق أفضل.

لمشروع جديد بحاجات اختبار بسيطة، سأستخدم bun test. لمشروع قائم مع Testing Library وMSW ومحاكاة معقدة، سأبقى مع Vitest.

مُحزم Bun#

bun build هو مُحزم JavaScript/TypeScript سريع. ليس بديلاً لـ webpack — إنه أكثر في فئة esbuild: سريع، ذو رأي محدد، ومُركّز على الحالات الشائعة.

التحزيم الأساسي#

bash
# تحزيم نقطة دخول واحدة
bun build ./src/index.ts --outdir ./dist
 
# التحزيم لأهداف مختلفة
bun build ./src/index.ts --outdir ./dist --target browser
bun build ./src/index.ts --outdir ./dist --target bun
bun build ./src/index.ts --outdir ./dist --target node
 
# التصغير
bun build ./src/index.ts --outdir ./dist --minify
 
# توليد خرائط المصدر
bun build ./src/index.ts --outdir ./dist --sourcemap external

واجهة برمجية#

typescript
const result = await Bun.build({
  entrypoints: ["./src/index.ts", "./src/worker.ts"],
  outdir: "./dist",
  target: "browser",
  minify: {
    whitespace: true,
    identifiers: true,
    syntax: true,
  },
  splitting: true,    // تقسيم الكود
  sourcemap: "external",
  external: ["react", "react-dom"],  // لا تحزم هذه
  naming: "[dir]/[name]-[hash].[ext]",
  define: {
    "process.env.NODE_ENV": JSON.stringify("production"),
  },
});
 
if (!result.success) {
  console.error("فشل البناء:");
  for (const log of result.logs) {
    console.error(log);
  }
  process.exit(1);
}
 
for (const output of result.outputs) {
  console.log(`${output.path} — ${output.size} بايت`);
}

إزالة الكود الميت#

يدعم Bun إزالة الكود الميت (tree-shaking) لـ ESM:

typescript
// utils.ts
export function used() {
  return "سأكون في الحزمة";
}
 
export function unused() {
  return "سأُزال بإزالة الكود الميت";
}
 
// index.ts
import { used } from "./utils";
console.log(used());
bash
bun build ./src/index.ts --outdir ./dist --minify
# الدالة `unused` لن تظهر في الإخراج

أين يقصر بناء Bun#

  • لا تحزيم CSS — تحتاج أداة منفصلة لـ CSS (PostCSS، Lightning CSS، Tailwind CLI).
  • لا توليد HTML — يحزم JavaScript/TypeScript، ليس تطبيقات ويب كاملة.
  • نظام الإضافات البيئي — لدى esbuild نظام إضافات أكبر بكثير. واجهة إضافات Bun متوافقة لكن المجتمع أصغر.
  • تقسيم الكود المتقدم — لا يزال webpack وRollup يقدمان استراتيجيات تقطيع أكثر تطوراً.

لبناء مكتبة أو حزمة JS لتطبيق ويب بسيط، bun build ممتاز. لبناء تطبيقات معقدة مع وحدات CSS وتحسين الصور واستراتيجيات تقطيع مخصصة، ستظل تحتاج مُحزماً كاملاً.

ماكروز Bun#

ميزة فريدة حقاً: تنفيذ الكود في وقت التجميع عبر الماكروز.

typescript
// build-info.ts — هذا الملف يعمل في وقت البناء، ليس وقت التشغيل
export function getBuildInfo() {
  return {
    builtAt: new Date().toISOString(),
    gitSha: require("child_process")
      .execSync("git rev-parse --short HEAD")
      .toString()
      .trim(),
    nodeVersion: process.version,
  };
}
typescript
// app.ts
import { getBuildInfo } from "./build-info" with { type: "macro" };
 
// getBuildInfo() تُنفّذ في وقت التحزيم
// النتيجة تُضمّن كقيمة ثابتة
const info = getBuildInfo();
console.log(`بُني في ${info.builtAt}، commit ${info.gitSha}`);

بعد التحزيم، تُستبدل getBuildInfo() بالكائن الحرفي — لا استدعاء دالة في وقت التشغيل، لا استيراد لـ child_process. الكود عمل أثناء البناء والنتيجة أُضمنت. هذا قوي لتضمين بيانات البناء التعريفية وعلامات الميزات والتهيئة الخاصة بالبيئة.

استخدام Bun مع Next.js#

هذا السؤال الأكثر شيوعاً الذي أُسأل عنه، لذا دعني أكون محدداً جداً.

ما يعمل اليوم#

Bun كمدير حزم لـ Next.js — يعمل بشكل مثالي:

bash
# استخدم Bun لتثبيت التبعيات، ثم استخدم Node.js لتشغيل Next.js
bun install
bun run dev    # هذا فعلاً يشغّل سكربت "dev" عبر Node.js افتراضياً
bun run build
bun run start

هذا ما أفعله لكل مشروع Next.js. أمر bun run <script> يقرأ قسم scripts في package.json وينفّذه. افتراضياً، يستخدم Node.js النظام للتنفيذ الفعلي. تحصل على تثبيت حزم Bun السريع بدون تغيير بيئة التشغيل.

بيئة تشغيل Bun لتطوير Next.js:

bash
# فرض تشغيل Next.js تحت بيئة تشغيل Bun
bun --bun run dev

هذا يعمل للتطوير في معظم الحالات. علامة --bun تخبر Bun باستخدام بيئة تشغيله بدلاً من التفويض لـ Node.js. استبدال الوحدات الحي يعمل. مسارات API تعمل. مكونات الخادم تعمل.

ما لا يزال تجريبياً#

بيئة تشغيل Bun لبناء Next.js الإنتاجي:

bash
# البناء ببيئة تشغيل Bun
bun --bun run build
 
# بدء خادم الإنتاج ببيئة تشغيل Bun
bun --bun run start

هذا يعمل للعديد من المشاريع لكنني واجهت حالات حدية:

  1. بعض سلوكيات الوسيط تختلف — إذا كنت تستخدم وسيط Next.js يعتمد على واجهات Node.js المحددة، قد تواجه مشاكل توافق.
  2. تحسين الصور — خط أنابيب تحسين صور Next.js يستخدم sharp، وهو إضافة أصلية. يعمل مع Bun، لكنني رأيت مشاكل عرضية.
  3. ISR (التجديد الساكن التدريجي) — يعمل، لكنني لم أختبره تحت ضغط مع Bun في الإنتاج.

توصيتي لـ Next.js#

استخدم Bun كمدير حزم. استخدم Node.js كبيئة تشغيل. هذا يعطيك فوائد سرعة bun install بدون أي خطر توافق.

json
{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start"
  }
}
bash
# سير العمل اليومي
bun install      # تثبيت حزم سريع
bun run dev      # يشغّل "next dev" عبر Node.js
bun run build    # يشغّل "next build" عبر Node.js

عندما يصل توافق Bun مع Node.js لـ 100% لاستخدام Next.js الداخلي (إنه قريب، لكنه لم يصل بعد)، سأنتقل. حتى ذلك الحين، مدير الحزم وحده يوفر لي وقتاً كافياً لتبرير التثبيت.

Docker مع Bun#

صورة Docker الرسمية لـ Bun مصانة جيداً وجاهزة للإنتاج.

Dockerfile أساسي#

dockerfile
FROM oven/bun:1 AS base
WORKDIR /app
 
# تثبيت التبعيات
FROM base AS deps
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
 
# البناء (إذا لزم الأمر)
FROM base AS build
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
 
# الإنتاج
FROM base AS production
WORKDIR /app
 
# لا تشغّل كـ root
RUN addgroup --system --gid 1001 appgroup && \
    adduser --system --uid 1001 appuser
USER appuser
 
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./
 
EXPOSE 3000
CMD ["bun", "run", "dist/server.js"]

بناء متعدد المراحل لصورة أصغر#

dockerfile
# مرحلة البناء: صورة Bun كاملة مع جميع التبعيات
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build ./src/index.ts --target bun --outdir ./dist --minify
 
# مرحلة التشغيل: صورة أساسية أصغر
FROM oven/bun:1-slim AS runtime
WORKDIR /app
 
RUN addgroup --system --gid 1001 appgroup && \
    adduser --system --uid 1001 appuser
USER appuser
 
COPY --from=builder /app/dist ./dist
 
EXPOSE 3000
CMD ["bun", "run", "dist/index.js"]

التجميع لملف ثنائي واحد#

هذه واحدة من ميزات Bun القاتلة للنشر:

bash
# تجميع تطبيقك لملف تنفيذي واحد
bun build --compile ./src/server.ts --outfile server
 
# الإخراج ملف ثنائي مستقل — لا حاجة لـ Bun أو Node.js لتشغيله
./server
dockerfile
# صورة Docker فائقة الحد الأدنى باستخدام ملف ثنائي مُجمّع
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build --compile ./src/server.ts --outfile server
 
# الصورة النهائية — فقط الملف الثنائي
FROM debian:bookworm-slim
WORKDIR /app
 
RUN addgroup --system --gid 1001 appgroup && \
    adduser --system --uid 1001 appuser
USER appuser
 
COPY --from=builder /app/server ./server
 
EXPOSE 3000
CMD ["./server"]

الملف الثنائي المُجمّع عادة 50-90 ميجابايت (يُضمّن بيئة تشغيل Bun). هذا أكبر من ملف Go الثنائي لكن أصغر بكثير من تثبيت Node.js كامل مع node_modules. للنشر في حاويات، الطبيعة المستقلة تبسيط كبير.

مقارنة الأحجام#

bash
# صورة Node.js
docker images | grep node
# node:20-slim    ~180MB
 
# صورة Bun
docker images | grep bun
# oven/bun:1-slim  ~130MB
 
# ملف ثنائي مُجمّع على debian:bookworm-slim
# ~80MB قاعدة + ~70MB ملف ثنائي = ~150MB إجمالي
 
# مقابل Alpine مع Node.js
# node:20-alpine   ~130MB + node_modules

نهج الملف الثنائي يزيل node_modules تماماً من الصورة النهائية. لا npm install في الإنتاج. لا سطح هجوم سلسلة توريد من مئات الحزم. مجرد ملف واحد.

أنماط الترحيل#

إذا كنت تفكر في الانتقال لـ Bun، إليك المسار التدريجي الذي أوصي به:

المرحلة 1: مدير الحزم فقط (صفر مخاطر)#

bash
# استبدل npm/yarn/pnpm بـ bun install
# غيّر خط أنابيب CI:
# قبل:
npm ci
 
# بعد:
bun install --frozen-lockfile

لا تغييرات في الكود. لا تغييرات في بيئة التشغيل. مجرد تثبيت أسرع. إذا انكسر شيء (لن ينكسر)، ارجع بحذف bun.lockb وتشغيل npm install.

المرحلة 2: السكربتات والأدوات#

bash
# استخدم bun لتشغيل سكربتات التطوير
bun run dev
bun run lint
bun run format
 
# استخدم bun للسكربتات لمرة واحدة
bun run scripts/seed-database.ts
bun run scripts/migrate.ts

لا تزال تستخدم Node.js كبيئة تشغيل لتطبيقك الفعلي. لكن السكربتات تستفيد من بدء Bun الأسرع ودعم TypeScript الأصلي.

المرحلة 3: مُشغّل الاختبارات (مخاطر متوسطة)#

bash
# استبدل vitest/jest بـ bun test لمجموعات الاختبار البسيطة
bun test
 
# أبقِ vitest لإعدادات الاختبار المعقدة
# (Testing Library، MSW، بيئات مخصصة)

شغّل مجموعة اختباراتك الكاملة تحت bun test. إذا مرّ كل شيء، ألغيت تبعية تطوير. إذا فشلت بعض الاختبارات بسبب التوافق، أبقِ Vitest لها واستخدم bun test للباقي.

المرحلة 4: بيئة التشغيل لخدمات جديدة (مخاطر محسوبة)#

typescript
// خدمات مصغّرة أو واجهات API جديدة — ابدأ مع Bun من اليوم الأول
Bun.serve({
  port: 3000,
  fetch(req) {
    // خدمتك الجديدة هنا
  },
});

لا تُهاجر خدمات Node.js الحالية لبيئة تشغيل Bun. بدلاً من ذلك، اكتب خدمات جديدة مع Bun من البداية. هذا يحدّ نطاق انفجارك.

المرحلة 5: ترحيل بيئة التشغيل (متقدم)#

bash
# فقط بعد اختبار شامل:
# استبدل node بـ bun للخدمات الحالية
# قبل:
node dist/server.js
 
# بعد:
bun dist/server.js

أوصي بهذا فقط للخدمات ذات تغطية اختبارية ممتازة. شغّل اختبارات الحمل تحت Bun قبل تبديل الإنتاج.

متغيرات البيئة والتهيئة#

يتعامل Bun مع ملفات .env تلقائياً — لا حاجة لحزمة dotenv:

bash
# .env
DATABASE_URL=postgresql://localhost:5432/myapp
API_KEY=sk-test-12345
PORT=3000
typescript
// هذه متاحة بدون أي استيراد
console.log(process.env.DATABASE_URL);
console.log(process.env.API_KEY);
console.log(Bun.env.PORT); // بديل خاص بـ Bun

يُحمّل Bun .env و.env.local و.env.production إلخ تلقائياً، متبعاً نفس اتفاقية Next.js. تبعية أقل في package.json.

معالجة الأخطاء والتصحيح#

تحسّن إخراج أخطاء Bun بشكل كبير، لكنه لا يزال ليس مصقولاً مثل Node.js في بعض الحالات:

bash
# مُصحح Bun — يعمل مع VS Code
bun --inspect run server.ts
 
# inspect-brk في Bun — توقف عند السطر الأول
bun --inspect-brk run server.ts

لـ VS Code، أضف هذا لـ .vscode/launch.json:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "bun",
      "request": "launch",
      "name": "Debug Bun",
      "program": "${workspaceFolder}/src/server.ts",
      "cwd": "${workspaceFolder}",
      "stopOnEntry": false,
      "watchMode": false
    }
  ]
}

تتبعات المكدس في Bun دقيقة بشكل عام وتتضمن خرائط مصدر لـ TypeScript. فجوة التصحيح الرئيسية هي أن بعض أدوات تصحيح Node.js المحددة (مثل ndb أو clinic.js) لا تعمل مع Bun.

اعتبارات أمنية#

بعض الأشياء للتفكير فيها إذا كنت تقيّم Bun للإنتاج:

النضج: Node.js في الإنتاج منذ 15+ سنة. كل حالة حدية في تحليل HTTP ومعالجة TLS ومعالجة التدفقات تم إيجادها وإصلاحها. Bun أحدث. مختبر جيداً، لكن سطح الأخطاء غير المكتشفة أكبر.

تصحيحات الأمان: فريق Bun يشحن تحديثات بشكل متكرر، لكن فريق أمان Node.js لديه عملية CVE رسمية وإفصاح منسّق وسجل حافل أطول. للتطبيقات ذات الحساسية الأمنية، هذا يهم.

سلسلة التوريد: ميزات Bun المدمجة (SQLite، خادم HTTP، WebSockets) تعني تبعيات npm أقل. تبعيات أقل تعني سطح هجوم سلسلة توريد أصغر. هذه ميزة أمنية حقيقية.

bash
# مقارنة أعداد التبعيات
# مشروع Express + SQLite + WebSocket نموذجي:
npm ls --all | wc -l
# ~340 حزمة
 
# نفس الوظائف مع ميزات Bun المدمجة:
bun pm ls --all | wc -l
# ~12 حزمة (فقط كود تطبيقك)

هذا انخفاض ذو معنى في عدد الحزم التي تأتمنها على حمل عمل الإنتاج.

ضبط الأداء#

بعض نصائح الأداء الخاصة بـ Bun:

typescript
// استخدم خيارات Bun.serve() لضبط الإنتاج
Bun.serve({
  port: 3000,
 
  // زيادة الحد الأقصى لحجم جسم الطلب (الافتراضي 128MB)
  maxRequestBodySize: 1024 * 1024 * 50, // 50MB
 
  // تفعيل وضع التطوير لصفحات خطأ أفضل
  development: process.env.NODE_ENV !== "production",
 
  // إعادة استخدام المنفذ (مفيد لإعادة التشغيل بدون توقف)
  reusePort: true,
 
  fetch(req) {
    return new Response("OK");
  },
});
typescript
// استخدم Bun.Transpiler لتحويل الكود في وقت التشغيل
const transpiler = new Bun.Transpiler({
  loader: "tsx",
  target: "browser",
});
 
const code = transpiler.transformSync(`
  const App: React.FC = () => <div>مرحباً</div>;
  export default App;
`);
bash
# علامات استخدام ذاكرة Bun
bun --smol run server.ts  # تقليل أثر الذاكرة (أبطأ قليلاً)
 
# تعيين الحد الأقصى لحجم الكومة
BUN_JSC_forceRAMSize=512000000 bun run server.ts  # ~512MB حد

المحاذير الشائعة#

بعد سنة من استخدام Bun، إليك الأشياء التي أوقعتني:

1. سلوك Fetch العام يختلف#

typescript
// fetch في Node.js 18+ و fetch في Bun مختلفتان قليلاً
// في كيفية التعامل مع بعض الرؤوس وعمليات إعادة التوجيه
 
// Bun يتبع إعادة التوجيه افتراضياً (مثل المتصفحات)
// fetch في Node.js أيضاً يتبع إعادة التوجيه، لكن السلوك
// مع بعض رموز الحالة (303، 307، 308) يمكن أن يختلف
 
const response = await fetch("https://api.example.com/data", {
  redirect: "manual", // كن صريحاً في التعامل مع إعادة التوجيه
});

2. سلوك خروج العملية#

typescript
// Bun يخرج عندما تكون حلقة الأحداث فارغة
// Node.js أحياناً يستمر في العمل بسبب مقابض متبقية
 
// إذا خرج سكربت Bun بشكل غير متوقع، شيء ما لا
// يحافظ على حلقة الأحداث حية
 
// هذا سيخرج فوراً في Bun:
setTimeout(() => {}, 0);
 
// هذا سيستمر في العمل:
setTimeout(() => {}, 1000);
// (Bun يخرج بعد أن يُطلق المؤقت)

3. تهيئة TypeScript#

typescript
// لدى Bun إعدادات tsconfig الافتراضية الخاصة
// إذا كنت تشارك مشروعاً بين Bun و Node.js،
// كن صريحاً في tsconfig.json:
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "types": ["bun-types"]  // أضف تعريفات أنواع Bun
  }
}
bash
# تثبيت أنواع Bun
bun add -d @types/bun

4. إعادة التحميل الساخنة في التطوير#

bash
# لدى Bun وضع مراقبة مدمج
bun --watch run server.ts
 
# هذا يعيد تشغيل العملية عند تغيّر الملفات
# ليس HMR (استبدال الوحدات الساخنة) — إنه إعادة تشغيل كاملة
# لكن لأن Bun يبدأ بسرعة كبيرة، يبدو فورياً

5. ملف تهيئة bunfig.toml#

toml
# bunfig.toml — ملف تهيئة Bun (اختياري)
 
[install]
# استخدام سجل خاص
registry = "https://npm.mycompany.com"
 
# سجلات ذات نطاق
[install.scopes]
"@mycompany" = "https://npm.mycompany.com"
 
[test]
# تهيئة الاختبارات
coverage = true
coverageReporter = ["text", "lcov"]
 
[run]
# الصدفة لاستخدامها مع bun run
shell = "bash"

حكمي#

بعد سنة من الاستخدام الإنتاجي، إليك أين استقريت:

أين أستخدم Bun اليوم#

مدير حزم لجميع المشاريع — بما في ذلك مدونة Next.js هذه. bun install أسرع، والتوافق مثالي تقريباً. لا أرى سبباً لاستخدام npm أو yarn بعد الآن. pnpm هو البديل الوحيد الذي سأفكر فيه (لحل التبعيات الصارم في المستودعات الأحادية).

بيئة تشغيل للسكربتات وأدوات سطر الأوامر — أي ملف TypeScript أحتاج تشغيله مرة، أشغّله بـ bun. لا خطوة تجميع. بدء سريع. تحميل .env مدمج. حلّ محل ts-node وtsx في سير عملي تماماً.

بيئة تشغيل لواجهات API الصغيرة والأدوات الداخليةBun.serve() + bun:sqlite مجموعة إنتاجية لا تصدق للأدوات الداخلية ومعالجات webhooks والخدمات الصغيرة. نموذج نشر "ملف ثنائي واحد، بدون تبعيات" مقنع.

مُشغّل اختبارات للمشاريع البسيطة — للمشاريع ذات احتياجات الاختبار المباشرة، bun test سريع ولا يتطلب تهيئة.

أين أتمسك بـ Node.js#

إنتاج Next.js — ليس لأن Bun لا يعمل، بل لأن نسبة المخاطرة للمكافأة لا تبررها بعد. Next.js إطار معقد بالعديد من نقاط التكامل. أريد بيئة التشغيل الأكثر اختباراً في المعارك تحتها.

خدمات الإنتاج الحرجة — خوادم API الرئيسية تعمل على Node.js خلف PM2. نظام المراقبة البيئي، أدوات التصحيح، المعرفة التشغيلية — كلها Node.js. Bun سيصل هناك، لكنه لم يصل بعد.

أي شيء بإضافات أصلية — إذا كانت سلسلة التبعيات تتضمن إضافات C++ أصلية، لا أحاول حتى مع Bun. لا يستحق تصحيح مشاكل التوافق.

الفرق غير المألوفة مع Bun — تقديم Bun كبيئة تشغيل لفريق لم يستخدمه قط يضيف عبئاً معرفياً. كمدير حزم، حسناً. كبيئة تشغيل، انتظر حتى يكون الفريق جاهزاً.

ما أراقبه#

مُتتبّع توافق Bun — عندما يصل لـ 100% لواجهات Node.js التي أهتم بها، سأعيد التقييم.

دعم الأُطر — Next.js وRemix وSvelteKit كلها بمستويات مختلفة من دعم Bun. عندما يدعم أحدها Bun رسمياً كبيئة تشغيل إنتاجية، هذه إشارة.

اعتماد المؤسسات — عندما تشغّل شركات بـ SLAs حقيقية Bun في الإنتاج وتكتب عنه، يُجاب سؤال النضج.

خط إصدار 1.2+ — Bun يتحرك بسرعة. ميزات تصل كل أسبوع. Bun الذي أستخدمه اليوم أفضل بشكل ملموس من Bun الذي جرّبته قبل سنة.

الخلاصة#

Bun ليس رصاصة فضية. لن يجعل تطبيقاً بطيئاً سريعاً ولن يجعل واجهة API مصممة بشكل سيء مصممة بشكل جيد. لكنه تحسين حقيقي في تجربة المطور لنظام JavaScript البيئي.

الشيء الذي أقدّره أكثر في Bun ليس أي ميزة بمفردها. إنه تقليل تعقيد سلسلة الأدوات. ملف ثنائي واحد يُثبّت الحزم ويشغّل TypeScript ويحزم الكود ويشغّل الاختبارات. لا tsconfig.json للسكربتات. لا Babel. لا تهيئة مُشغّل اختبارات منفصلة. فقط bun run your-file.ts ويعمل.

النصيحة العملية: ابدأ بـ bun install. صفر مخاطر، فائدة فورية. ثم جرّب bun run للسكربتات. ثم قيّم الباقي بناءً على احتياجاتك المحددة. لست مضطراً للانتقال كلياً. Bun يعمل بشكل ممتاز كبديل جزئي، وهذا ربما كيف يجب على معظم الناس استخدامه اليوم.

مشهد بيئات تشغيل JavaScript أفضل مع وجود Bun فيه. المنافسة تجعل Node.js أفضل أيضاً — Node.js 22+ أصبحت أسرع بشكل ملحوظ، جزئياً استجابة لضغط Bun. الجميع يفوز.

مقالات ذات صلة