TuBrief
구독 채널
비디오
커뮤니티

تشغيل خادم وسائط باستخدام ميزات Bun المدمجة بدلاً من Sharp في Node.js

TuBrief 편집팀
2026년 8월 22일
0
Computing/Software

원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.

العربية한국어Español中文FrançaisBahasa Indonesiaहिन्दीPortuguês日本語EnglishDeutschРусский

관련 영상

Bun.Image يجعل خط معالجة الصور بالكامل شيئاً من الماضي4:34

Bun.Image يجعل خط معالجة الصور بالكامل شيئاً من الماضي

Better Stack

커뮤니티의 다른 글

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

2026년 9월 13일

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

2026년 9월 13일

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

2026년 9월 13일

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

2026년 9월 13일

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

2026년 9월 12일

Apple Won the AI Race

2026년 9월 12일

댓글 (0)

Log in to leave a comment

아직 작성된 글이 없습니다

© 2026 . All rights reserved.

TuBrief
구독 채널
비디오
커뮤니티
로그인

تشغيل خادم وسائط باستخدام ميزات Bun المدمجة بدلاً من Sharp في Node.js

في بعض الأحيان، قد تجد أن صورة Docker التي تعمل بشكل جيد محلياً تواجه خطأ في البناء على خادم النشر. إذا بحثت عن السبب، ستجد أن الجاني في الغالب هو مكتبة sharp ومكتبة libvips اللتين تستخدمان ربط C++ الأصلي. وإذا نظرت إلى رسوم اشتراك Cloudinary أو Imgix التي تقتطع عشرات الدولارات شهرياً، فستشعر بالتساؤل عن سبب تحمل كل هذا العناء لمجرد إضافة ميزة معالجة صور بسيطة.

فيما يلي ملخص لطريقة الانتقال العملية لإنشاء مسار معالجة صور باستخدام الميزات المدمجة لبيئة تشغيل Bun الفردية وحدها، دون خطوات تجميع C++ الخارجية، مما يقلل من حجم الحاوية ويوفر التكاليف السحابية.

التخلص من روابط C++ التي تتسبب في فشل بناء حاوية Alpine

عند استخدام sharp في بيئة Node.js، فإنه يتصل ديناميكياً بمكتبات C الخاصة بنظام التشغيل عبر طبقة node-gyp و N-API. وعند محاولة إنشاء صورة Docker بناءً على نظام Alpine Linux خفيف الوزن، يحدث خطأ بسبب عدم التوافق بين مكتبات C الخاصة بـ glibc و musl، مما يجبر node-gyp rebuild على العمل داخل الحاوية.

خلال هذه العملية، تدخل أدوات التجميع مثل GCC و Python و make بالكامل داخل الصورة. وغالباً ما يتسبب ذلك في انهيار الكود الذي يعمل بسلاسة محلياً (على أجهزة macOS ARM64) بمجرد رفعها إلى خادم النشر (Linux x86_64) مع ظهور خطأ تجزئة الذاكرة (SIGSEGV).

بينما تضم بيئة Bun ترميزات JPEG و PNG و WebP مباشرة داخل ثنائيات بيئة التشغيل (Binary)، ولا تحتاج إلى تثبيت أي مترجمات خارجية أو حزم نظام تشغيل مثل libvips بشكل منفصل.

مقارنة العناصر Node.js (Sharp + libvips) Bun الأصلي (Bun.Image)
تبعيات ربط C++ ضرورية مثل node-gyp و N-API غير موجودة (مدمجة في ثنائيات التشغيل)
أدوات البناء تتطلب GCC و Python و make غير مطلوبة
حجم حزمة النشر مئات الميجابايت متضمنة الأدوات مخفض إلى مستوى صورة الأساس
أخطاء وقت التشغيل يحدث SIGSEGV عند عدم تطابق glibc/musl يتم تجنبها عبر الربط الثابت المدمج

استبدال كود Sharp بـ Bun.Image بنسبة 1 إلى 1

تدعم واجهة Bun.Image التسلسل (Chaining)، مما يتيح لك نقل كود sharp الحالي تقريباً كما هو، وهي تتعامل مع Uint8Array مباشرة دون نسخ الذاكرة.

`typescript
// الكود القديم المبني على sharp
import sharp from "sharp";

export async function processImageSharp(inputBuffer: Buffer): Promise {
const image = sharp(inputBuffer);
const metadata = await image.metadata();

if (!metadata.width || metadata.width > 2000) {
return await image
.resize(1024, 1024, { fit: "inside", withoutEnlargement: true })
.rotate(90)
.webp({ quality: 85 })
.toBuffer();
}
return inputBuffer;
}

`

