استخدام صور WebP لتسريع مواقع الويب

نشره عبد اللطيف ايمش في
استخدام صور WebP لتسريع مواقع الويب

تمهيد

صيغة WebP هي صيغة صور حديثة مفتوحة المصدر ومطوَّرة من Google في عام 2010 بناءً على صيغة الفيديو VP8. منذ ذاك الحين، ازداد عدد مواقع الويب (وتطبيقات الهاتف) التي تستخدم صيغة WebP ازديادًا كبيرًا. يدعم متصفحا Google Chrome و Opera صيغة WebP دون إضافات، وهذان المتصفحات مسؤولان عن عرض 74% تقريبًا من صفحات الويب، مما يضمن أنَّ عددًا كبيرًا من المستخدمين يمكنهم تصفح المواقع بشكل أسرع فيما لو استعملت تلك المواقع صورًا بصيغة WebP؛ يجدر بالذكر أنَّ هنالك خططٌ لإضافة دعم لصيغة WebP في متصفح Firefox.

تدعم صيغة WebP ضغط الصور الفقود (lossy) وغير الفقود (lossless)، وتدعم أيضًا الرسومات المتحركة، لذا يمكنها أن تستبدل صيغة GIF. ميزتها الرئيسية التي تتفوق فيها على صيغة الصور الأخرى هي أنَّ الحجم التخزيني للصورة أقل بكثير، مما يعني أنَّ سرعة تحميل الصور ستكون أكبر، وسيقل تراسل البيانات في الخادم وعند زائر الموقع. ولمّا كانت الصور بصيغة WebP أقل حجمًا بنسبةٍ كبيرة مقارنةً مع صور PNG و JPEG، فإنَّها ستسرِّع تحميل الصفحات بنسبةٍ كبيرة. إذا كان تطبيق الويب أو الموقع الخاص بك يعاني من مشاكل في الأداء أو كان حجم التراسل الشبكي كبيرًا، فتحويل صورك إلى صيغة WebP سيساعدك كثيرًا في تحسين أداء الصفحات.

سنستخدم في هذه المقالة أداةً تعمل من سطر الأوامر باسم cwebp لتحويل الصور إلى صيغة WebP، وسنُنشِئ سكربتات تسمح لنا بتحويل جميع الصور الموجودة في مجلّد مُحدَّد إلى هذه الصيغة، بما في ذلك الصور المضافة حديثًا. وفي النهاية، سنناقش منهجيتين يمكننا اتباعهما لتخديم صور WebP إلى زوار موقعنا.

المتطلبات المسبقة

أغلبية الخطوات والأوامر في هذه المقالة تنطبق على جميع توزيعات لينكس، والاختلافات تكون في طريقة تثبيت البرامج وحسب؛ لذا سنعتمد على ذكر طريقة تثبيت الأدوات اللازمة على توزيعة أوبنتو 16.04 و CentOS 7. ما يلزم هو خادم قد ثبتنا عليها إحدى التوزيعتين السابقتين، ومضبوطٌ فيه خادم أباتشي، مع وحدة mod_rewrite، لاحظ أنَّ حزمة أباتشي في توزيعة CentOS 7 تأتي مع دعم mod_rewrite مسبقًا، أما أوبنتو فهي تحتاج إلى تفعيل.

لاحظ أنَّ الغالبية العظمى من الأوامر في هذا الدرس ستعمل على التوزيعات الأخرى دون تعديل.

بعد حصولك على وصول إلى الخادم، وتثبيت أباتشي وتفعيل وحدة mod_rewrite، فيمكنك المتابعة مع هذه المقالة.

الخطوة الأولى: تثبيت cwebp وتهيئة مجلد الصور

سنثبِّت في هذا القسم البرمجات التي سنستخدمها لتحويل الصور، وسنُنشِئ أيضًا مجلدًا وننزِّل فيه صورًا لنختبر الأوامر عليها.

لتثبيت cwebp، البرمجية التي تضغط الصور وتحوِّلها إلى صيغة ‎.webp، فسنستخدم الأمر الآتي:

``` sudo apt-get install webp ```

أما إذا كنتَ تستعمل CentOS 7، فيمكنك استخدام الأمر الآتي بدلًا من الأمر السابق:

``` sudo yum install libwebp-tools ```

سنُنشِئ مجلدًا جديدًا في المجلد الجذر لخادم أباتشي (والموجود في المسار ‎/var/www/html) باسم webp:

``` sudo mkdir /var/www/html/webp ```

سنغيّر ملكية هذا المجلد إلى المستخدم سامي (sammy، بفرض أنَّ لدينا في النظام مستخدمٌ بذاك الاسم، وإن لم يكن متاحًا فيمكنك إنشاؤه إن أردت، فهو أفضل من ترك الملفات بملكية الجذر [المستخدم root]) وذلك باستخدام الأمر الآتي:

``` sudo chown sammy: /var/www/html/webp ```

سنحتاج إلى بعض الصور لتجربة الأوامر عليها، لذا سنُنزِّل صورًا حرةً باستخدام الأمر wget. لاحظ أنَّ هذه الأداة مثبتة افتراضيًا في أوبنتو 16.04، لكن تثبيتها سهلٌ جدًا في CentOS 7:

``` sudo yum install wget ```

ملاحظة: لتسهيل متابعة الأوامر الموجودة في هذه المقالة، سنُنزِّل ثلاث صور حرة من الإنترنت، أوّل صورتان منها (Junonia orithya و Mycalesis junonia) مرخصتان برخصة CC BY-SA 4.0 لصاحبها Jee & Rani Nature Photography؛ أما الصورة الأخيرة (Dental Care) فهي مرخصة برخصة CC0.

``` wget -c "https://upload.wikimedia.org/wikipedia/commons/2/24/Junonia_orithya-Thekkady-2016-12-03-001.jpg?download" -O /var/www/html/webp/image1.jpg wget -c "https://upload.wikimedia.org/wikipedia/commons/5/54/Mycalesis_junonia-Thekkady.jpg" -O /var/www/html/webp/image2.jpg wget -c "https://cdn.pixabay.com/photo/2017/07/18/15/39/dental-care-2516133_640.png" -O /var/www/html/webp/logo.png ```

