حسّن منهجيّتك في تطوير الواجهات 2 – Grunt

نعود اليوم لموضوع تطوير الواجهات والأدوات التي تساعدنا على تخفيف الأعباء الروتينية للتركيز على الأجزاء الممتعة من العمل. هناك الكثير لنفعله في هذه المقالة، جهز كوباً من الشاي ولنبدأ.

حسّن منهجيّتك في تطوير الواجهات 2 – Grunt

سأفترض أنك قرأت الجزء الأول من هذه السلسلة ونفذت ما ورد ضمنه، وهذا يعني أنك تملك Node.js على حاسوبك.

كما ذكرت سابقاً، الخروج عما نعرفه وتبني أدوات جديدة ليس بالأمر السهل. لكن منذ فترة صادفني مقال بعنوان Grunt for People Who Think Things Like Grunt are Weird and Hard ورأيت أن هذا العنوان ينطبق علي تماماً.

يوضح Chris Coyier خلال المقال بأن أحد أسباب ابتعادنا عن الأدوات الجديدة هو حاجة غريبة في أذهاننا لأن نعرف معظم تفاصيل عملها، ويثبت أننا عملياً لسنا بحاجة لذلك مع جميع أدواتنا، وهذا ما دفعني في النهاية لمحاولة استخدام أدوات جديدة وكتابة هذه السلسلة من المقالات.

مشغل المهام Grunt

يحتاج مصممو ومطورو الواجهات للقيام بالكثير من المهام الروتينية (بمعزل عن التطوير الفعلي) لضمان أفضل أداء وتوافق لما ينتجونه، فهم خلال اليوم بحاجة بشكل دائم إلى ترجمة ملفات LESS أو SASS (لا تقل لي أنك لا زلت تكتب CSS) وتجميع وضغط ملفات CSS و JavaScript وتنفيذ الاختبارات وضغط الصور… إلخ

يتم تجاهل الكثير من هذه المهام في عالمنا العربي، لكنها أصبحت واقعاً وأمراً مفروضاً في عالم الويب اليوم، ولا داعي لأن نحتفظ بمنهجياتنا المنقرضة لنرضي كسلنا وإلا انقرضنا نحن أيضاً. إذا كنت من هذه الفئة فيمكنك أن تبدأ منذ الآن مع أداة تسهل عليك كل ذلك، وإن كنت من القلة المتميزة التي تقوم بهذه المهام فأنا أهنئك على مثابرتك.

Grunt و Grunt CLI

للعمل مع Grunt تحتاج إلى حزمتين من Node.js. الأولى هي Grunt التي نقوم بتنصيبها واستخدامها مع كل مشروع. والثانية هي Grunt CLI التي يتم تنصيبها بشكل عام (Global) لمرة واحدة ولها وظيفة واحدة وبسيطة وهي تشغيل نسخة Grunt الموجودة ضمن كل مشروع (بجوار ملف Gruntfile – سنصل لذلك لاحقاً)، هذا يسمح لنا باستخدام إصدارات مختلفة من Grunt باختلاف المشاريع التي نعمل عليها.

إذاً قبل أن نبدأ، عليك تنصيب Grunt CLI كما يلي:

npm install -g grunt-cli

بنية المشروع

لتهيئة مشروعنا لاستخدام Grunt نحتاج لملفين بشكل أساسي:

  1. package.json يحوي معلومات عن المشروع والحزم التي نستخدمها بشكل مشابه لما شاهدناه مع Bower.
  2. Gruntfile.js (أو يمكن أن يكون Gruntfile.coffee) ويحوي الإعدادات اللازمة لتشغيل المهام التي سيقوم Grunt بتشغيلها، يشار إليه عادة باسم Gruntfile فقط.

قم أولاً بإنشاء ملف package.json ضمن مجلد مشروعك بالمحتوى التالي:

{
    "name": "grunt-demo",
    "version": "0.1.0",
    "devDependencies": {
        "grunt": "~0.4.2"
    }
}

بعد ذلك افتح سطر الأوامر وتوجه إلى مجلد المشروع، قوم بتنفيذ الأمر التالي:

npm install

سينتج عن ذلك إنشاء مجلد node_modules ضمن مشروعك، وسيحوي بدوره مجلد grunt الذي تم تنصيبه ضمن المشروع.

العمل مع المهام

تنصيب Grunt ببساطة لا يحقق شيئاً لمشروعك. بما أنه مشغل مهام عليك أن تعطيه بعض المهام لتأديتها، وسنوضح ذلك وفق بعض الأمثلة الشائعة، لكن بشكل عام يتم العمل مع المهام وفق المسار التالي:

  1. تنصيب حزمة المهمة المطلوبة باستخدام npm install
  2. إضافة الحزمة لملف package.json (يمكن اختصار هذه الخطوة مع سابقتها باستخدام خيار save-dev–) عند تنصيب الحزمة.
  3. إضافة إعدادات تشغيل المهمة إلى Gruntfile.

بعد ذلك كلما أردنا تنفيذ المهام نقوم بتنفيذ أمر grunt (لكن سنقوم بأتمتة ذلك أيضاً كما سنرى).

مثال 1: دمج ملفات جافاسكريبت

كل مهمة في Grunt هي عملياً حزمة من Node.js تقوم بإضافتها لمشروعك، هناك العديد من الحزم التي تعمل مع Grunt لكن من المستحسن العمل مع المهام التي يقوم فريق Grunt نفسه بإدارتها ضمن حسابهم على GitHub، وأسماؤها عادة لها البادئة grunt-contrib.

لنفترض أنك في مشروعك تستخدم مكتبتي jQuery و Slip ضمن مجلد libs بالإضافة لملفك الخاص global.js وكل ذلك موجود ضمن مجلد js في مشروعك.

لنبدأ بتنصيب الحزمة grunt-contrib-concat التي ستقوم بدمج الملفات.

npm install grunt-contrib-concat --save-dev

هذا سيقوم بتنصيب الحزمة وإضافتها إلى package.json. ننتقل لإنشاء ملف Gruntfile.js، لا تقلق بشأن فهم كل ما يحتويه الملف لكن حاول أن تفهم أقسامه بحسب التعليقات:

module.exports = function(grunt) {

    // 1. إعدادات تشغيل كافة المهام.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        concat: {
            // 1.1. إعدادات تشغيل مهمة دمج الملفات.
        }

    });

    // 2. أسماء الحزم التي نستخدمها.
    grunt.loadNpmTasks('grunt-contrib-concat');

    // 3. قائمة المهام التي تنفذ عند تنفيذ أمر grunt.
    grunt.registerTask('default', ['concat']);

};

سينمو هذا الملف مع كل مهمة نضيفها. القسمان الثاني والثالث (بحسب التعليقات) لن يكون من الصعب التعامل معهما، أما بالنسبة للقسم الأول فعليك قراءة ملفات دعم المهة لكي تعرف طريقة إعدادها لأن ذلك يتغير من مهمة إلى أخرى.

بالنسبة لدمج الملفات، سنقوم بإعداد المهمة لتدمج الملفات بدءاً بالمكتبات الموجودة ضمن مجلد libs ثم ملفنا الخاص لنضمن تحميل المكتبات أولاً. سندرج هذه الإعدادات ضمن قسم concat من Gruntfile:

concat: {
    dist: {
        src: [
            'js/libs/*.js', // المكتبات
            'js/global.js'  // الملف الذي قمنا بكتابته
        ],
        dest: 'js/build/production.js',
    }
}

نقوم بتجربة أمر grunt، من المفروض أن يعطينا رسالة النجاح (Done, without errors) وأن نجد ضمن مجلد js مجلداً باسم build يحوي ملف production.js الذي يحوي كامل شيفرة جافاسكريبت لمشروعنا، لنختصر بذلك عدة طلبات HTTP إلى طلب واحد، وهذا سيحسن سرعة تحميل صفحاتنا بشكل كبير. لكن لا تستخدم هذا الملف مع صفحاتك بعد، فهنالك المزيد!

مثال 2: ضغط الشيفرة البرمجية

يمكننا بعد أن قمنا بتجميع ملفات جافاسكريبت أن نسير خطوة أخرى نحو أداء أفضل وذلك بضغط الملف الناتج باستخدام حزمة grunt-contrib-uglify، لنقم بتنصيبها:

npm install grunt-contrib-uglify --save-dev

كما ذكرنا، يتكفل هذا الأمر بتنصيب الحزمة وإضافتها إلى package.json. نضيف إعدادات تشغيلها في Gruntfile بعد إعدادات المهمة السابقة:

concat: {
    ...
}, // لا تنسى إضافة الفاصلة
uglify: {
    build: {
        src: 'js/build/production.js', // المصدر - ناتج المهمة السابقة
        dest: 'js/build/production.min.js' // مسار الملف المضغوط
    }
}

لا بد أنك لاحظت بعض الفوارق بين إعدادات هذه المهمة وسابقتها، حيث تختلف الإعدادات من مهمة لأخرى بحسب الغرض منها وما يراه المطورون مناسباً لها. كل ما عليك هو قراءة مستندات توثيق كل مهمة لتعرف طريقة إعدادها، ليس ذلك بالأمر الصعب.

وطبعاً يجب ألا ننسى إضافة المهمة الجديدة في القسمين الأخيرين من الملف، لاحظ الأسطر المحددة فيما يلي:

// 2. أسماء الحزم التي نستخدمها.
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');

// 3. قائمة المهام التي يتم تشغيلها عند تنفيذ أمر grunt.
grunt.registerTask('default', ['concat', 'uglify']);

في حال قمنا بتعديل الإعدادات بشكل صحيح يجب أن ينتج عن تنفيذ أمر grunt تنفيذ المهام المسجلة بشكل متتال، فيتم تجميع الملفات في ملف واحد مساره js/build/production.js وبعدها يتم ضغط هذا الملف إلى js/build/production.min.js وهذا الأخير هو الملف الذي يجب أن نستخدمه ضمن صفحاتنا.

رائع! أليس كذلك؟

مثال 3: ترجمة ملفات SASS إلى CSS

لمن لا يعرف: SASS هي إحدى حزم Ruby، وتتواجد مهمة من Grunt لترجمة ملفات SASS إلى CSS لكنها تحتاج إلى وجود SASS (وبالتالي Ruby) على حاسوبك، لا بد أنك خمنت أن اسمها grunt-contrib-sass.

مررنا بما يكفي على موضوع تنصيب المهمات وإضافتها للقسمين الأخيرين من Gruntfile، لذا سأذكر إعدادات تنفيذ المهمة فقط (أضفها بعد إعدادات uglify ولا تنس الفاصلة):

sass: {
    dist: {
        options: {
            style: 'compressed' // نمط الخرج مضغوط
        },
    files: {
        'css/build/global.css': 'css/global.scss'
        }
    }
}

بعد التأكد من صحة الإعدادات وتنفيذ أمر grunt ينتج لدينا ملف css/build/global.css الذي هو ترجمة لملف css/global.scss.

لا زلت مستيقظاً؟!..

مثال 4: للصور نصيب أيضاً

مع أني أولي عناية كبيرة لتصدير الصور من برامج التصميم لضمان توازن جيد بين النوعية والحجم، إلا أني اكتشفت وجود الكثير من الخدمات والبرمجيات التي تعطي نتائج أفضل من حيث تصغير الحجم والحفاظ على الدقة، ومنها grunt-contrib-imagemin. إعداداتها كما يلي:

imagemin: {
    dynamic: {
        files: [{
        expand: true,
        cwd: 'images/',
        src: ['*.{png,jpg,gif}'],
        dest: 'images/build/'
        }]
    }
}

صور أصغر = تحميل أسرع = زوار أسعد.

مثال 5: العبها بذكاء – دع Grunt يراقب تغيرات ملفاتك

لا بد أنك سعيد لوجود أدوات تنفذ هذه المهام الروتينية بالنيابة عنك، لكن بعد فترة ستدور في بالك النقطتان التاليتان:

  1. قد أقوم بتعديل ملف من نوع واحد فقط (صورة أو CSS مثلاً) ولا أريد تضييع وقتٍ في تنفيذ جميع مهام Grunt التي أستخدمها. يمكنك هنا استخدام أمر grunt taskname (بدل taskname باسم المهمة المطلوبة) لكن ذلك لا يعطي حلاً للنقطة الثانية.
  2. من المتعب تنفيذ أمر grunt بعد كل تعديل على ملفاتي، كما أنني قد أنسى، وبهذا أضاف Grunt عملاً روتينياً جديداً في حين كان يعدني بالتخلص من هذه الأعمال.

لحسن الحظ هناك مهمة ستساعدك في أتمتة المهام، اسمها grunt-contrib-watch. تقوم هذه المهمة بمراقبة ملفاتك وعند تغير أحدها تنفذ المهام المرتبطة به بحسب الإعدادات في Gruntfile.

بعد تنصيب الحزمة ندخل إعداداتها كما يلي:

watch: {
    scripts: {
        files: ['js/*.js'], // لسنا بحاجة لمرافبة المكتبات
        tasks: ['concat', 'uglify'], // المهمات التي ستنفذ
        options: {
            spawn: false, // لا تسألني عن هذا
        },
    },
    css: {
        files: ['css/*.scss'],
        tasks: ['sass'],
        options: {
            spawn: false,
        },
    },
    images: {
        files: ['images/*.{png,jpg,gif}'],
        tasks: ['imagemin'],
        options: {
            spawn: false,
        }
    }
}

لاحظ أنك لست بحاجة لإضافة المهمة إلى الاستدعاء grunt.registerTask في نهاية الملف، والسبب أننا سنقوم بتشغيل المهمة بتنفيذ الأمر grunt watch، حيث ستلاحظ أن سطر الأوامر لن يقبل منك أوامر وإنما سيبقى في حالة الانتظار، وعند تغيير أحد الملفات سيتم تنفيذ المهام ذات العلاقة وسترى الخرج يظهر ضمن سطر الأوامر.

لإيقاف مراقبة الملفات اضغط Ctrl+C ضمن سطر الأوامر.

ولهذه المهمة ميزة إضافية ذكية في أنها تدعم التحديث التلقائي للمتصفح (Live Reload). حيث يتم تحديث صفحة مشروعك ضمن المستعرض عند كل تعديل يطرأ على الملفات المرتبطة بمهمة المراقبة. لتنفيذ هذا قم بإضافة ما يلي لإعدادات المهمة:

watch: {
    options: {
        livereload: true,
    },
    scripts: {
        ...
    }
    ...
}

ثم نفذ الخطوات التالية:

  1. قم بتنصيب إضافة LiveReload لمستعرضك.
  2. أعد إقلاع المستعرض وادخل إلى صفحة من مشروعك واضغط زر إضافة LiveReload.
  3. أقلع مهمة المراقبة بتنفيذ أمر grunt watch.
  4. قم بتعديل أحد الملفات ولاحظ كيف يتم تنفيذ المهمات اللازمة ثم تتحدث صفحة المستعرض تلقائياً.

وبهذا أصبحت تدير مشروعك بذكاء، وأنزلت عن كتفك حملاً ثقيلاً من المهام الروتينية المملة لتولي المهام الفكرية الحقيقية الاهتمام والوقت الذي تستحقه.

ولكن…

قد تكون كتابة ملفات الإعدادات ممتعة عند تعلم أداة جديدة، لكنها تفقد رونقها مع الوقت لتبحث مجدداً عما يوفر عليك الوقت والعناء.

يمتلك Grunt أداة مرفقة هي grunt-init، تساعدك في استخدام قوالب وتجميع بعض المهام الشائعة لنوع مشروعك بشكل سهل، لكننا لن نتحدث عنها لأن استخدامها يتراجع أمام الأداة التي سنتحدث عنها في مقالة قادمة.

في الختام

كما ذكرت في البداية، هذه المقالة مستوحاة من مقالة أجنبية، كما أنها تستخدم نفس الأمثلة تقريباً، إلا أنها ليست ترجمة حرفية. يمكنكم قراءة تلك المقالة لمعلومات إضافية.

للمزيد من المعلومات حول Grunt:

كما قمت بإنشاء مستودع على GitHub يحوي جميع الأمثلة المذكورة. الصور الموجودة من أحد مشاريعنا القديمة.

لا تنسوا أني أتعلم كحال الجميع، في حال وجود أخطاء أو معلومات إضافية لا تبخلوا علينا بها في التعليقات.

انتظرونا في الجزء الثالث والأخير من هذه السلسلة!

محمود الدعاس

من مواليد دمشق. ابتدأ بعد التخرج كمبرمج ويب عام 2005 مع شركة إيميا، انتقل بعدها إلى شركة تكنوليد للأتمتة المتكاملة ومنها إلى شقيقتها أتمتة للحلول المتقدمة كمصمم غرافيك ومصمم ومطور ويب. أسس مع مجموعة من زملاء الدراسة موقع HOMELESS-PRO.com لنشر قصص مصورة باللغة العربية للشباب، و Guestra كوكالة تصميم وتطوير.

مواضيع الكاتبموقع الكاتب

لمتابعة الكاتب:
TwitterFacebookPinterest

One thought on “حسّن منهجيّتك في تطوير الواجهات 2 – Grunt

  1. تنبيه: حسّن منهجيّتك في تطوير الواجهات 4 – Brunch | مدونة جيسترا

أضف تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *