لو موقعك بيحمّل 4MB لكل صفحة منها 3.2MB صور JPEG رفعها مستخدم من موبايله بدون أي معالجة، Lighthouse هيقعّد score الـ Performance تحت 40 وGoogle هيخفّض ترتيبك في نتايج البحث. هتبني هنا خدمة Node.js صغيرة تستقبل أي صورة، تنتج منها 4 أحجام × 3 صيغ (AVIF + WebP + JPEG) في 200 ميلي ثانية، وتوفّر 78% من الحجم في المتوسط.
المشكلة باختصار
الصورة اللي بتطلع من iPhone 15 حجمها 3.5MB ودقتها 4032×3024. لما تتعرض على شاشة موبايل عرضها 390 بكسل، 90% من البيانات اللي اتنزلت معاش لها لزمة. ضرب ده في 50 ألف زائر يوميًا، النتيجة 175GB bandwidth زيادة في الشهر بدون داعي.
الحل المتداول إنك ترفع الصورة لخدمة زي Cloudinary أو Imgix بسعر يبدأ من 89 دولار شهريًا. الحل التاني إنك تكتب 30 سطر Node.js يعملوا نفس الـ optimization tier-1 محليًا بـ Sharp.
ليه Sharp بالظبط، مش ImageMagick
قبل ما نكتب أي كود، نفهم الأدوات. ImageMagick هي الـ standard من سنة 1990 وفيها كل feature ممكن تحتاجه، لكنها بطيئة ومستهلكة ذاكرة. Sharp مكتبة Node.js مبنية فوق libvips الـ C library، وبتشتغل أسرع 4 إلى 5 مرات من ImageMagick على نفس الصورة.
مثال بسيط الأول
تخيل عندك مصنع بيرسم لوحات. ImageMagick زي رسّام بيرسم اللوحة كاملة في الذاكرة، ولما يحتاج جزء صغير منها بيلف على اللوحة كلها. libvips بترسم اللوحة على شكل شرايط أفقية صغيرة (strips)، تشتغل على شريط واحد في الذاكرة ساعة المعالجة، وترميه قبل ما تجيب اللي بعده. النتيجة: لو الصورة 50 ميجابيكسل، ImageMagick بياخد 1.2GB RAM، Sharp بياخد 80MB.
التعريف العلمي
Sharp بتستخدم تقنية اسمها streaming demand-driven processing. الـ pipeline بيتبني كرسم بياني (DAG) من العمليات (resize, format, compress)، وبيتنفّذ بطريقة lazy: العملية بتطلب البيانات من اللي قبلها بس لما تحتاجها فعلاً. ده بيخلّي الذاكرة المستخدمة دالة في عرض الصورة، مش في حجمها الكلي. libvips بتاخد فايدة Multi-threading افتراضيًا وبتوزّع المعالجة على كل النوايا (cores) بدون كود إضافي منك.
بنبني الخدمة في 7 خطوات
- اعمل مشروع جديد —
mkdir img-svc && cd img-svc && npm init -y - ثبّت الـ dependencies —
npm i sharp@0.34 fastify@5 @fastify/multipart pino - اكتب الـ pipeline الأساسي — دالة واحدة بتاخد buffer وترجّع 12 ملف
- ابنِ endpoint — POST /optimize بيستقبل multipart/form-data
- ضيف caching layer — hash للصورة الأصلية مفتاح، النواتج قيمة