`typescript
// الكود المحول إلى Bun.Image
export async function processImageBun(inputBytes: Uint8Array): Promise {
// قراءة الترويسة بسرعة للتحقق من الحجم دون فك ضغط الصورة بأكملها
const meta = await new Bun.Image(inputBytes).metadata();

if (!meta.width || meta.width > 2000) {
return await new Bun.Image(inputBytes)
.resize(1024, 1024, { fit: "inside", withoutEnlargement: true })
.rotate(90)
.webp({ quality: 85 })
.bytes();
}
return inputBytes;
}

`

تقوم طريقة metadata() بتحليل منطقة الترويسة فقط دون فك تشفير الصورة بالكامل، مما يقلل من هدر وحدة المعالجة المركزية عند التعاملกับ الصور الأصلية الكبيرة.

طرق التعامل مع واجهات برمجة التطبيقات لكل مهمة:

  • يتم إنشاء الكائنات باستخدام new Bun.Image(bytes) أو Bun.file(path).image() بدلاً من sharp(buf).
  • يتم الحفاظ على سلاسل تحويل الصيغ والضغط مثل .webp({ quality: 85 }) كما هي.
  • يتم استخراج الملف الثنائى النهائي باستدعاء .bytes() بدلاً من .toBuffer() لإرجاع Uint8Array.
  • يتم إنشاء صور التشويش (Blur) لتحميل واجهة المستخدم باستخدام طريقة .placeholder() المدمجة دون الحاجة لمكتبة خارجية.

خطوات الانتقال بسيطة: قم بإزالة sharp و @types/sharp من package.json، ثم قم بتغيير تنسيق مخرجات وظائف الأدوات المساعدة إلى bytes()، وقم بإجراء التحقق من الوظائف باستخدام bun test.

إنشاء ذاكرة تخزين مؤقت للصور المصغرة باستخدام قاعدة بيانات SQLite المدمجة في Bun

ترتفع تكاليف الخدمات السحابية للصور مثل Cloudinary بسرعة حتى مع زيادة طفيفة في حركة المرور. في مرحلة تطوير الخدمات الفردية، يمكنك إنشاء خادم تخزين مؤقت لإعادة التحجيم الخاص بك وبكفاءة عالية باستخدام مزيج من bun:sqlite و Bun.serve فقط.

`typescript
import { Database } from "bun:sqlite";

const db = new Database("image_cache.sqlite");
// تطبيق وضع WAL لتحسين أداء القراءة والكتابة المتزامنة
db.exec("PRAGMA journal_mode = WAL;");
db.exec( CREATE TABLE IF NOT EXISTS image_cache ( key TEXT PRIMARY KEY, data BLOB NOT NULL, placeholder TEXT NOT NULL, mime_type TEXT NOT NULL, created_at INTEGER NOT NULL ));

const selectQuery = db.query("SELECT data, mime_type FROM image_cache WHERE key = ?");
const insertQuery = db.query( INSERT OR REPLACE INTO image_cache (key, data, placeholder, mime_type, created_at) VALUES (?, ?, ?, ?, ?));

export async function getOrGenerateThumbnail(
originalBytes: Uint8Array,
cacheKey: string,
width: number = 300
): Promise<{ bytes: Uint8Array; mimeType: string }> {
const cached = selectQuery.get(cacheKey) as { data: Uint8Array; mime_type: string } | null;
if (cached) {
return { bytes: cached.data, mimeType: cached.mime_type };
}

const imagePipeline = new Bun.Image(originalBytes);
const transformedBytes = await imagePipeline.resize(width).webp({ quality: 80 }).bytes();
const placeholder = await imagePipeline.placeholder();

insertQuery.run(cacheKey, transformedBytes, placeholder, "image/webp", Date.now());

return { bytes: transformedBytes, mimeType: "image/webp" };
}

`

`typescript
// نقطة نهاية خدمة الوسائط
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);

if (url.pathname.startsWith("/images/")) {
  const imageId = url.pathname.replace("/images/", "");
  const width = parseInt(url.searchParams.get("w") || "300", 10);
  const cacheKey = `${imageId}_w${width}`;

  const originalFile = Bun.file(`./uploads/${imageId}`);
  if (!(await originalFile.exists())) {
    return new Response("Image Not Found", { status: 404 });
  }

  const originalBytes = await originalFile.bytes();
  const { bytes, mimeType } = await getOrGenerateThumbnail(originalBytes, cacheKey, width);

  return new Response(bytes, {
    headers: {
      "Content-Type": mimeType,
      "Cache-Control": "public, max-age=31536000, immutable",
    },
  });
}

return new Response("Not Found", { status: 404 });

},
});

`

يتم تمرير الطلبات الواردة لأول مرة فقط بعملية إعادة التحجيم وتخزينها في SQLite على هيئة BLOB، بينما يتم تقديم الطلبات اللاحقة مباشرة من ذاكرة التخزين المؤقت لقاعدة البيانات. وإذا قمت بضبط رأس الاستجابة Cache-Control لمدة طويلة، سيعمل التخزين المؤقت على مستوى المتصفح وش شبكة توصيل المحتوى (CDN) أيضاً.

