تتوفر عدِّة «أدوات بناء» (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 تنضوي تحت لواء الأنماط الآتية:
-
النمط
*.scss
: رمز النجمة*
هو محرفٌ خاصٌ يُطابِق أيّة ملف في المجلد المعيّن. وفي مثالنا ستُطابَق جميع الملفات التي تنتهي باللاحقة.scss
في المجلد الرئيسي للمشروع. -
النمط
**/*.scss
: هذه حالةٌ أعم من المثال السابق، حيث ستتم مطابقة أيّة ملفات تنتهي باللاحقة.scss
في المجلد الرئيسي للمشروع وفي جميع المجلدات الفرعية الموجودة فيه. -
النمط
!not-me.scss
: الرمز!
يعني أنَّ على Gulp استثناء هذا النمط من الملفات المُحدَّدة، وهذا مفيدٌ إن شئتَ أن تستثني ملفًا من التحويل؛ وفي مثالنا سنستثني الملفnot-me.scss
. -
النمط
*.+(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); });
حتى الآن قمنا بثلاثة أمور:
- تشغيل خادوم ويب للتطوير
- تحويل ملفات Sass إلى CSS
- إعادة تحميل الصفحة في المتصفح عند حفظ الملفات
سنشرح في القسم الآتي كيفية تحسين الملفات الملحقة بصفحات الويب، وسنبدأ بتحسين ملفات 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.
المصادر
- تعتمد هذه المقالة اعتمادًا أساسيًا على مقالة Gulp for Beginners لصاحبها Zell Liew
- قائمة بالمقالات التي تتحدث عن Gulp
- المجاري (streams) في Node
- مقالة Automate Your Tasks Easily with Gulp.js لصاحبها Justin Rexroad
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.