دليلك الشامل إلى أداة البناء Gulp


عبد اللطيف ايمش

تتوفر عدِّة «أدوات بناء» (build tools) تعمل على تسهيل عملية تطوير مواقع الويب وأتمتة المهام الروتينية مثل «تصغير» (minify) ملفات JavaScript و HTML و CSS والصور، وتحويل ملفات LESS و SASS إلى CSS، والتحقق مباشرةً من صحة البنية اللغوية لملفات JavaScript والكثير من الأمور التي ستغيّر من طريقة عملك وتجعلها أكثر سرعةً وكفاءة.

سنشرح في درسنا هذا إحدى أدوات البناء المشهورة وهي الأداة Gulp بالإضافة إلى أساسيات استخدامها، شرحًا مدعمَّا بالأمثلة العملية.

ملاحظة مهمة: هذا الدليل كتب من أجل الإصدار 3 من Gulp وهنالك بعض الاختلافات والتغييرات الجوهرية في الإصدار 4. لذلك، إن كنت تستعمل الإصدار 4، فهذا الدليل ليس لك لأنه هنالك بعض الدوال والواجهات البرمجية التي لن تعمل مع ذلك الإصدار.

ما هي أداة البناة Gulp؟

أدوات البناء هو أمر حديث العهد ولم ينتشر إلا منذ بضعة سنوات. كانت ملفات ‎.sh التي تكتب بلغة Bash أحد أشهر أدوات البناء غير المباشرة وغير المخصصة المستعملة آنذاك لكتابة التعليمات وأتمتة بعض المهام الروتينية المحدودة مثل مراقبة الملفات وتحديثها. هذه الملفات تعمل مع الصدفة shell التي تأتي مدمجة مع أنظمة التشغيل.

انتشرت أدوات بناء أخرى مثل Ant و Gradle ولكنهما كانتا ضمن عالم جافا وتحتاجان إلى استعمال لغة XML لضبطهما وهي اللغة التي لا يحبها مطوري الويب عدا عن تعلم لغة جديدة لا يألفونها. اللغة التي يحبها مطورو الويب هي جافاسكربت وهنا جاءت أداتا البناء Gulp و Grunt لسد هذه الفجوة.

تتفوق أداة البناء Gulp على منافستها Grunt بأنها أسرع بكثير لأنها تنفذ المهام على التوازي (على عكس Grunt) كما أنها تمرر الملفات المفتوحة عبر مجاري Streams داخلية. أضف إلى ذلك أن اللغة المستعملة في ضبط مهام Gulp هي جافاسكريبت بالتأكيد وهي اللغة التي يحبها مطورو الويب كثيرًا.

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

سيجري العمل على نظام التشغيل أوبنتو 18.04 ويجب أن تكون قد ثبَّت Node.js و npm مسبقًا. إن لم تكن Node.js مثبتةً لديك، فابدأ بتثبيتها هي ومدير الحزم npm عبر الأمر التالي:

sudo apt update
sudo apt install nodejs

للتحقق من عملية التثبيت (وللتأكد من الإصدار المثبت مسبقًا لديك)، نفذ الأمر التالي:

nodejs --version

يجب أن ترى ناتجًا شبيهًا بالناتج التالي:

v8.10.0

ولتكون قادرًا على تنزيل وتثبيت حزم npm، تحتاج إلى تنزيل npm (مدير حزم Node.js) إن لم يكن مثبَّتًا لديك:

sudo apt install npm

تحقق من عملية التثبيت (أو الإصدار المثبت مسبقًا) عبر الأمر التالي:

npm --version

يجب أن ترى ناتجًا شبيهًا بالناتج التالي:

3.5.2

أنت الآن جاهز لإكمال هذا الدليل!

تثبيت Gulp

يمكن تثبيت Gulp عبر مدير الحزم npm الذي تحدثنا عنه منذ قليل، بشكل سهلٌ للغاية فكل ما عليك فعله هو تنفيذ الأمر الآتي في سطر الأوامر لتثبيت Gulp 3 لعموم النظام:

npm install gulp@3.9.0 

ثم عليك تهيئة مجلد المشروع الذي ستعمل عليه. نفِّذ الأمر الآتي في مجلد المشروع:

npm init

عليك الآن تثبيت Gulp في المشروع وإضافته إلى الاعتماديات (أي devDependencies):

npm install --save-dev gulp

أصبح Gulp مثبتًا في مشروعك وجاهزًا للاستخدام.

البنية الهيكلية للمشروع الذي سنعمل عليه

مرونة Gulp تسمح لنا باستخدامه بغض النظر عن بنية المجلدات التابعة للمشروع، كل ما يلزمك هو فهمٌ صحيحٌ كاملٌ لآلية عمله مما يسمح لك بتعديله لتلبية احتياجاتك. سنعتمد في مقالتنا البنية الآتية للمجلدات:

  |- app/
      |- css/
      |- fonts/
      |- images/ 
      |- index.html
      |- js/ 
      |- scss/
  |- dist/
  |- gulpfile.js
  |- node_modules/
  |- package.json

هذه بنيةٌ بسيطة نستعمل فيها مجلد app لاحتواء ملفات التطوير، بينما المجلد dist (اختصار للكلمة «distribution») سيحتوي على الملفات الإنتاجية.

كتابة أول مهمة من مهام Gulp