منع حظر حلقة الأحداث الرئيسية باستخدام خيوط العمل (Worker Threads)

تعتبر عمليات ترميز وفك ترميز الصور من المهام التي تستهلك وحدة المعالجة المركزية بشكل كبير. وعندما تتدفق طلبات الرفع، يتوقف استجابة الخادم بالكامل إذا تم تحويل الصور داخل حلقة الأحداث الرئيسية. وهذا هو سبب توقف خادم API بالكامل مع ارتفاع وقت الاستجابة P99 إلى مئات الملي ثانية.

يجب استخدام واجهة Worker الخاصة بـ Bun لنقل مهام معالجة الصور إلى خيوط الخلفية لضمان استمرار عمل الحلقة الرئيسية.

`typescript
// imageWorker.ts
declare var self: Worker;

interface ResizeTask {
id: string;
buffer: ArrayBuffer;
width: number;
}

self.onmessage = async (event: MessageEvent) => {
const { id, buffer, width } = event.data;

try {
const inputBytes = new Uint8Array(buffer);
const processedBytes = await new Bun.Image(inputBytes)
.resize(width)
.webp({ quality: 80 })
.bytes();

self.postMessage(
  { id, success: true, buffer: processedBytes.buffer },
  [processedBytes.buffer] as any
);

} catch (error) {
self.postMessage({ id, success: false, error: (error as Error).message });
}
};

`

`typescript
// server.ts
const worker = new Worker("./imageWorker.ts");
const pendingTasks = new Map<string, (buf: ArrayBuffer) => void>();

worker.onmessage = (event) => {
const { id, success, buffer, error } = event.data;
const resolve = pendingTasks.get(id);

if (resolve && success) {
resolve(buffer);
pendingTasks.delete(id);
} else if (!success) {
console.error(فشل المهمة (${id}):, error);
pendingTasks.delete(id);
}
};

export function dispatchImageJob(id: string, buffer: ArrayBuffer, width: number): Promise {
return new Promise((resolve) => {
pendingTasks.set(id, resolve);
// نقل الملكية دون نسخ الذاكرة باستخدام كائن Transferable
worker.postMessage({ id, buffer, width }, [buffer]);
});
}

`

باستخدام Transferable ArrayBuffer، لا توجد تكلفة لنسخ الذاكرة حتى عند تبادل مخازن مؤقتة بحجم عدة ميجابايت بين الخيوط. وحتى مع تكدس الطلبات ذات الحجم الكبير، يقوم الخيار الرئيسي بإرجاع استجابة 202 Accepted ومعالجة طلبات API الأخرى دون تأخير.

ملاحظات تشغيلية يجب التحقق منها قبل النشر

يجب مراجعة بعض الاختلافات التي قد تحدث بين بيئة التطوير المحلية وبيئة حاوية النشر مسبقاً:

  • التحقق من نطاق دعم الترميزات: يدعم Bun.Image تنسيقات JPEG و PNG و WebP و GIF و BMP بشكل مشترك عبر جميع بيئات Linux و macOS و Windows. بينما قد يختلف دعم HEIC أو AVIF اعتماداً على نظام تشغيل الخادم. من الآمن تثبيت تنسيق الإخراج الافتراضي على WebP أو JPEG في بيئة إنتاج Linux.
  • تنظيف ملفات Docker: قم بإزالة عبارات تثبيت حزم الترجمة مثل python3 و make و g++ و libvips-dev تماماً من ملف Dockerfile. سيؤدي ذلك إلى تسريع وقت البناء وجعل صورة الحاوية أخف بكثير.
  • صلاحيات الدليل المؤقت: في حال قفل نظام ملفات الحاوية على وضع القراءة فقط (Read-Only) لأسباب تتعلق بالأمان، يجب التأكد من فتح صلاحيات الكتابة على الدليل /tmp الذي يستخدمه المخزن المؤقت لفك التشفير الداخلي لـ Bun حتى لا تتعطل العملية.
  • تفعيل وضع SQLite WAL: إذا لم يتم تنفيذ PRAGMA journal_mode = WAL; فور إنشاء ملف SQLite، ستواجه أخطاء قفل قاعدة البيانات عند تكدس طلبات القراءة والكتابة المتزامنة.

يؤدي التخلص من التبعيات الخارجية غير الضرورية إلى تقليل احتمالية فشل عملية البناء. فقط من خلال الاستفادة الفعالة من الأدوات المدمجة في بيئة التشغيل الفردية، يمكنك تقليل تعقيد تشغيل الخدمة وتكاليف صيانة البنية التحتية بشكل كبير.