معظم عملنا سيكون داخل المجلد ‎/var/www/html/webp، لذا سنغيّر مجلد العمل الحالي إليه بكتابة الأمر:

``` cd /var/www/html/webp ```

بعد أن ثبتنا خادم أباتشي، وفعّلنا إضافة mod_rewrite، وثبتنا cwebp وبعد أن ضبطنا مجلد التجارب، يمكننا الآن أن نتابع مع الخطوات القادمة.

الخطوة الثانية: ضغط الصور باستخدام cwebp

قبل أن نناقش كيفية تخديم صور ‎.webp إلى زوار موقعنا، علينا أن نُنشِئ نسخًا بهذه الصيغة من الصور الموجودة لدينا حاليًا. يمكننا استخدام الأداة cwebp لتحويل صور JPEG أو PNG أو TIFF إلى صياغة ‎.webp. الصيغة العامة لهذا الأمر تبدو كما يلي:

``` cwebp image.jpg -o image.webp ```

يمكن تحديد مسار ملف المخرجات (بصيغة ‎.webp) بعد الخيار ‎-o، لاحظ أنَّ صور WebP تنتهي عادةً باللاحقة ‎.webp.

يمكننا ضبط جودة الصورة ‎-q إلى أيّ قيمة بين 0 و 100؛ وستكون القيمة الافتراضية، إن لم نضبطها، مساويةً إلى 75.

إذا نزّلتَ الصور التي ذكرناها في الخطوة الأولى، فيمكنك تنفيذ الأوامر الآتية لتحويل الصورة image1.jpg إلى image1.webp و image2.jpg إلى image2.webp. تذكّر أن تغيّر مجلد العمل الحالي إلى ‎/var/www/html/webp، ثم نفِّذ الأمر cwebp لتحويل الصور بجودة 100%:

``` cd /var/www/html/webp cwebp -q 100 image1.jpg -o image1.webp cwebp -q 100 image2.jpg -o image2.webp ```

لنلقِ نظرةً على الحجم التخزيني لملفات JPEG و WebP باستخدام الأمر ls. سنستخدم الخيار ‎-l لعرض الصيغة التفصيلية التي تتضمن حجم الملف التخزيني، والخيار ‎-h الذي يطلب من الأمر ls أن يعرض الحجم بصيغةٍ سهلة القراءة:

``` ls -lh image1.jpg image1.webp image2.jpg image2.webp ```

سيُنتِج الأمر السابق الناتج الآتي:

``` -rw-r--r-- 1 sammy sammy 7.4M Oct 28 23:36 image1.jpg -rw-r--r-- 1 sammy sammy 3.9M Feb 18 16:46 image1.webp -rw-r--r-- 1 sammy sammy 16M Dec 18 2016 image2.jpg -rw-r--r-- 1 sammy sammy 7.0M Feb 18 16:59 image2.webp ```

يُظهِر ناتج الأمر ls أنَّ حجم الملف image1.jpg هو 7.4 ميغابايت، بينما حجم الملف image1.webp هو 3.9 ميغابايت؛ والمثل ينطبق على ملف image2.jpg (16 ميغابايت) و image2.webp (7.0 ميغابايت). لاحظ أنَّ حجم الملفات قد أصبح نصف ما كان عليه!

إذا كنّا نجري ضغطًا غير فقود (lossless، مما يعني أنَّ البيانات الأصلية للصورة ستُحفَظ كاملةً دون أيّ ضياع أثناء عملية الضغط)، فيمكننا أن نستعمل الخيار ‎-lossless، الذي يُعدّ أفضل طريقة للحفاظ على جودة صور PNG. إذا كانت لديك صور بصيغة PNG (كالتي نزّلناها في الخطوة السابقة) وتريد تحويلها إلى صيغة WebP لتقليل حجمها التخزيني، فيمكنك أن تستعمل الأمر الآتي:

``` cwebp -lossless logo.png -o logo.webp ```

الأمر الآتي يُظهِر أنَّ الحجم التخزيني لصورة WebP هو 60 كيلوبايت، وهو حوالي نصف الحجم التخزيني لصورة PNG (حوالي 116 كيلوبايت).

``` ls -lh logo.png logo.webp ```

ناتج الأمر السابق:

``` -rw-r--r-- 1 sammy sammy 116K Jul 18 2017 logo.png -rw-r--r-- 1 sammy sammy 60K Feb 18 16:42 logo.webp ```

الخلاصة هي أنَّ الحجم التخزيني لنسخة WebP من الصور هي أقل بحوالي 50% من الحجم التخزيني لمثيلاتها من JPEG و PNG، لكن لاحظ أنَّ نسبة الضغط قد تختلف اعتمادًا على الصور التي عندك. يجدر بالذكر أنَّ هنالك عوامل أخرى كثيرة تؤثر على معدّل الضغط، بما في ذلك معدّل الضغط للصورة الأصلية، وصيغة الملف الأصلي، وطريقة التحويل (فقودة أم غير فقودة)، والجودة المطلوبة، ونظام التشغيل الذي تجري عليه عملية التحويل.

الخطوة الثالثة: تحويل جميع صور JPEG و PNG في أحد المجلدات

لقد حوّلنا بعض الملفات في الخطوة السابقة إلى صيغة WebP يدويًا، لكن فعل ذلك يأخذ وقتًا طويلًا ويتطلب جهدًا كثيرًا. يمكننا تبسيط هذه العملية بكتابة سكربت تحويل الذي سيبحث عن ملفات JPEG ويحولها إلى صيغة WebP بجودة 90%، وسيحوِّل أيضًا صور PNG إلى صور WebP غير فقودة.

أوّل عنصر في السكربت الذي سنكتبه هو الأمر find. يمكنك تعلّم المزيد من المعلومات حول الأمر find من الفصل السابع عشر «البحث عن الملفات» من كتاب «سطر أوامر لينكس». سيبحث الأمر find عن الملفات في المجلد المُحدَّد، وسنستخدم معه بعض الخيارات لمطابقة أسماء تلك الملفات.

لنُنشِئ السكربت باستخدام المحرر النصي المُفضَّل لدينا. سنستخدم هاهنا المحرر nano لكنك تستطيع أن تستعمل أيّ محرر ترتاح معه. سنُنشِئ الملف webp-convert.sh في مجلد ‎/var/www/html/webp:

``` nano ~/webp-convert.sh ```

سيبدو أوّل سطر من الملف كما يلي:

``` find $1 -type f -and \( -iname "*.jpg" -o -iname "*.jpeg" \) ```

للحصول على مسار المجلد الذي يحتوي على الصور التي نريد تحويلها، فسنستخدم المعامل الموضعي ‎$1. لاحظ أنَّنا سنُمرِّر مسار مجلّد الصور التي نريد تحويلها إلى السكربت من سطر الأوامر، وهنا سيأتي دور المعامل الموضعي السابق. الغرض من استخدامه هو القدرة على تحديد مسار مجلد الصور بغض النظر عن مكان تخزين السكربت.

باستخدامنا للخيار ‎-type f أخبرنا الأمر find أن يبحث عن الملفات العادية فقط (أي أننا لا نريد أن نحصل على مسارات المجلدات في نواتجه). نريد أيضًا أن نُطابِق اسم الملف مع نمطٍ معيّن باستخدام الاختبار ‎-iname. لاحظ أنَّنا استخدمنا اختبارًا غيرَ حساسٍ لحالة الأحرف (وذلك باستخدامنا للاختبار ‎-iname بدلًا من ‎-name)، وهذا الاختبار سيبحث عن أيّة ملفات تنتهي باللاحقة ‎.jpg ‏(‎*.jpg) أو ‎.jpeg ‏(‎*.jpeg). رمز النجمة * هو محرفٌ خاصٌ يُطابِق محرفًا عاديًا صفر مرة أو أكثر. لاحظ أيضًا أنَّنا استخدمنا المعامل المنطقي ‎-o (الذي يعني «أو») لنطلب من الأمر find أن يعرض الملفات التي تُطابِق الاختبار الأول ‎-iname (أي ‎-iname "*.jpg"‎) أو الاختبار الثاني (‎-iname "*.jpeg"‎). وضعنا كلا الاختبارين بين قوسين لنحرص على أنَّ الاختبار الأول (أي ‎-type f) سيُنفَّذ دومًا باستخدام المعامل المنطقي ‎-and.

بعد العثور على الملفات المطلوبة، علينا أن نتخذ إجراءً لتحويلها إلى صيغة WebP. سنفعل ذلك باستخدام المعامل ‎-exec في السطر الثاني من السكربت الذي نعمل عليه (لاحظ أنَّنا استخدمنا الشرطة المائلة الخلفية \ للسماح بامتداد الأمر find على أكثر من سطر). البنية العامة لهذا المعامل هي ‎-exec command {} \;‎. السلسلة النصية {} ستُستبدل بمسار كل ملف يعثر عليه الأمر find (أي أنَّ الأمر المُحدَّد بهذا المعامل سيُنفَّذ على جميع الملفات، كلًا على حدة). استخدمنا ; لنخبر الأمر find أين ينتهي الأمر (لاحظ أنَّنا هربنا [escape] المحرف ; لأنَّ له معنى خاص في الصدفة [shell] ‏bash، وهو الإشارة إلى نهاية الأوامر).

علينا أن نتحقق أولًا من عدم وجود نسخة ‎.webp من الصور وذلك لكل صورة يُعثر الأمر find عليها، وإن لم تكن موجودةً فسنحوِّلها باستخدام الأمر cwebp كما ناقشنا في الخطوة السابقة. السؤال الآن هو ما هو الأمر الذي علينا تنفيذه مع المعامل ‎-exec؟ صراحةً، سنحتاج إلى تنفيذ أكثر من أمر واحد لتحويل الصور إلى ‎.webp. ولهذا السبب، سنستخدم الأمر bash لتنفيذ سكربت صغير الذي سيُنشِئ نسخة ‎.webp من الملف إن لم تكن موجودةً. ولفعل ذلك سنُمرِّر هذا السكربت الصغير كسلسلة نصية (محاطة بعلامتي اقتباس) باستخدام الخيار ‎-c. إلى هذه المرحلة، لقد تضمّن الأمر find المعامل ‎-exec، الذي يُنفِّذ الأمر bash الذي سنُمرِّر إليه سكربتًا صغيرًا (يُمثَّل فيما يلي بالمحتوى النائب `'commands') الذي سيُنفَّذ مجموعة أوامر على ملفات JPEG. يجب أن يبدو الملف الذي نعمل عليه كما يلي في هذه المرحلة:

``` find $1 -type f -and \( -iname "*.jpg" -o -iname "*.jpeg" \) \ -exec bash -c 'commands' {} \; ```
أما داخل `'commands'` (أي السكربت الموجود بين علامتي اقتباس بعد الخيار `‎-c` التابع للأمر `bash`)، فسنبدأ بتهيئة متغير جديد باسم `webp_path` للاحتفاظ بمسار ملف WebP الجديد، الذي سنولِّده بتبديل لاحقة الملف المُطابَق من الأمر `find` (أي `‎.jpg` أو `‎.jpeg`) باللاحقة `‎.webp`. سنستخدم الأمر `sed` هنا لتبديل الملف لاحقة الملف الأصلي (أي جميع المحارف التي تأتي بعد آخر نقطة `.` في اسم الملف) باللاحقة `‎.webp`. رجاءً ارجع إلى قسم الأمر `sed` في الفصل العشرين «معالجة النصوص» في كتاب [سطر أوامر لينكس](https://itwadi.com/node/2765) لمزيدٍ من المعلومات حوله. المعامل الموضعي `‎$0` سيحمل مسار الملف المُطابَق هنا، وذلك لأنَّنا مررنا مسار كل ملف إلى الأمر `bash` بوضع السلسلة النصية `{}` كمعامل له (تذكّر أنَّ الشكل العامل للأمر كان `bash -c 'commands' {} \;‎`). مرّرنا اسم الملف الناتج إلى مجرى الدخل القياسي للأمر `sed` باستخدام __here string__ وهي العلامة `‎<<<‎` (راجع الصفحة 411 من كتاب [سطر أوامر لينكس](https://itwadi.com/node/2765) للتفاصيل):
``` webp_path=$(sed 's/\.[^.]*$/.webp/' <<< "$0"); ```

للحفاظ على طاقة المعالجة في الخادم، فسنختبر إن كان هنالك ملفٌ محوّلٌ مسبقًا إلى صيغة WebP، وذلك باستخدام الأمر if. سنختبر ‎[‎ إن لم يكن ! هنالك ملفٌ ‎-f باسم ‎"$webp_path"‎ موجودًا مسبقًا. لاحظ وجود علامتي اقتباس حول اسم الملف لتفادي حدوث مشاكل فيما لو احتوى على فراغات:

``` if [ ! -f "$webp_path" ]; then # conversion command fi; ```

في النهاية، إذا لم يكن الملف موجودًا، فسنستخدم الأمر cwebp لإنشائه، كما فعلنا عند تحويل الصور في مجلد webp في الخطوة السابقة:

``` cwebp -quiet -q 90 "$0" -o "$webp_path"; ```
ملاحظة: ربما لاحظتَ أنَّ الأمر `cwebp` يُظهِر الكثير من المخرجات، وقد لا نرغب بها في حال استخدمناه ضمن سكربتات، لذا يمكننا الاستفادة من الخيار `‎-quiet` لإخبار الأمر `cwebp` ألّا يعرض أيّ شيء.

يجب أن يبدو السكربت المستخدم لتحويل صور JPEG كما يلي عند هذه المرحلة:

``` # converting JPEG images find $1 -type f -and \( -iname "*.jpg" -o -iname "*.jpeg" \) \ -exec bash -c ' webp_path=$(sed 's/\.[^.]*$/.webp/' <<< "$0"); if [ ! -f "$webp_path" ]; then cwebp -quiet -q 90 "$0" -o "$webp_path"; fi;' {} \; ```

أما لتحويل صور PNG، فسنستعمل شيئًا شبيهًا جدًا بما فعلناه مع صور JPEG، باستثناء أمرين اثنين: أولهما هو تغيير النمط المستعمل في الأمر find إلى ‎"*.png"‎، وثانيهما هو استعمال الخيار ‎-lossless في الأمر cwebp لتحويل صور PNG بطريقة غير فقودة (كما ناقشنا في الخطوة الثانية).

سيبدو الملف كله كما يلي:

``` #!/bin/bash converting JPEG images

find $1 -type f -and ( -iname ".jpg" -o -iname ".jpeg" )
-exec bash -c ' webp_path=$(sed 's/.[^.]*$/.webp/' <<< "$0"); if [ ! -f "$webp_path" ]; then cwebp -quiet -q 90 "$0" -o "$webp_path"; fi;' {} ;

converting PNG images

find $1 -type f -and -iname ".png"
-exec bash -c ' webp_path=$(sed 's/.[^.]
$/.webp/' <<< "$0"); if [ ! -f "$webp_path" ]; then cwebp -quiet -lossless "$0" -o "$webp_path"; fi;' {} ;

</div>

لنختبر الآن السكربت `webp-convert.sh`، سنستخدم الملفات التي أنشأناها في الخطوة السابقة لتجربته. لكن احرص أولًا على جعل ملف السكربت قابلًا التنفيذ:
<div markdown="1" dir="ltr">

chmod a+x ~/webp-convert.sh

</div>

لنشغِّل السكربت على المجلد `‎/var/www/html/webp`:
<div markdown="1" dir="ltr">

./webp-convert.sh /var/www/html/webp

</div>

لم يحدث شيء! ذلك لأنَّ السكربت بحث عن وجود نسخة `‎.webp` من الصور في المجلد `‎/var/www/html/wepb`، لكننا حولناها كلها في الخطوة الثانية من هذا الدرس. علينا إذًا أن نحصل على صورٍ جديدةٍ أو نحذف نسخة `‎.webp`. الأمر الآتي سيحذف جميع ملفات `‎.webp` الموجودة في المجلد `‎/var/www/html/webp`:
<div markdown="1" dir="ltr">

rm /var/www/html/webp/*.webp

</div>

بعد حذف جميع صور `‎.webp`، يمكننا تشغيل السكربت مرةً أخرى للتحقق أنَّه يعمل:
<div markdown="1" dir="ltr">

./webp-convert.sh /var/www/html/webp

</div>

سيؤكد الأمر `ls` أنَّ السكربت قد حوّل الصور بنجاح:
<div markdown="1" dir="ltr">

ls -lh /var/www/html/webp

</div>

الناتج:
<div markdown="1" dir="ltr">

-rw-r--r-- 1 sammy sammy 7.4M Oct 28 23:36 image1.jpg -rw-r--r-- 1 sammy sammy 3.9M Feb 18 16:46 image1.webp -rw-r--r-- 1 sammy sammy 16M Dec 18 2016 image2.jpg -rw-r--r-- 1 sammy sammy 7.0M Feb 18 16:59 image2.webp -rw-r--r-- 1 sammy sammy 116K Jul 18 2017 logo.png -rw-r--r-- 1 sammy sammy 60K Feb 18 16:42 logo.webp

</div>

هذه الخطوة هي أساس استخدام صور WebP في موقعنا، لأننا نحتاج إلى وجود نسخة WebP من جميع الصورة في موقعنا. سننظر في الخطوات القادمة كيف يمكننا الاستفادة من تلك الصور لتسريع مواقع الويب!

## الخطوة الرابعة: مراقبة ملفات الصور في أحد المجلدات

حوّلنا جميع الصور عندنا إلى صيغة `‎.webp` في الخطوة الثالثة، لكننا قد نحتاج إلى أتمتة هذه الخطوة للتعامل مع الصور الجديدة التي لم تحوَّل إلى WebP بعد. يمكننا إضافة سكربت جديد لتحويل لمراقبة التغييرات التي تحصل في مجلد الصور لتحويل الصور الجديدة مباشرةً دون الحاجة إلى تشغيل سكربت `webp-convert.sh` يدويًا في كل مرة نضيف فيها صورةً جديدةً.

إحدى المشاكل في سكربت `webp-convert.sh` هي أننا لا نستطيع أن نعرف أنَّ صورةً ما قد أُعيدَت تسميتها أو حُذِفَت. فمثلًا، لو أنشأنا صورةً باسم `foo.jpg` وشغّلنا سكربت `webp-convert.sh`، ثم أعدنا تسمية تلك الصورة إلى `bar.jpg` وشغّلنا السكربت `webp-convert.sh` مجددًا، فسينتهي بنا المطاف بوجود ملفي `‎.webp` متطابقين (وهما `foo.webp` و `bar.webp`). لهل هذه المشكلة، ولتفادي تشغيل السكربت يدويًا، فسنُضيف مراقبات (watchers) التي تراقب ملفاتٍ أو مجلداتٍ معيّنة للتغييرات، وتُشغِّل أوامر ردًا على تلك التغييرات.

يمكننا ضبط تلك المراقبات باستخدام الأمر `inotifywait`، الذي هو جزءٌ من الحزمة `inotify-tools`، وهي مجموعةٌ من الأوامر السطرية التي توفِّر واجهةً بسيطةً لنظام inotify في نواة نظام لينكس. يمكننا تثبيت هذه الحزمة في نظام أوبنتو بالأمر الآتي:
<div markdown="1" dir="ltr">

sudo apt-get install inotify-tools

</div>

أما لتوزيعة CentOS 7، فالحزمة `inotify-tools` متوافرة في مستودع EPEL. يمكننا تثبيت مستودع EPEL وتثبيت الحزمة `inotify-tools` باستخدام الأمرين الآتيين:
<div markdown="1" dir="ltr">

sudo yum install epel-release sudo yum install inotify-tools

</div>

الأمر `inotifywait` ينتظر حدوث تغييرات في مجلدٍ معيّن. الخيار `‎-q` سيخبر الأمر `inotifywait` ألّا يخرج الكثير من المخرجات. نرغب أيضًا أن يعمل الأمر `inotifywait` دون أجلٍ مسمى (`‎-m`) كيلا ينتهي تنفيذه بعد الحصول على أوّل حدث وقع في المجلد. إضافةً إلى ذلك، نرغب بضبط المراقبات في المجلد المُحدَّد وجميع المجلدات الفرعية التابعة له تعاوديًا باستخدام الخيار `‎-r`.

الصيغة التي نريد الحصول عليها عند وقوع تغييرٍ في المجلد هي «اسم الحدث» متبوعًا بمسار الملف، ويمكننا تغيير الصيغة باستخدام الخيار `‎--format`. الأحداث التي نريد مراقبتها هي `close_write` (الذي سيُطلَق عند إنشاء ملف وانتهاء عملية كتابته إلى القرص)، و `moved_from` و `moved_to` (التي ستُطلَق عند نقل ملف)، والحدث `delete` (الذي سيُطلَق عند حذف ملف).

يمكننا إنشاء السكربت بمحررنا النصي المُفضَّل كما فعلنا في الخطوة السابقة مع السكربت `webp-convert.sh`. الأمر الآتي سيُنشِئ الملف `webp-watchers.sh` في المجلد `‎/var/www/html/webp`:
<div markdown="1" dir="ltr">

nano ~/webp-convert.sh

</div>

سيبدو أوّل سطرٍ في السكربت كما يلي:
<div markdown="1" dir="ltr">

inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete $1

</div>

لتفحص ما هي المخرجات التي سيُظهرها هذا الأمر، فنحاول تشغيله في المجلد `‎/var/www/html/webp`، مع تشغيله في الخلفية بإضافة الرمز `&` في نهاية الأمر. سننتقل الآن من تعديل السكربت إلى سطر الأوامر لاختبار سلوك الأمر `inotifywait` (احرص على حفظ الملف والخروج من المحرِّر):
<div markdown="1" dir="ltr">

inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete /var/www/html/webp &

</div>

لمعرفة المخرجات التي يُنتِجها الأمر `inotifywait`، فسنُنشِئ عدِّة ملفات ونجري عمليات عليها. سنُنِشئ أولًا ملفًا جديدًا فارغًا باسم `new_file` باستخدام الأمر `touch`. أدخِل الأمر الآتي في سطر الأوامر:
<div markdown="1" dir="ltr">

touch /var/www/html/webp/new_file

</div>

ثم سنُعيد تسمية الملف (أي ننقله) إلى `moved_file` باستخدام الأمر `mv`:
<div markdown="1" dir="ltr">

mv /var/www/html/webp/new_file /var/www/html/webp/moved_file

</div>

وفي النهاية سنحذف الملف `moved_file` باستخدام الأمر `rm`:
<div markdown="1" dir="ltr">

rm /var/www/html/webp/moved_file

</div>

سنشاهد ناتجًا شبيهًا بالناتج الآتي في الطرفية:
<div markdown="1" dir="ltr">

CLOSE_WRITE,CLOSE /var/www/html/webp/new_file MOVED_FROM /var/www/html/webp/new_file MOVED_TO /var/www/html/webp/moved_file DELETE /var/www/html/webp/moved_file

</div>

سيظهر أوّل سطر بعد إنشاء الملف الجديد، وسيظهر السطران الثاني والثالث بعد نقل الملف (ويشيران إلى مسار المصدر والوجهة). في النهاية، آخر سطر يُظهِر أنَّ الملف قد حُذِف.

بالعودة إلى أوّل سطر في سكربت `webp-watchers.sh`، سنستخدم الأمر `grep` لمعرفة إن كانت الملفات التي أطلقت أحد الأحداث السابقة هي ملفات بصيغة JPEG أو PNG؛ ولمّا كان الأمر `inotifywait` لا يوفِّر الخيار `‎--include` لمراقبة الملفات التي تُطابِق تعبيرًا نمطيًا محدَّدًا، فنحن مجبرون لاستخدام الأمر `grep` لحلّ التفافي لهذا القصور. أمر `grep` الآتي سيؤدي إلى مطابقة صور JPEG أو PNG فقط. وسنستخدم فيه الخيار `‎-i` لتجاهل حالة الأحرف، والخيار `‎-E` لاستخدام التعابير النمطية الموسّعة (extended regular expressions). سنضيف أيضًا الخيار `‎--line-buffered` إلى أمر `grep` لتمرير الأسطر التي جرت مطابقتها إلى حلقة التكرار (التي سنُناقشها بعد قليل):
<div markdown="1" dir="ltr">

| grep -i -E '.(jpe?g|png)$' --line-buffered

</div>

أي أنَّ ناتج الأمر `inotifywait` سيُمرِّر مسار الملفات التي تغيّرت في المجلد المُحدَّد عبر أنبوب (pipe، أي الرمز `|`) إلى الأمر `grep`، الذي بدوره سيتحقق إن كان الملف صورةً.

الخطوة الآتية هي بناء حلقة التكرار `while`. يمكننا الاستفادة من حلقة التكرار `while` مع الأمر `read` في حالات مثل حالتنا؛ مما يسمح لنا بمعالجة كل سطر من الأسطر المُمرَّرة إلى الحلقة فور إخراجها. سيُخزِّن الأمر `read` الحدث في متغيرٍ باسم `‎$operation` وسيخزِّن مسار الملف المُعالَج في متغيرٍ باسم `‎$path`:
<div markdown="1" dir="ltr">

| while read operation path; do

commands

done;

</div>

لندمج ذلك مع أمر `inotifywait` الموجود في أوّل سطر من السكربت:
<div markdown="1" dir="ltr">

inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete $1
| grep -i -E '.(jpe?g|png)$' --line-buffered
| while read operation path; do

commands

done;

</div>

حلقة `while` ستتحقق من الحدث المُطلَق من الأمر `inotifywait`، ومن ثم ستجري الأوامر الموجودة داخل الحلقة الأفعال الآتية:

* إنشاء ملف WebP جديد إن أنشأنا صورةً جديدةً أو نقلناها إلى المجلد الهدف.

* حذف ملف WebP إذا حُذِفَت الصورة الأصلية أو نُقِلَت من المجلد الهدف.

هنالك ثلاثة أقسام رئيسية في حلقة التكرار. أولها هو تعريف متغيرٍ باسم `webp_path` الذي هيّأناه ليُخزِّن نسخة `‎.webp` من الصورة الهدف (أنشأناه بأسلوب مشابه لما فعلناه في الخطوة الثالثة):
<div markdown="1" dir="ltr">

webp_path="$(sed 's/.[^.]*$/.webp/' <<< "$path")";

</div>

الخطوة المنطقية التالية هي اختبار الحدث الذي وقع:
<div markdown="1" dir="ltr">

if [ $operation = "MOVED_FROM" ] || [ $operation = "DELETE" ]; then

commands to be executed if the file is moved or deleted

elif [ $operation = "CLOSE_WRITE,CLOSE" ] || [ $operation = "MOVED_TO" ]; then

commands to be executed if a new file is created

fi;

</div>

إذا نقلنا أو حذفنا الملف، فسنتحقق من وجود نسخة `‎.webp` منه، فإذا كانت موجودةً فسنحذفها باستخدام الأمر `rm`:
<div markdown="1" dir="ltr">

if [ -f "$webp_path" ]; then $(rm -f "$webp_path"); fi;

</div>

أما بالنسبة إلى الملفات المُنشَأة حديثًا، فسنختبر إن كان الملف بصيغة PNG (باستخدام `if` و `grep` كما فعلنا في الخطوة الثالثة). فعلنا لذا سيسمح لنا باستخدام التحويل غير الفقود (عبر الخيار `‎-lossless`). إذا لم يكن الملف المُطابَق بصيغة PNG فسنجري تحويلًا فقودًا باستخدام نفس الأمر المذكور في الخطوة الثانية (أي بتحديد جودة الصورة عبر المعامل `‎-q`):
<div markdown="1" dir="ltr">

if [ $(grep -i '.png$' <<< "$path") ]; then $(cwebp -quiet -lossless "$path" -o "$webp_path"); else $(cwebp -quiet -q 90 "$path" -o "$webp_path"); fi;

</div>

قد تتساءل لماذا لم ننقل ملف `‎.webp` عند نقل الملف الأصلي المرتبط به، ذلك لأننا اتبعنا المنهجية الأسهل: حذف ملف WebP ثم إعادة إنشاءه من الصفر؛ ذلك لأننا لا نريد تعقيد السكربت في حال أضفنا الجزء المسؤول عن نقل نسخة `‎.webp` المرتبطة بالملفات الأصلية.

يجب أن يكون كامل ملف `webp-watchers.sh` كما يلي:
<div markdown="1" dir="ltr">

#!/bin/bash echo "Setting up watches.";

watch for any created, moved, or deleted image files

inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete $1
| grep -i -E '.(jpe?g|png)$' --line-buffered
| while read operation path; do webp_path="$(sed 's/.[^.]*$/.webp/' <<< "$path")"; if [ $operation = "MOVED_FROM" ] || [ $operation = "DELETE" ]; then # if the file is moved or deleted if [ -f "$webp_path" ]; then $(rm -f "$webp_path"); fi; elif [ $operation = "CLOSE_WRITE,CLOSE" ] || [ $operation = "MOVED_TO" ]; then # if new file is created if [ $(grep -i '.png$' <<< "$path") ]; then $(cwebp -quiet -lossless "$path" -o "$webp_path"); else $(cwebp -quiet -q 90 "$path" -o "$webp_path"); fi; fi; done;

</div>

لا تنسَ أن تجعل السكربت قابلًا للتنفيذ:
<div markdown="1" dir="ltr">

chmod a+x ~/webp-convert.sh

</div>

يمكننا الآن تشغيل السكربت على المجلد `‎/var/www/html/webp` لمراقبته (وجميع المجلدات الفرعية فيه) للتغيرات. ربما ترغب بتشغيل الأمر في الخلفية بإضافة الرمز `&` في نهاية الأمر:
<div markdown="1" dir="ltr">

./webp-watchers.sh /var/www/html/webp

</div>

في هذه المرحلة، حوّلنا جميع الصور الموجودة في المجلد `‎/var/www/html/webp` إلى صيغة WebP، وضبطنا المراقبات باستعمال سكربت `webp-watchers.sh`. حان الوقت الآن لاستكشاف خياراتنا بخصوص طريقة تخديم صور WebP إلى زوار مواقعنا.

## الخطوة الخامسة: تخديم ملفات WebP إلى الزوار

إذا اتبعتَ التعليمات الموجودة في الخطوات السابقة، فيجب أن تصبح عندك نسخة `‎.webp` لكل صورة JPEG أو PNG موجودة في مجلد `‎/var/www/html/webp`. يمكننا الآن التفكير بطريقة تخديم تلك الصور إلى المتصفحات الداعمة لها.

سنفعل ذلك بمنهجيتين اثنتين: استخدام عناصر HTML5 (العنصر [`<picture>`](http://wiki.hsoub.com/HTML/picture) تحديدًا)، ووحدة `mod_rewrite` في خادم الويب أباتشي. سنناقش طريقة استخدام العنصر `<picture>` في هذه الخطوة، وسنُرجئ شرح الوحدة `mod_rewrite` إلى الخطوة القادمة.

إذا كنتَ مطوِّر (أو صائن) موقعك، فيمكنك استخدام العنصر `<picture>` لتضمين الصور في صفحات الويب في موقعك. يسمح هذا العنصر بتعريف أكثر من مصدر (source) للصورة. وإذا كان المتصفح يدعم صيغة WebP فسيُنزِّل نسخة `‎.webp` بدلًا من الصورة الأصلية، مما يُسرِّع تخديم الصفحة. يجدر بالذكر أنَّ العنصر `<picture>` مدعومٌ دعمًا جيدًا في المتصفحات الحديثة التي تدعم صيغة WebP.

العنصر `<picture>` هو حاوية التي تحتوي على عناصر [`<source>`](http://wiki.hsoub.com/HTML/source) لتحديد «مصدر» مختلف للصورة المُحدَّدة في العنصر [`<img>`](http://wiki.hsoub.com/HTML/img) المحتوى فيها. إذا استخدمنا العنصر `<source>` للإشارة إلى صورة `‎.webp`، فسينظر المتصفح إن كان قادرًا على تفسير تلك الصفحة، وإن لم يكن قادرًا فسيستعمل الصورة المُحدَّدة في الخاصية [src](http://wiki.hsoub.com/HTML/img#src) في العنصر `<img>`.

لنقل أنَّ لدينا صورة PNG باسم `logo.png` وحولنا إلى `logo.webp` باستخدام السكربتات السابقة، فيمكننا استخدام شيفرة HTML الآتية لعرض الصورة `logo.webp` لأي متصفح يدعم صيغة WebP، والصورة `logo.png` للمتصفحات الأخرى التي لا تدعم صيغة WebP أو لا تدعم العنصر `<picture>`. لنفتح محررنا النصي المُفضَّل ونُنشِئ ملف HTML في المسار `‎/var/www/html/webp/picture.html`:
<div markdown="1" dir="ltr">

nano /var/www/html/webp/picture.html

</div>

سنختصر الصفحة الاختبارية إلى البنية الآتية:
<div markdown="1" dir="ltr">
Site Logo ```

يمكن استخدام العنصر <picture> لتوفير أكثر من نسخة من الصورة، مما يسمح للمتصفح باختيار أكثر صورة ملائمة بينها لعرضها. هذا أبسط خيارٍ متاحٍ أمامنا لتخديم صور ‎.webp.

بعد أن شرحنا كيفية تخديم صور ‎.webp مباشرةً من شيفرة HTML، سننتقل الآن إلى شرح طريقة أتمتة هذه العملية باستخدام وحدة mod_rewrite التابعة لخادم أباتشي.

الخطوة السادسة: تخديم صور WebP باستخدام وحدة mod_rewrite

إذا أردنا تحسين سرعة موقعنا، لكنه يحتوي على عددٍ كبيرٍ من الصفحات أو لدينا وقتٌ وموارد قليلة لتعديل شيفرة HTML، فستساعدنا وحدة mod_rewrite في أتمتة عملية تخديم صور ‎.webp إلى المتصفحات الداعمة لها.

أولًا، علينا اختبار إذا كانت وحدة mod_rewrite متوافرةً، وذلك في ملف ‎.htaccess باستخدام التعليمة ifModule؛ وإذا كانت الوحدة متاحةً فسنُفعِّلها باستخدام التعليمة RewriteEngine On. يمكن إنشاء ملف ‎.htaccess في مجلد ‎/var/www/html/webp باستخدام الأمر الآتي:

``` nano /var/www/html/webp/.htaccess ```

الصيغة الأوليّة لملف ‎.htaccess ستكون كما يلي:

``` RewriteEngine On # further directives ```

لكي نعرف متى علينا تخديم صور ‎.webp إلى المستخدم، فيجب أن يجري خادم الويب عدِّة اختبارات. فعندما يجري المتصفح طلبيةً، فسيُضمِّن فيها ترويسةً تُخبِر الخادم ماذا يستطيع المتصفح تشغيله. وفي حالة صور WebP، سيُرسِل الخادم ترويسة Accept التي تحتوي على image/webp. سنتحقق إذا كان المتصفح قد أرسل هذا الترويسة باستخدام التعليمة RewriteCond. التعليمة RewriteCond تُحدِّد المعايير أو الشروط التي يجب أن تُطابَق حتى يُنفَّذ ما في التعليمة RewriteRule. راجع توثيق وحدة mod_rewrite الرسمي لمزيدٍ من المعلومات:

``` RewriteCond %{HTTP_ACCEPT} image/webp ```

علينا ترشيح كل الملفات ما عدا صور JPEG و PNG، وذلك باستخدام تعبيرٍ نمطيٍ شبيهٍ بالتعبير المستخدم في الأقسام السابقة، وذلك لمطابقة عنوان URI المطلوب من المتصفح. لاحظ أننا استخدمنا ‎(?i) لجعل عملية المطابقة غير حساسة لحالة الأحرف:

``` RewriteCond %{REQUEST_URI} (?i)(.*)(\.jpe?g|\.png)$ ```

لتتحقق أنَّ نسخة ‎.webp من الملف موجودة (من البدهي أننا لا نريد تخديم صورة غير موجودة إلى المستخدم)، فيمكننا استخدام تعليمة RewriteCond كما يلي:

``` RewriteCond %{DOCUMENT_ROOT}%1.webp -f ```

في النهاية، إذا تحققت كل الشروط السابقة، فسنُعيد توجيه صورة JPEG أو PNG المطلوبة إلى ملف WebP المرتبط بها. لاحظ أننا «نعيد توجيه الملف» (redirect، أي سيُطلَب من المتصفح أن يجري طلبية جديدة لرابط URI جديد يُشير إلى ملف ‎.webp) باستخدام الراية R، ولا «نعيد كتابة رابط URI» ‏(rewrite). الفرق بين إعادة التوجيه وإعادة كتابة رابط URI هي أنَّ المتصفح سيُخدِّم رابط URI الجديد دون إخبار المتصفح بذلك، مما يعني أنَّ الملف المنقول إلى المتصفح هو ‎.webp لكنّه سيبقى مخدّمًا بنفس رابط URI الأصلي. بعبارةٍ أخرى، سيظهر أنَّ رابط URI يُشير إلى ملف ‎.png (على سبيل المثال)، لكنه في الحقيقة ملف ‎.webp:

``` RewriteRule (?i)(.*)(\.jpe?g|\.png)$ %1\.webp [L,T=image/webp,R] ```

عند هذه المرحلة، أنهينا قسم mod_rewrite في ملف ‎.htaccess، لكن ماذا سيحدث لو كان هنالك خادم تخزين مؤقت بين خادمنا وجهاز المستخدم؟ قد يؤدي ذلك إلى تخديم النسخة الخطأ من الملف إلى الزائر، ولهذا السبب علينا أن نتحقق إذا كانت الوحدة mod_headers مفعّلةً، وذلك لإرسال ترويسة Vary: Accept. الترويسة Vary تُخبر الخوادم الوسيطة (proxy servers) أنَّ نوع المحتوى للمستند (أي MIME type. بعبارةٍ أخرى: صيغة الملف المُخدَّم) تختلف (Vary) اعتمادًا على قدرات المتصفح الذي طلب الملف. إضافةً إلى ذلك، إنَّ المحتوى المُخدَّم إلى الزائر قد وُلِّد اعتمادًا على ترويسة Accept الموجودة في الطلبية. فطلبيةٌ أخرى لها ترويسة Accept مختلفة قد تحصل على ناتج آخر. لاحظ أنَّ إرسال هذه الترويسة هو أمرٌ ضروري لمنع الخوادم الوسيطة من تخديم صور WebP إلى المتصفحات غير الداعمة لها:

``` Header append Vary Accept env=REDIRECT_accept ```

في نهاية ملف ‎.htaccess، سنضبط نوع MIME لملفات ‎.webp إلى image/webp باستخدام التعليمة AddType. فعلنا ذلك لتخديم الصور باستخدام نوع MIME الصحيح:

``` AddType image/webp .webp ```

هذه هي النسخة النهائية من ملف ‎.htaccess:

``` RewriteEngine On RewriteCond %{HTTP_ACCEPT} image/webp RewriteCond %{REQUEST_URI} (?i)(.*)(\.jpe?g|\.png)$ RewriteCond %{DOCUMENT_ROOT}%1.webp -f RewriteRule (?i)(.*)(\.jpe?g|\.png)$ %1\.webp [L,T=image/webp,R] Header append Vary Accept env=REDIRECT_accept

AddType image/webp .webp

</div>

ملاحظة: لاحظ أنَّك تستطيع دمج ملف `‎.htaccess` السابقة مع الملف الموجود مسبقًا عندك. فلو كنتَ تستخدم ووردبريس مثلًا، فيمكنك نسخ ملف `‎.htaccess` السابق ولصقه في ***بداية*** الملف الموجود عندك.

لنجرِّب ما فعلناه في هذه الخطوة. لو اتبعنا الخطوات السابقة، فيجب أن يكون لدينا ملف باسم `logo.png` و `logo.webp` في مجلد `‎/var/www/html/webp`. لنجرِّب استخدام عنصر `<img>` بسيط لتضمين صورة الشعار `logo.png` في صفحة الويب. يمكننا إنشاء ملف HTML تجريبي مثلما فعلنا في الخطوة السابقة:
<div markdown="1" dir="ltr">

nano /var/www/html/webp/img.html

</div>

أدخِل شيفرة HTML في ملف `‎/var/www/html/webp/img.html`:
<div markdown="1" dir="ltr">
Site Logo ```
عندما تزو صفحة الويب السابقة باستخدام متصفح Chrome بزيارة الرابط [http://example.com/webp/img.html](http://example.com/webp/img.html) فستلاحظ أنَّ الصورة المُخدَّمة هي نسخة `‎.webp` من الشعار (جرِّب مثلًا فتح الصورة في لسانٍ جديد)؛ أما إذا كنتَ تستخدم Firefox فستحصل على نسخة `‎.png` تلقائيًا.

الخلاصة

تعلمنا في هذه المقالة عن كيفية استخدام صيغة صور WebP لجعل تحميل صفحات الويب أسرع، وشرحنا كيفية استخدام الأداة cwebp لتحويل الملفات. أوّل طريقة استعملناها لتخديم الملفات هي عبر عنصر <picture>، أما الخطوة الثانية فكانت باستعمال وحدة mod_rewrite في أباتشي.

ما شرحناه في هذا الدرس كافٍ لتحقيق هدف تسريع الموقع باستخدام صور WebP، لكن يمكنك تخصيص السكربتات لتناسب احتياجاتك. ننصحك بأخذ وقتك لتصفح المراجع الآتية:

  • لتعلّم المزيد حول ميزات صيغة WebP وكيفية استخدام أدوات التحويل، فانظر إلى توثيق WebP.

  • لمزيدٍ من التفاصيل والأمثلة عن استخدام العنصر <picture>، فانظر إلى صفحته في موسوعة حسوب.

  • للتوسع في فهم وحدة mod_rewrite، فألقِ نظرةً على توثيقها.

استخدام صيغة WebP لصور موقعك سيُقلِّل حجمها التخزيني بنسبةٍ كبيرة، مما يُقلِّل حجم التراسل الشبكي ويجعل تحميل الصفحات أسرع، خصوصًا إذا كان موقعك يحتوي على عددٍ كبيرٍ من الصور.

مصدر هذه المقالة هو مقالة How To Create and Serve WebP Images to Speed Up Your Website المنشورة في DitgitalOcean والتي كتبها عبد اللطيف ايمش.