عندما نُشغِّل Gulp من سطر الأوامر، فسيبحث عن وجود الملف gulpfile.js في مجلد المشروع. سيُخبِر هذا الملف Gulp ما هي الإضافات التي يجب تحميلها وما هي المهام التي يجب القيام بها. علينا إذًا إنشاء الملف gulpfile.js وتضمين gulp في بدايته كما يلي:

var gulp = require('gulp');

عبارة require السابقة ستخبر Node أن تبحث في مجلد node_modules عن حزمة باسم gulp، وعند العثور عليها فستُسنَد محتوياتها إلى المتغير gulp. يمكننا الآن البدء بكتابة «مهمّة Gulp» (أي Gulp task) بالاستفادة من المتغير gulp. البنية الأساسية لمهمّة Gulp هي:

gulp.task('task-name', function() {
  // سنضع الشيفرات هنا
});

task-name تشير إلى اسم المهمّة، والتي ستُستعمَل في أي مكانٍ تريد تشغيل هذه المهمّة فيه. نستطيع تشغيل هذه المهمة بتمرير اسمها كوسيط للأمر gulp في سطر الأوامر (أي تنفيذ gulp task-name). لنجرِّب ما سبق بإنشائنا للمهمّة hello في ملف gulpfile.js السابق (الذي يحتوي على عبارة require) والتي ستطبع الجملة Hello World:

gulp.task('hello', function() {
  console.log('Hello World');
});

يمكننا تشغيل هذه المهمة بتنفيذ الأمر gulp hello في سطر الأوامر، لا تنسَ الانتقال إلى مجلد المشروع (عبر الأمر cd في لينكس أو ماك، أو dir في ويندوز) قبل تنفيذ الأمر. مخرجات الأمر السابق هي:

user@linuxbox:~/gulp-test$ gulp hello
[18:34:41] Using gulpfile ~/gulp-test/gulpfile.js
[18:34:41] Starting 'hello'...
Hello World
[18:34:41] Finished 'hello' after 127 μs

حسنًا، لا تكون مهام Gulp بهذه البساطة، حيث تحتوي عادةً على دالتين تابعتين للكائن gulp بالإضافة إلى استخدام إضافة (plugin) أو أكثر من إضافات Gulp. هذا مثالٌ عن البنية العامة لمهمّات Gulp:

gulp.task('task-name', function () {
  //الحصول على الملفات التي يجب إجراء عمليات عليها
  // gulp.src باستخدام الدالة
  return gulp.src('source-files')
    .pipe(aGulpPlugin()) // ومن ثم تمريرها إلى إضافة
    .pipe(gulp.dest('destination')) // وإخراج الملف إلى المجلد الهدف
})

كما هو واضح، تتطلب المهام المفيدة في gulp دالتين هما gulp.src و gulp.dest. الدالة gulp.src تخبر Gulp ما هي الملفات التي يجب تطبيق المهمة عليها، بينما الدالة gulp.dest تخبر Gulp أين يجب إخراج الملفات بعد تنفيذ المهمّة.

يَستخدم Gulp المجاري (streams) التي توفرها node.js، وهذا يسمح بتمرير البيانات التي ستُعالَج عبر الأنابيب (pipes) وهذا ما تفعله الدالة ‎.pipe()‎؛ لشرحٍ تفصيليٍ عن المجاري في node.js، فأحيلك إلى هذه المقالة.

سنشرح كيفية الاستفادة من Gulp عبر استعماله لتحويل ملفات Sass إلى ملفات CSS.

تحويل ملفات Sass إلى CSS عبر Gulp

نستطيع استخدام إضافة باسم gulp-sass لتحويل ملفات Sass إلى CSS. عليك تثبيت الإضافة gulp-sass في مشروعك باستخدام الأمر npm install كما فعلنا سابقًا مع gulp.

يفضل أيضًا استخدام الخيار ‎--save-dev لكي تُضاف الحزمة gulp-sass إلى devDependencies في ملف package.json وهذا يسهل نسخ الملف إلى مشروع آخر وتثبيت نفس الاعتماديات (عبر الأمر npm install فقط):

npm install gulp-sass –save-dev

علينا الآن تضمين الحزمة gulp-sass عبر require من مجلد node_modules كما فعلنا أول مرة مع gulp كي نتمكن من استخدام الإضافة:

var gulp = require('gulp');
// gulp-sass تضمين إضافة
var sass = require('gulp-sass');

يمكننا استخدام gulp-sass بوضع الدالة sass()‎ بدلًا من aGulpPlugin()‎ في المثال السابق. ولمّا كان الغرض من المهمّة هو تحويل ملفات Sass إلى CSS فلنسمِّها sass:

gulp.task('sass', function(){
  return gulp.src('source-files')
    .pipe(sass()) // gulp-sass استخدام إضافة
    .pipe(gulp.dest('destination'))
});

علينا توفير ملفات مصدرية بصيغة sass -ومجلد لإخراج الناتج فيه- إلى المهمّة sass، لنبدأ بإنشاء الملف styles.scss في مجلد app/scss، ومن ثم سنضع مسار هذا الملف في الدالة gulp.src في مهمّة sass. ونريد أيضًا إخراج ملف styles.css النهائي إلى مجلد app/css الذي سيكون هو مجلد الوجهة (destination) لدالة gulp.dest.

