تكبير مدخراتك

كيفية الاستفادة إلى أقصى حدّ من إعدادات التصميم

مقدمة

إذا كان عالم "غرينت" جديدًا بالنسبة لك، يمكنك البدء بقراءة مقالة "كريس كويير" الممتازة بعنوان "Grunt for People Your Things Like Grunt". بعد مقدّمة "كريس"، ستكون قد أعددت مشروعك الخاص وتذوق بعض مزايا اللعبة الرائعة.

في هذه المقالة، لن نركز على ما تفعله مكونات Grunt الإضافية المتعددة لرمز المشروع الفعلي، ولكن على عملية إنشاء Grunt نفسها. سنقدم لك أفكارًا عملية حول:

  • كيفية إبقاء ملف Gruntfile منظّمًا ومرتبًا،
  • كيفية تحسين وقت التصميم بشكل كبير
  • وكيف يتم إعلامك عند حدوث الإصدار.

حان الوقت لإخلاء مسؤولية سريع: يُعد أسلوب غرانت واحدًا من العديد من الأدوات التي يمكنك استخدامها لإنجاز المهمة. إذا كان Gulp أكثر أسلوبك، فهذا رائع! إذا كنت لا تزال ترغب في إنشاء سلسلة أدوات خاصة بك بعد استطلاع آراء الخيارات المتاحة، فلا بأس بذلك أيضًا! لقد اخترنا التركيز على Grunt في هذه المقالة نظرًا إلى منظومتها المتكاملة القوية وقاعدة مستخدميها العريقة.

تنظيم ملف Gruntfile

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

ملف Gruntfile، قبل التحسين

إليك كيف يبدو ملف Gruntfile قبل إجراء أي تحسين عليه:

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      dist: {
        src: ['src/js/jquery.js','src/js/intro.js', 'src/js/main.js', 'src/js/outro.js'],
        dest: 'dist/build.js',
      }
    },
    uglify: {
      dist: {
        files: {
          'dist/build.min.js': ['dist/build.js']
        }
      }
    },
    imagemin: {
      options: {
        cache: false
      },

      dist: {
        files: [{
          expand: true,
          cwd: 'src/',
          src: ['**/*.{png,jpg,gif}'],
          dest: 'dist/'
        }]
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-imagemin');

  grunt.registerTask('default', ['concat', 'uglify', 'imagemin']);

};

إذا كنت تقول الآن "مرحبًا! كنت أتوقع أسوأ بكثير! هذا في الواقع يمكن الحفاظ عليه!"، على الأرجح تكون على حق. ولتبسيط الأمر، قمنا بتضمين ثلاثة مكونات إضافية فقط بدون الكثير من التخصيص. سيتطلب استخدام إنتاج Gruntfile فعلي لبناء مشروع بحجم معتدل التمرير اللانهائي في هذه المقالة. لنرَ ما يمكننا فعله.

تحميل مكوّنات Grunt الإضافية تلقائيًا

عند إضافة مكوّن Grunt إضافي جديد تريد استخدامه إلى مشروعك، يجب إضافته إلى ملف package.json كتبعية npm ثم تحميله ضمن ملف Gruntfile. بالنسبة إلى المكوّن الإضافي "grunt-contrib-concat"، قد يبدو على النحو التالي:

// tell Grunt to load that plugin
grunt.loadNpmTasks('grunt-contrib-concat');

إذا ألغيت تثبيت المكوّن الإضافي الآن من خلال npm وحدثت package.json، ولكن نسيت تحديث Gruntfile، سيتوقف تصميمك. وهنا يأتي دور المكوّن الإضافي load-grunt-tasks اللطيف في مساعدتك.

في السابق، كان علينا تحميل مكونات Grunt الإضافية يدويًا، كما يلي:

grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-imagemin');

باستخدام مهامload-grunt، يمكنك تصغيرها إلى السطر التالي:

require('load-grunt-tasks')(grunt);

بعد طلب المكوّن الإضافي، سيحلّل ملف package.json، ويحدّد التبعيات التي هي مكوّنات إضافية في Grunt، وستحمّلها كلّها تلقائيًا.

تقسيم إعداد Grunt إلى ملفات مختلفة

تؤدي load-grunt-tasks إلى تقليص حجم ملف Gruntfile قليلاً في الترميز وزيادة مستوى التعقيد قليلاً، ولكن عندما تضبط تطبيقًا كبيرًا، سيظل الملف كبيرًا جدًا. وهنا يأتي دور load-grunt-config. ويتيح لك load-grunt-config تقسيم إعدادات Gruntfile حسب المهمة. علاوة على ذلك، تتضمن هذه الخدمة load-grunt-tasks ووظائفه.

مع ذلك، قد لا يكون تقسيم ملف Gruntfile مفيدًا في كل الحالات. إذا كان لديك الكثير من التكوينات المشتركة بين مهامك (أي استخدام الكثير من نماذج Grunt)، فيجب أن تكون حذرًا قليلاً.

باستخدام load-grunt-config، سيبدو ملف Gruntfile.js على النحو التالي:

module.exports = function(grunt) {
  require('load-grunt-config')(grunt);
};

نعم، هذا كل شيء حقًا، الملف بأكمله! الآن أين توجد تكوينات المهام لدينا؟

أنشئ مجلدًا باسم grunt/ في دليل Gruntfile. يشتمل المكوّن الإضافي تلقائيًا على ملفات داخل هذا المجلد تتطابق مع اسم المهمة التي تريد استخدامها. يجب أن تبدو بنية الدليل على النحو التالي:

- myproject/
-- Gruntfile.js
-- grunt/
--- concat.js
--- uglify.js
--- imagemin.js

دعونا الآن نضع تكوين المهام لكل مهمة من مهامنا مباشرةً في الملفات المعنية (سترى أن هذه في الغالب تكون مجرد نسخ ولصق من ملف Gruntfile الأصلي في هيكل جديد):

grunt/concat.js

module.exports = {
  dist: {
    src: ['src/js/jquery.js', 'src/js/intro.js', 'src/js/main.js', 'src/js/outro.js'],
    dest: 'dist/build.js',
  }
};

grunt/uglify.js

module.exports = {
  dist: {
    files: {
      'dist/build.min.js': ['dist/build.js']
    }
  }
};

grunt/imagemin.js

module.exports = {
  options: {
    cache: false
  },

  dist: {
    files: [{
      expand: true,
      cwd: 'src/',
      src: ['**/*.{png,jpg,gif}'],
      dest: 'dist/'
    }]
  }
};

إذا لم تكن كتل إعدادات JavaScript هي ما تريده، تتيح لك أداة load-grunt-tasks استخدام بنية YAML أو CoffeeScript بدلاً من ذلك. لنكتب الملف الأخير المطلوب في YAML، وهو ملف aliases. هذا ملف خاص يسجل الأسماء المستعارة للمهام، وهو شيء كان علينا فعله كجزء من ملف Gruntfile من قبل عبر الدالة registerTask. إليك مجموعتنا:

grunt/aliases.yaml

default:
  - 'concat'
  - 'uglify'
  - 'imagemin'

بهذا تكون قد انتهيت. نفِّذ الأمر التالي في الوحدة الطرفية:

$ grunt

إذا نجح كل شيء، فسيعرض هذا الآن المهمة "الافتراضية" وسيشغل كل شيء بالترتيب. لقد انتهينا من هنا بعد أن انزعنا إلى رمز Gruntfile الرئيسي إلى ثلاثة أسطر من الرمز، ولن نحتاج أبدًا إلى الضغط على كل إعدادات للمهام وإدراجها خارجيًا. لكن يا رجل، لا يزال الأمر بطيئًا جدًا في إنشاء كل شيء. لنرَ ما يمكننا فعله لتحسين ذلك.

تقليل وقت التصميم

وعلى الرغم من أن أداء وقت التشغيل ووقت التحميل في تطبيق الويب يمثل أهمية كبرى في العمل أكثر من الوقت اللازم لتنفيذ إصدار ما، إلا أن بطء الإصدار لا يزال يمثل مشكلة. ستصعّب عملية تنفيذ إصدارات تلقائية باستخدام مكونات إضافية، مثل grunt-contrib-watch أو بعد تنفيذ Git بشكل سريع بما فيه الكفاية، وستفرض عليك "عقوبة" لتشغيل الإصدار، فكلما زادت سرعة وقت الإنشاء، زادت مرونة سير العمل. إذا استغرق تنفيذ الإصدار العلني أكثر من 10 دقائق، لن تتمكّن من تشغيله إلا عند الضرورة القصوى، وستتجول لاحتساء القهوة أثناء تشغيله. وهذا عامل مهمّ لتعزيز الإنتاجية. لدينا بعض الإجراءات التي يجب تسريعها.

إصدار الملفات التي تم تغييرها بالفعل: grunt-newer

بعد إنشاء أولي لموقعك، من المحتمل أنك لم تتناول سوى بعض الملفات في المشروع عندما تتنقل إلى صفحة البناء مرة أخرى. لنفترض مثلاً أنّك غيّرت صورة في دليل src/img/ سيكون من المنطقي تشغيل صورة imagemin لإعادة تحسين الصور، ولكن بالنسبة لتلك الصورة المفردة فقط، وبالطبع، ستؤدي إعادة تشغيل concat وuglify إلى إهدار دورات وحدة المعالجة المركزية الثمينة.

بالطبع، يمكنك في أي وقت تشغيل $ grunt imagemin من الوحدة الطرفية بدلاً من $ grunt لتنفيذ مهمة ما بشكل انتقائي فقط، ولكن هناك طريقة أكثر ذكاءً. يُطلق عليه grunt-newer.

لدى Grunt-newer ذاكرة تخزين مؤقت محلية تخزّن فيها معلومات حول الملفات التي تم تغييرها بالفعل، ولا تنفذ سوى مهامك للملفات التي تغيّرت في الواقع. لنلقِ نظرة على كيفية تفعيله.

هل تريد حفظ ملف aliases.yaml؟ عليك تغييرها مما يلي:

default:
  - 'concat'
  - 'uglify'
  - 'imagemin'

لهذا:

default:
  - 'newer:concat'
  - 'newer:uglify'
  - 'newer:imagemin'

ما عليك سوى إضافة عبارة "newer: " في البداية إلى أي من مهامك، يؤدي هذا الإجراء إلى نقل ملفات المصدر والوجهة من خلال المكوّن الإضافي grunt-newer أولاً، ثم تحديد الملفات التي يجب تنفيذ المهمة فيها، إن توفّرت..

تنفيذ مهام متعددة بالتوازي: نخر متداخل

grunt-consync هو مكوّن إضافي يصبح مفيدًا حقًا عندما يكون لديك الكثير من المهام المستقلة عن بعضها البعض وتستهلك الكثير من الوقت. وهو يستخدم عدد وحدات المعالجة المركزية (CPU) في جهازك ويُنفِّذ عدة مهام بالتوازي.

وأفضل ما في الأمر هو أنّ إعداداتها بسيطة للغاية. بافتراض أنك تستخدمload-grunt-config، أنشئ الملف الجديد التالي:

grunt/concurrent.js

module.exports = {
  first: ['concat'],
  second: ['uglify', 'imagemin']
};

يتم فقط إعداد مسارات التنفيذ المتوازية بالاسمين "first" و"second". ويجب تشغيل المهمة concat أولاً، ولا يتم تشغيل أي شيء آخر في الوقت الحالي في المثال الذي نقدّمه. في المسار الثاني، نضع كلاً من uglify وimagemin، لأنّهما مستقلان عن بعضهما، ويستغرق كلاهما وقتًا طويلاً.

هذا بحد ذاته لا يفعل أي شيء حتى الآن. علينا تغيير الاسم المستعار التلقائي للمهام للإشارة إلى المهام المتزامنة بدلاً من المهام المباشرة. إليك المحتوى الجديد من قناة grunt/aliases.yaml:

default:
  - 'concurrent:first'
  - 'concurrent:second'

في حال أعدت الآن تشغيل إصدار النخر، سيشغل المكوّن الإضافي المتزامن مهمة concat أولاً، ثم ينتج سلسلتين على نواتين مختلفتين من وحدة المعالجة المركزية لتشغيل كل من imagemin وuglify بالتوازي. رائع!

ملاحظة مهمة: في مثالنا الأساسي، لن يؤدي استخدام واجهة المستخدم السريعة إلى جعل التصميم أسرع بكثير. ويرجع سبب ذلك إلى النفقات العامة التي يتم إنتاجها من خلال ظهور مثيلات مختلفة من Grunt في سلاسل محادثات مختلفة: في حالتي، ينتج عن هذه العملية أكثر من 300 ملي ثانية على الأقل.

كم من الوقت استغرقت؟ نخرجة

الآن بعد أن نقوم بتحسين كل مهمة من مهامنا، سيكون من المفيد حقًا أن نفهم مقدار الوقت المطلوب لكل مهمة فردية لتنفيذها. لحسن الحظ، يتوفر مكوّن إضافي لذلك أيضًا: time-grunt.

إنّ "time-grunt" ليس مكوّنًا إضافيًا كلاسيكيًا يمكنك تحميله كمهمة npm، بل مكوّنًا إضافيًا يمكنك تضمينه مباشرةً، على غرار upload-grunt-config. سنضيف طلبًا للنهمة الزمنية إلى Gruntfile، تمامًا كما فعلنا مع upload-grunt-config. من المفترض أن يبدو ملف Gruntfile على النحو التالي الآن:

module.exports = function(grunt) {

  // measures the time each task takes
  require('time-grunt')(grunt);

  // load grunt config
  require('load-grunt-config')(grunt);

};

يؤسفني أن أشعر بالانزعاج، ولكن هذا كل ما في الأمر. جرِّب إعادة تشغيل Grunt من خلال تطبيق Terminal ولكل مهمة (بالإضافة إلى التصميم الكلي)، من المفترض أن تظهر لك لوحة معلومات منسّقة بشكل جيّد عند وقت التنفيذ:

وقت الخروج عن المألوف

إشعارات النظام التلقائية

الآن وبعد أن أصبح لديك إصدار Grunt مُحسَّن بدرجة كبيرة يتم تنفيذه بسرعة، ووفرت لك إنشاؤه تلقائيًا بطريقة ما (مثلاً من خلال مشاهدة الملفات باستخدام ساعة مشاهدة حادة أو بعد عمليات الالتزام)، ألن يكون من الرائع أن يرسل لك النظام إشعارًا عندما يصبح تصميمك الجديد جاهزًا للاستهلاك، أم عندما يحدث أي ضرر؟ استخدِم ميزة grunt-notify في Meet.

بشكل تلقائي، توفِّر ميزة grunt-notify إشعارات تلقائية لجميع أخطاء وتحذيرات Grunt باستخدام أي نظام إشعارات متوفّر على نظام التشغيل لديك: Growl لنظام التشغيل OS X أو Windows أو Mountain Lion’s وMavericks’ Notification Center (مركز إشعارات Mountain Lion’s and Mavericks) و وأصدر الإشعارات. ما يثير الدهشة، كل ما تحتاجه للحصول على هذه الوظيفة هو تثبيت المكوّن الإضافي من npm وتحميله في Gruntfile (لا تنسَ أنّك إذا كنت تستخدم grunt-load-config أعلاه، ستكون هذه الخطوة مؤتمتة).

إليك كيف سيبدو الأمر وفقًا لنظام التشغيل الذي تستخدمه:

Notify

بالإضافة إلى الأخطاء والتحذيرات، يمكننا ضبطها حتى تعمل بعد انتهاء المهمة الأخيرة من تنفيذها. لنفترض أنّك تستخدِم grunt-load-config لتقسيم المهام بين الملفات، إلّا أنّ هذا هو الملف الذي سنحتاج إليه:

grunt/notify.js

module.exports = {
  imagemin: {
    options: {
      title: 'Build complete',  // optional
        message: '<%= pkg.name %> build finished successfully.' //required
      }
    }
  }
}

في المستوى الأول من كائن التهيئة، يجب أن يتطابق المفتاح مع اسم المهمة التي نريد ربطها بها. سيؤدي هذا المثال إلى ظهور الرسالة بعد تنفيذ مَهمّة imagemin مباشرةً، وهي الأخيرة في سلسلة التصميم.

إنهاء كل شيء

إذا تابعت من الأعلى، أصبحت مالك عملية إنشاء مرتَّبة ومنظمة للغاية، وسريعة للغاية بسبب موازاة المعالجة والمعالجة الانتقائية، وتبلغك عند حدوث أي مشكلة.

إذا اكتشفت أداة أخرى تحسّن أداء Grunt ومكوناتها الإضافية بشكل أكبر، يُرجى إعلامنا بذلك. حتى ذلك الحين، نتمنى لك حظًا سعيدًا.

تعديل (14/2/2014): للحصول على نسخة من مثال مشروع Grunt الكامل، انقر هنا.