gulp.task('sass', function(){
  return gulp.src('app/scss/styles.scss')
    .pipe(sass())
    .pipe(gulp.dest('app/css'))
});

سنختبر المهمّة sass الآن للتأكد من عملها عملًا صحيحًا، لكن قبل ذلك علينا استخدام دالة من دوال Sass ضمن ملف styles.scss.

// styles.scss
.testing {
  width: percentage(5/7);
}

إذا نفَّذتَ الأمر gulp sass في سطر الأوامر، فيجب أن يُنشَأ الملف styles.css في مجلد app/css، مع تبديل الدالة percentage(5/7)‎ وتحويلها إلى 71.42857%‎.

/* styles.css */
.testing {
  width: 71.42857%; 
}

تحققنا أنَّ المهمّة sass تعمل بشكلٍ سليم. أحيانًا نحتاج إلى بناء أكثر من ملف ‎.scss وتحويلها إلى ملفات CSS في نفس الوقت، وسنحتاج حينها إلى استخدام «محارف التحديد» (Globs).

محارف التحديد في Node

تسمح لك محارف التحديد بتمرير أكثر من ملف إلى الدالة gulp.src، وهي شبيهة بالتعابير النمطية (regular expressions) لكنها تُستعمَل خصيصًا لمسارات الملفات.

عند استخدامك لمحرف تحديد (glob) فسيتحقق جهازك من أسماء الملفات والمسارات المُحدَّدة بالنمط المستخدم، فإن تمت مطابقة النمط فسيُضاف الملف إلى الدالة gulp.src.

أغلبية الأنماط التي نستعملها مع Gulp تنضوي تحت لواء الأنماط الآتية:

  1. النمط ‎*.scss: رمز النجمة * هو محرفٌ خاصٌ يُطابِق أيّة ملف في المجلد المعيّن. وفي مثالنا ستُطابَق جميع الملفات التي تنتهي باللاحقة ‎.scss في المجلد الرئيسي للمشروع.
  2. النمط ‎**/*.scss: هذه حالةٌ أعم من المثال السابق، حيث ستتم مطابقة أيّة ملفات تنتهي باللاحقة ‎.scss في المجلد الرئيسي للمشروع وفي جميع المجلدات الفرعية الموجودة فيه.
  3. النمط ‎!not-me.scss: الرمز ! يعني أنَّ على Gulp استثناء هذا النمط من الملفات المُحدَّدة، وهذا مفيدٌ إن شئتَ أن تستثني ملفًا من التحويل؛ وفي مثالنا سنستثني الملف not-me.scss.
  4. النمط ‎*.+(scss|sass): إشارة الزائد + والأقواس ()‎ ستسمح لأداة Gulp بمطابقة عدِّة أنماط معًا، والتي سيُفصَل بينها بمحرف الخط العمودي |. وفي مثالنا سيُطابِق Gulp جميع الملفات التي تنتهي باللاحقة ‎.scss أو ‎.sass في المجلد الرئيسي للمشروع.

بعد تعلمنا لمحارف التحديد، أصبح بإمكاننا وضع التعبير app/scss/**/*.scss بدلًا من app/scss/styles.scss، وبهذا ستُطابَق جميع الملفات التي لها اللاحقة ‎.scss في المجلد app/scss أو أيّ مجلدٍ فرعيٍ موجودٍ داخله.

gulp.task('sass', function() {
  // scss الحصول على جميع الملفات التي تنتهي باللاحقة
  // app/scss والموجودة في المجلد
  return gulp.src('app/scss/**/*.scss')
    .pipe(sass())
    .pipe(gulp.dest('app/css'))
})

سيُضمَّن أيّ ملف Sass موجود في مجلد app/scss تلقائيًا في مهمّة sass المُعدَّلة. فلو أضفتَ ملف print.scss إلى المشروع، فستلاحظ توليد الملف print.css في مجلد app/css تلقائيًا. حسنًا، تمكّنا من تحويل جميع ملفات Sass إلى ملفات CSS بأمرٍ وحيد، لكن السؤال الآن هو: ما الفائدة من المهمّة التي أنشأناها إن كنا سنحتاج إلى تطبيق الأمر gulp sass يدويًا في كل مرة نرغب فيها بتحويل ملفات Sass إلى CSS؟ لحسن الحظ، يمكننا أن نخبر Gulp أن يُشغِّل المهمّة sass تلقائيًا في كل مرة يُحفَظ فيها أحد تلك الملفات، وهذا ما ندعوه «المراقبة» (watching).

مراقبة التغييرات في ملفات Sass

يوفِّر لنا Gulp الدالة watch لمعرفة إن حُفِظَ أحد الملفات. الشكل العام لدالة watch هو:

gulp.watch('files-to-watch', ['tasks', 'to', 'run']); 

إذا أردنا مراقبة جميع ملفات Sass وتشغيل المهمّة sass عندما يحفظ أحد تلك الملفات، فعلينا أن نضع app/scss/**/*.scss بدلًا من files-to-watch ونضع ['sass'] بدلًا من ['tasks', 'to', 'run']:

gulp.watch('app/scss/**/*.scss', ['sass']); 

وفي أغلبية الأوقات سنحتاج إلى مراقبة أكثر من نوع من الملفات في آنٍ واحد، ويمكننا إنشاء مجموعة من عمليات المراقبة مع بعضها ضمن مهمّة باسم watch:

gulp.task('watch', function(){
  gulp.watch('app/scss/**/*.scss', ['sass']); 
  // عمليات المراقبة الأخرى
})

إذا جرّبتَ تنفيذ الأمر gulp watch الآن، فسترى أنَّ Gulp قد بدأ مراقبة الملفات فوريًا.

user@linuxbox:~/gulp-test$ gulp watch
[21:10:00] Using gulpfile ~/gulp-test/gulpfile.js
[21:10:00] Starting 'watch'...
[21:10:00] Finished 'watch' after 12 ms

وأنَّ Gulp سيُنفِّذ المهمّة sass عندما تحفظ أحد ملفات ‎.scss، جرِّب الآن فتح الملف styles.scss السابق وإضافة أيّ شيء إليه وحفظه، ثم انظر إلى ناتج Gulp:

abd@linuxbox:~/gulp-test$ gulp watch
[21:10:00] Using gulpfile ~/gulp-test/gulpfile.js
[21:10:00] Starting 'watch'...
[21:10:00] Finished 'watch' after 12 ms
[21:12:03] Starting 'sass'...
[21:12:03] Finished 'sass' after 69 ms

ما رأيك أن نخطو خطوةً إلى الأمام ونجعل Gulp يُعيد تحميل الصفحة عندما نحفظ أحد ملفات ‎.scss لكي يظهر تأثير تغيير الصفحة مباشرةً على المتصفح، وذلك بالاستعانة بأداة Browser Sync.

تحديث الصفحة آنيًا باستخدام Browser Sync

يُسهِّل Browser Sync من تطوير الويب بإنشاء خادوم ويب الذي يساعدنا على تحديث الصفحات آنيًا عند تغييرها. وله ميزاتٌ أخرى، مثل مزامنة الأحداث بين أكثر من جهاز. لنبدأ أولًا بتثبيت الأداة Browser Sync كالمعتاد:

npm install browser-sync –save-dev

علينا بعدئذٍ أن نُضمِّن Browser Sync عبر التعليمة require:

var browserSync = require('browser-sync').create();

سنحتاج إلى إنشاء مهمّة باسم browserSync للسماح لأداة Gulp بتشغيل خادوم ويب باستخدام Browser Sync، ولأننا سنُنشِئ خادوم ويب، فعلينا أن نُحدِّد للخادوم ما هو المجلد الرئيسي للموقع، والذي هو في حالتنا المجلد app:

gulp.task('browserSync', function() {
  browserSync.init({
    server: {
      baseDir: 'app'
    },
  })
})

علينا أيضًا تغيير المهمّة sass قليلًا لكي يتمكن Browser Sync من تحديث أنماط CSS في المتصفح عندما تُشغَّل المهمّة sass:

gulp.task('sass', function() {
  return gulp.src('app/scss/**/*.scss') 
    .pipe(sass())
    .pipe(gulp.dest('app/css'))
    .pipe(browserSync.reload({
      stream: true
    }))
});

انتهينا الآن من ضبط الأداة Browser Sync، لكن علينا تشغيل المهمّتَين watch و browserSync في نفس الوقت لكي تُحدَّث الصفحة آنيًا.

من غير المقبول فتح نافذَتي سطر أوامر وتشغيل gulp browserSync في إحداها و gulp watch في الأخرى، لذا لنجعل Gulp يشغِّلهما معًا بإخبار المهمّة watch أنَّه يجب إكمال المهمّة browserSync قبل السماح بتنفيذ watch.

يمكننا فعل ذلك بتمرير وسيطٍ ثانٍ إلى المهمّة watch. الشكل العام هو:

gulp.task('watch', ['array', 'of', 'tasks', 'to', 'complete','before', 'watch'], function (){
  // ...
})

وفي حالتنا هذه سنضيف المهمّة browserSync:

gulp.task('watch', ['browserSync'], function (){
  gulp.watch('app/scss/**/*.scss', ['sass']); 
  // عمليات المراقبة الأخرى
})

علينا أيضًا أن نتأكد أنَّ المهمّة sass ستعمل قبل watch لكي يتم تحويل أيّة ملفات sass حُفِظَت قبل تشغيل Gulp إلى ملفات CSS.

gulp.task('watch', ['browserSync', 'sass'], function (){
  gulp.watch('app/scss/**/*.scss', ['sass']); 
  // عمليات المراقبة الأخرى
});

إذا شغَّلتَ gulp watch في سطر الأوامر، فسيُشغِّل Gulp المهمّتين sass و browserSync ثم بعد إكمالهما ستُشغَّل المهمّة watch. ستجد في الناتج رابط URL شبيه بالرابط الآتي http://localhost:3000 الذي إذا فتحته في نافذة المتصفح، فسترى صفحة app/index.html التي يجب أن يكون محتواها شبيهًا بما يلي:

<!doctype html>
<html>
<head><link rel="stylesheet" href="css/styles.css"></head>
<body></body>
</html>

شغِّل الآن المتصفح وادخل إلى الرابط السابق وستعرض أمامك صفحة فارغة، ولتجربة التحديث الآني للصفحة، فافتح ملف styles.scss وأضف لونًا للخلفية (مثلًا: body {background-color: red;} واحفظ الملف وستجد أنَّ الصفحة قد حُدِّثَت تلقائيًا!

ما رأيك الآن أن نضيف قليلًا على المهمّة السابقة ونراقب تغيير ملفات HTML أو JavaScript (بالإضافة إلى ملفات ‎.sass) ومن ثم إعادة تحميل الصفحة حينها؟

يمكننا فعل ذلك بإضافة عمليتَي مراقبة، واستدعاء الدالة browserSync.reload عند حفظ الملف:

gulp.task('watch', ['browserSync', 'sass'], function (){
  gulp.watch('app/scss/**/*.scss', ['sass']); 

  // تحديث الصفحة عند حفظ هذه الملفات
  gulp.watch('app/*.html', browserSync.reload); 
  gulp.watch('app/js/**/*.js', browserSync.reload); 
});

حتى الآن قمنا بثلاثة أمور:

  1. تشغيل خادوم ويب للتطوير
  2. تحويل ملفات Sass إلى CSS
  3. إعادة تحميل الصفحة في المتصفح عند حفظ الملفات

سنشرح في القسم الآتي كيفية تحسين الملفات الملحقة بصفحات الويب، وسنبدأ بتحسين ملفات CSS و JavaScript.

تحسين ملفات CSS و JavaScript

يُجري المطورون مهمّتَين عندما يحاولون تحسين ملفات CSS و JavaScript: تصغير الملفات وجمعها في ملفٍ واحد. إحدى المشاكل التي يواجهها المطورون عند أتمتة هذه العملية هي الصعوبة في جمع السكربتات بترتيبٍ صحيح. لنقل أننا أضفنا 3 وسوم script في صفحة index.html:

<body>
  <script src="js/lib/a-library.js"></script>
  <script src="js/lib/another-library.js"></script>
  <script src="js/main.js"></script>
</body>

هذه السكربتات موجودة في مجلدين مختلفين، ومن الصعب جمعها باستخدام الإضافات التقليدية مثل gulp-concatenate؛ لكن لحسن الحظ، تأتي إضافة gulp-useref لتحل لنا هذه الإشكالية.

تجمع إضافة gulp-useref أيّ عدد من ملفات CSS أو JavaScript إلى ملفٍ وحيد بالبحث عن تعليق يبدأ بالتعبير ‎<!--build:‎ وينتهي بالتعبير <‎!--endbuild--‎> الشكل العام له هو:

<!-- build:<type> <path> -->
... HTML Markup, list of script / link tags.
<!-- endbuild -->

النوع <type> يمكن أن يكون إما js أو css أو remove. من الأفضل تحديد نوع type للملفات التي تحاول جمعها؛ وإذا ضَبطتَ type إلى remove فسيَحذف Gulp الشيفرةَ المُحدَّدةَ ولن يولِّد ملفًا. أما <path> فيشير إلى مسار الملف الذي سيولّد.

إذا أردتَ أن يكون ملف JavaScript المولَّد في مجلد js باسم main.min.js فستبدو الشيفرة كالآتي:

<!--build:js js/main.min.js -->
  <script src="js/lib/a-library.js"></script>
  <script src="js/lib/another-library.js"></script>
  <script src="js/main.js"></script>
<!-- endbuild -->

لنضبط الآن إضافة gulp-useref في ملف gulpfile.js. علينا أولًا تثبيت الإضافة ثم تضمينها في الملف. نفِّذ الأمر الآتي للتثبيت:

npm install gulp-useref –save-dev

أضف السطر الآتي إلى ملف gulpfile.js:

var useref = require('gulp-useref');

تهيئة المهمّة useref شبيهة بتهيئة المهام الأخرى التي أنجزناها من قبل. هذه هي الشيفرة:

gulp.task('useref', function(){
  return gulp.src('app/*.html')
    .pipe(useref())
    .pipe(gulp.dest('dist'))
});

إذا شغّلتَ مهمّة useref الآن فسيأخذ Gulp ملفات JavaScript المُحدَّدة وسيجمعها في ملف dist/js/main.min.js.

لكن الملف لم يُصغَّر (minified) إلى الآن، وعلينا استخدام إضافة gulp-uglify لفعل ذلك. وسنحتاج أيضًا إلى إضافة تدعى gulp-if لكي نُصغّر ملفات JavaScript دونًا عن غيرها. تثبيت الإضافة:

npm install gulp-uglify --save-dev 

الشيفرة التي سنضيفها إلى ملف gulpfile.js:

var uglify = require('gulp-uglify');
var gulpIf = require('gulp-if');

gulp.task('useref', function(){
  return gulp.src('app/*.html')
    .pipe(useref())
    // JavaScript التصغير إذا كان الملف
    .pipe(gulpIf('*.js', uglify()))
    .pipe(gulp.dest('dist'))
});

يجب أن يولِّدَ Gulp الملفَ main.min.js في كل مرة تشغِّل فيها المهمّة useref. إحدى الأمور الرائعة التي لم أخبرك عنها بعد هي أنَّ إضافة gulp-useref ستحوِّل جميع السكربتات الموجودة بين ‎<!--build:‎ و <‎!--endbuild--‎> إلى ملف JavaScript وحيد الذي يُشير إلى js/main.min.js وذلك في صفحة index.html، أي أنَّ ملف index.html الناتج سيكون شبيهًا بما يلي:

<!doctype html>
<html>
<head></head>
<body>
    <script src="js/main.min.js"></script>
</body>
</html>

سنستعمل نفس الطريقة لجمع ملفات CSS:

<!--build:css css/styles.min.css-->
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="css/another-stylesheet.css">
<!--endbuild-->

يمكننا أيضًا تصغير ملف CSS الناتج، لكننا بحاجة إلى تثبيت الإضافة gulp-cssnano:

npm install gulp-cssnano

الشيفرة التي سنضيفها إلى ملف gulpfile.js:

var cssnano = require('gulp-cssnano');

gulp.task('useref', function(){
  return gulp.src('app/*.html')
    .pipe(useref())
    .pipe(gulpIf('*.js', uglify()))
    // CSS التصغير إذا كان الملف
    .pipe(gulpIf('*.css', cssnano()))
    .pipe(gulp.dest('dist'))
});

ستحصل الآن على ملف CSS وملف JavaScript وحيد ومُصغّر في كل مرة تُشغِّل فيها المهمّة useref.

تحسين دعم المتصفحات لخاصيات CSS وشيفرة JavaSctipt

نبدأ بتحسين دعم خاصيات CSS عبر مختلف المتصفحات. فمن المؤكد أنك رأيت شيفرة CSS مكتوبة بالشكل التالي:

.navigation {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex
}

ولابد أنك استصعبت - أثناء كتابة شيفرة CSS - البحث عن دعم كل خاصية من خاصيات CSS لمختلف المتصفحات وتساءلت عن وجود أداة تضيف السوابق الخاصة بدعم الخاصيات في المتصفحات الأخرى مثل ‎-webkit-box و ‎-ms-flexbox. الحل بسيط جدًا وهو استعمال الإضافة gulp-autoprefixer الموجودة لهذا الغرض. نفذ الأمر التالي لتثبيت هذه الإضافة:

npm install gulp-autoprefixer --save-dev

أضف السطر الآتي إلى ملف gulpfile.js:

const autoprefixer = require('gulp-autoprefixer');

تهيئة واستعمال هذه الإضافة - مع الإضافة gulp-sass والإضافة browser-sync التي تحدثنا عنهما في الأعلى - يكون بالشكل التالي:

gulp.task("styles", function() {
  gulp
    .src("app/css/**/*.scss")
    .pipe(sass().on("error", sass.logError))
    .pipe(autoprefixer({
        // دعم آخر إصدارين للمتصفح
        browsers: ["last 2 versions"] 
      })
    )
    .pipe(gulp.dest("css"))
    .pipe(browserSync.stream());
});

نحصل بذلك على شيفرة CSS السابقة عند كتابة:

.navigation {
  display: flex
}

فقط لتصبح الشيفرة مدعومة على كافة المتصفحات التي لا تدعم خاصيات CSS محدَّدة مثل التي رأيناها للتو.

ملاحظة: إن أردت استعمال الإضافة gulp-autoprefixer مع الإضافة gulp-cssnano (وإضافات أخرى)، فيمكنك استعمالهما سوية عبر استعمال الإضافة gulp-postcss. تمرر هذه الإضافة شيفرة CSS إلى عدة إضافات مع تحليل الشيفرة مرةً واحدةً مما يزيد من سرعة تنفيذ العملية.

var postcss = require('gulp-postcss');
var gulp = require('gulp');
var autoprefixer = require('autoprefixer');
var cssnano = require('cssnano');

gulp.task('css', function () {
    // CSS تحديد الإضافات المراد استعمالها مع شيفرة 
    var plugins = [
        autoprefixer({browsers: ['last 2 version']}),
        cssnano()
    ];
    return gulp.src('app/css/*.css')
        .pipe(postcss(plugins))
        .pipe(gulp.dest('./dest'));
});

اطلع على توثيق الإضافة الرسمي لمزيد من الأمثلة وكيفية الاستخدام.

ننتقل الآن إلى تحسين دعم شيفرة JavaScript وأتحدث الآن عن دعم ميزات ES6 عبر مختلف المتصفحات. الحقيقة أن الميزات التي يوفرها الإصدار ES6 (أو ECMAScript 6) مغرية جدًا لجميع مطوري الويب ولكن نقص الدعم في مختلف المتصفحات والإصدارات القديمة هو من أكبر العقبات أمام الاستفادة من تلك الميزات. انطلاقًا من ذلك، جاءت فكرة وجود محول يدعى transpiler والذي يحول من لغة برمجية معينة إلى برمجية أخرى (في حالتنا من ECMAScript 6 إلى ES2015 أو ما قبلها). المحول الذي سنختاره هنا هو المحول الشهير Babel JS الذي يحوي الكثير من الميزات والمدعوم بقوة من قبل المجتمع.

سنستعمل الإضافة gulp-babel لهذا الغرض. ثبت الإضافة عبر الأمر التالي:

# Babel 6
npm install --save-dev gulp-babel
# Babel 7
npm install --save-dev gulp-babel@next @babel/core

أضف الشيفرة التالية إلى ملف gulpfile.js:

var babel = require("gulp-babel");

gulp.task("default", function () {
  return gulp.src("app/js/*.js")
    // تحويل الشيفرة إلى الإصدار الأقدم
    .pipe(babel())
    .pipe(gulp.dest("dist"));
});

تنبيه: احرص على استدعاء babel()‎ قبل إجراء أية عملية تصغير أو ضغط على شيفرة JavaScript.

تحسين الصور

أظن أنك ستتوقع أننا سنحتاج إلى تثبيت إضافة لمساعدتنا في موضوع تحسين الصور، وهي gulp-imagemin. الضغط الذي ستوفره هذه الإضافة هو الضغط غير الفَقُود (lossless compression).

npm install gulp-imagemin –save-dev

يمكننا تصغير صورة png و jpg و gif وحتى svg باستخدام إضافة gulp-imagemin. لننشِئ مهمّة images لهذا الغرض:

var imagemin = require('gulp-imagemin');

gulp.task('images', function(){
  return gulp.src('app/images/**/*.+(png|jpg|gif|svg)')
  .pipe(imagemin())
  .pipe(gulp.dest('dist/images'))
});

ولأن كلُّ نوعٍ من أنواع الصور سيُحسّن بطريقةٍ مختلفة، فربما ترغب بإضافة بعض الخيارات إلى imagemin لتخصيص كيفية تحسين الصورة. على سبيل المثال، يمكننا إنشاء صورة GIF متداخلة (interlaced) بضبط قيمة الخيار interlaced إلى ture.

gulp.task('images', function(){
  return gulp.src('app/images/**/*.+(png|jpg|jpeg|gif|svg)')
  .pipe(imagemin({
      interlaced: true
    }))
  .pipe(gulp.dest('dist/images'))
});

إن كانت تريد إجراء عملية ضغط فقود (lossy compression)، والذي سيخفّض حجم الصورة بشكل كبيرة على حساب الجودة، فاستعمل الإضافة imagemin-pngquant.

npm install imagemin-pngquant –save-dev

خيار الضغط الذي توفره هذه الإضافة يتمتع بالذكاء والذي يدعى PNG quantization (توضيح صور PNG)، إذ يعمل على اللعب بالألوان التي يراها دماغنا على أنها تقريبًا متماثلة محولًا الصورة إلى حجم 256 أو 8 بت للألوان. أفضل شيء في هذه الإضافة هو أنها لن تعدل أي شيء على الصور إن لم تتحقق حدود معينة متعلقة بالجودة. سنعدل على المثال السابق لاستعمال pngquant() مع imagemin بالشكل التالي:

var imagemin = require('gulp-imagemin');

gulp.task('default', function() {
    return gulp.src('app/images/*')
        .pipe(imagemin({
            progressive: true,
            use: [pngquant()]
        }))
        .pipe(gulp.dest('dist/images'));
});

على أي حال، عملية تحسين الصور هي عمليةٌ بطيئةٌ للغاية، ولا ترغب في تكرارها إلا إذا كان ذلك ضروريًا، ويمكننا تخزينها مؤقتًا باستخدام إضافة gulp-cache.

npm install gulp-cache –save-dev

الشيفرة التي سنضيفها إلى ملف gulpfile.js:

var cache = require('gulp-cache');

gulp.task('images', function(){
  return gulp.src('app/images/**/*.+(png|jpg|jpeg|gif|svg)')
  // تخزين الصور المُحسّنة مؤقتًا
  .pipe(cache(imagemin({
      interlaced: true
    })))
  .pipe(gulp.dest('dist/images'))
});

انتهينا تقريبًا من عمليات التحسين، وبقي مجلدٌ أخير يجب علينا نقله من مجلد app إلى dist وهو مجلد الخطوط.

لمزيد من التفاصيل حول تحسين الصور في موقعك، ننصحك بالإطلاع على مقال «دليلك الشامل لتحسين أداء الصور على موقعك».

نسخ الخطوط إلى مجلد dist

لن تحتاج الخطوط إلى أيّة عمليات تحسين، وكل ما علينا فعله هو نسخها إلى مجلد dist. يمكننا نسخ الملفات باستخدام Gulp ببساطة باستخدام الدالتين gulp.src و gulp.dest دون أيّة إضافات:

gulp.task('fonts', function() {
  return gulp.src('app/fonts/**/*')
  .pipe(gulp.dest('dist/fonts'))
})

سينسخ Gulp مجلد fonts من app إلى dist في كل مرة تشغِّل فيها الأمر gulp fonts. أصبحت لدينا ست مهام مختلفة في ملف gulpfile.js، ويجب علينا استدعاؤها يدويًا باستخدام سطر الأوامر. ربما نفكّر بربط المهام جميعًا إلى أمرٍ وحيد، لكن قبل ذلك لننظر إلى كيفية حذف الملفات المولّدة تلقائيًا.

حذف الملفات المولدة تلقائيًا

لمّا كنّا نولِّد الملفات تلقائيًا، فعلينا أن نتأكد أنَّ الملفات التي لم تعد مستخدمةً لن تبقَ موجودةً دون علمنا. سنحتاج إلى استخدام del لمساعدتنا في ذلك.

npm install del –save-dev

الدالة del تأخذ مصفوفةً من محارف التحديد (globs) التي تخبرها ما هي المجلدات التي يجب حذفها. يمكننا استخدامها كمهمّة في Gulp كما فعلنا من قبل:

var del = require('del');

gulp.task('clean:dist', function() {
  return del.sync('dist');
})

سيَحذف Gulp المجلد dist في كل مرة تُشغِّل فيها الأمر gulp clean:dist.

ملاحظة: لا حاجة أن نقلق من حذف مجلد dist/images لأنَّ إضافة gulp-cache خزَّنت نسخةً مؤقتةً من الصور في نظامك. ستحتاج إلى إنشاء مهمّة منفصلة لحذف النسخة المؤقتة وليكن اسمها cache:clear:

gulp.task('cache:clear', function (callback) {
return cache.clearAll(callback)
})

جمع مهام Gulp مع بعضها

ما فعلناه إلى الآن هو إنشاء مجموعتين منفصلتين من مهمات Gulp.

الغرض من المجموعة الأولى هو المساعدة في التطوير، حيث حوّلنا ملفات Sass إلى CSS، وراقبنا تغيرات الملفات، وأعدنا تحميل الصفحة في متصفح الويب وقت الحاجة. أما المجموعة الثانية فكانت لتحسين الملفات الملحقة بالصفحة، حيث جهّزنا جميع الملفات للموقع الإنتاجي، وذلك بدمج ملفات CSS و JavaScript وتصغيرها، وتحسين الصور ونسخ الخطوط من app إلى dist.

لقد وضعنا أول مجموعة من المهمات في بُنية يمكن تشغيلها باستخدام الأمر gulp watch:

gulp.task('watch', ['browserSync', 'sass'], function (){
  // ... 
})

أما المجموعة الثانية فتستخدم لإنشاء الموقع الإنتاجي، وهي تتضمن المهمات clean:dist و sass و useref و images و fonts.

يمكننا إنشاء مهمة باسم build التي ستجمع كل ما سبق في مهمة واحدة.

gulp.task('build', [`clean`, `sass`, `useref`, `images`, `fonts`], function (){
  console.log('Building files');
})

للأسف لا يمكننا كتابة المهمّة build بهذه الطريقة لأن Gulp سيشغِّل المهمات المُمرَّرة كوسيطٍ ثانٍ إلى الدالة task معًا وليس بالترتيب.

فهنالك احتمالٌ أن ينتهي تنفيذ المهمات useref أو images أو حتى fonts قبل إكمال المهمّة clean التي تُسبِّب حذف كامل مجلد dist! لذا لضمان أنَّ المهمات ستُنفَّذ بترتيبٍ صحيح، فسنحتاج إلى استخدام إضافة خارجية باسم Run Sequence:

npm install run-sequence –save-dev

وهذه هي البنية العامة لطريقة تشغيل سلسلة من المهمّات:

var runSequence = require('run-sequence');

gulp.task('task-name', function(callback) {
  runSequence('task-one', 'task-two', 'task-three', callback);
});

عندما تستدعى المهمّة task-name فسيُشغِّل Gulp المهمّة task-one أولًا، وبعد انتهاء تنفيذها سيُشغِّل المهمة task-two، ثم task-three وهكذا.

تسمح لك إضافة Run Sequence بتشغيل المهمات معًا إذا وضعتَها في مصفوفة:

gulp.task('task-name', function(callback) {
  runSequence('task-one', ['tasks','two','run','in','parallel'], 'task-three', callback);
});

في هذه الحالة، سيُشغِّل Gulp المهمّة task-one، وبعد انتهاء تنفيذها فسيُشغِّل جميع المهمات الموجودة في المصفوفة معًا، وجميع المهمات الموجودة في المصفوفة يجب أن ينتهي تنفيذها قبل تنفيذ task-three.

لذا يمكننا الآن إنشاء مهمة التي تُشغِّل المهمّة clean:dist أولًا، ثم تتبعها بقية المهمات:

gulp.task('build', function (callback) {
  runSequence('clean:dist', 
    ['sass', 'useref', 'images', 'fonts'],
    callback
  )
})

ولجعل الملف متناسقًا، فسنستخدم نفس الطريقة مع أوّل مجموعة من المهمات، وسنستخدم الاسم default كاسمٍ للمهمّة:

gulp.task('default', function (callback) {
  runSequence(['sass','browserSync', 'watch'],
    callback
  )
})

لماذا اخترنا الاسم default؟ لأنّ المهمّة ذات الاسم default ستُشغَّل إن كَتبتَ الأمر gulp دون تمرير اسم المهمة له، مما يوفِّر عليك بعض الوقت :-) .

الخلاصة

بدأنا درسنا ونحن لا نعرف شيئًا عن Gulp، ثم كتبنا مهمةً بسيطةً لتحويل ملفات Sass إلى CSS ومراقبة ملفات HTML و JS، وتعلمنا كيف نشغِّل تلك المهمّة في سطر الأوامر باستخدام الأمر gulp.

ثم بنينا بعد ذلك مهمّةً ثانيةً، ألا وهي build، والتي تُنشِئ المجلد dist لكي يحتوي على ملفات الموقع الإنتاجي، وحوّلنا ملفات Sass إلى CSS وأجرينا عمليات تحسين على ملحقات الصفحة، ونسخنا المجلدات الضرورية إلى مجلد dist؛ وتمكنّا من تنفيذ كل ما سبق بأمرٍ وحيد وهو gulp build.

في النهاية، أنشأنا المهمّة clean التي تحذف المجلد dist وبالتالي سنحذف أيّة ملفات غير مستخدمة في الموقع. لا تقف عند هذا الحد! Gulp واسع جدًا وإمكانياته لا تنتهي، وأرى أنَّ عليك تصفّح الإضافات الخاصة به والموجودة في الموقع الرسمي، وعليك الاستعانة بتوثيق API إن أردت رؤية توثيق دوال Gulp.

المصادر



3 اشخاص أعجبوا بهذا


تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن