अपनी ग्रंटफ़ाइल को सुपरचार्ज करना

अपने बिल्ड कॉन्फ़िगरेशन का ज़्यादा से ज़्यादा फ़ायदा कैसे पाएं

शुरुआती जानकारी

अगर आपके लिए ग्रंट की दुनिया नई है, तो इसकी शुरुआत करने के लिए क्रिस कॉयर का यह लेख “ग्रंट फ़ॉर पीपल जो थिंक थिंग लाइक ग्रंट हैं, अजीब और मुश्किल है” है. क्रिस के परिचय के बाद, आप अपना खुद का ग्रंट प्रोजेक्ट सेट अप करेंगे और पावर ग्रंट ऑफ़र का एक हिस्सा चखेंगे.

इस लेख में, हम इस बात पर ध्यान नहीं देंगे कि बहुत से Grunt प्लग आपके असल प्रोजेक्ट कोड के साथ काम करते हैं. इसके बजाय, हम Grunt की बिल्ड प्रोसेस पर फ़ोकस करेंगे. हम आपको इन विषयों के बारे में व्यावहारिक आइडिया देंगे:

  • अपनी Gruntfile को साफ़-सुथरी कैसे रखें,
  • बिल्ड टाइम को कैसे बेहतर बनाएं,
  • साथ ही, बिल्ड होने पर सूचना कैसे पाएं.

जल्दी से डिसक्लेमर करने का समय: ग्रंट उन कई टूल में से एक है जिनकी मदद से काम पूरा किया जा सकता है. अगर गल्प आपकी शैली ज़्यादा है, तो बढ़िया है! अगर विकल्पों का सर्वे करने के बाद भी, आपको खुद की टूलचेन बनानी है, तो कोई दिक्कत नहीं है! हमने इस लेख के मज़बूत नेटवर्क और पुराने उपयोगकर्ता आधार की वजह से, इस लेख के लिए ग्रंट पर फ़ोकस करना चुना है.

अपनी Gruntfile को व्यवस्थित करना

चाहे आपको बहुत से Gruntfile शामिल करने हों या आपको 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']);

};

अगर अब आप कह रहे हैं “नमस्ते! मुझे इससे भी खराब की उम्मीद थी! इसका रखरखाव भी किया जा सकता है!”, यह भी हो सकता है कि आप सही हों. आसानी के लिए, हमने ज़्यादा कस्टमाइज़ेशन के बिना सिर्फ़ तीन प्लग इन शामिल किए हैं. अगर सामान्य साइज़ का प्रोजेक्ट बनाने के लिए, असल प्रोडक्शन ग्रंटफ़ाइल का इस्तेमाल किया जाता है, तो इस लेख में इनफ़ाइनाइट स्क्रोलिंग की ज़रूरत होगी. आइए, देखते हैं कि हम क्या कर सकते हैं!

अपने 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');

लोड-ग्रंट-टास्क की मदद से, यहां बताए गए एक लाइनर टास्क को छोटा किया जा सकता है:

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

प्लगिन की ज़रूरत होने के बाद, यह आपकी Package.json फ़ाइल का विश्लेषण करेगा और तय करेगा कि Grunt प्लगिन कौनसी डिपेंडेंसी हैं और उन सभी को अपने-आप लोड करेगा.

अपने Grunt कॉन्फ़िगरेशन को अलग-अलग फ़ाइलों में बांटना

load-grunt-tasks ने कोड और जटिलता में आपकी Gruntfile को छोटा कर दिया. हालांकि, जब आप किसी बड़े ऐप्लिकेशन को कॉन्फ़िगर करेंगे, तो वह बहुत बड़ी फ़ाइल बन जाएगी. यहीं से load-grunt-config काम शुरू होता है. load-grunt-config की मदद से अपने Gruntfile कॉन्फ़िगरेशन को काम के हिसाब से अलग-अलग किया जा सकता है. इतना ही नहीं, इसमें load-grunt-tasks और उसकी सुविधाओं की जानकारी भी शामिल है!

हालांकि, अहम जानकारी को अलग-अलग हिस्सों में बांटना ज़रूरी नहीं है. ऐसा हर स्थिति में किया जा सकता है. अगर आपके टास्क के बीच बहुत से कॉन्फ़िगरेशन शेयर किए गए हैं (जैसे कि बहुत सारे ग्रंट टेंप्लेट का इस्तेमाल करना), तो आपको थोड़ा सावधान रहना चाहिए.

load-grunt-config का इस्तेमाल करने पर, आपकी Gruntfile.js ऐसी दिखेगी:

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

हां, सिर्फ़ यही है, पूरी फ़ाइल! अब हमारे टास्क का कॉन्फ़िगरेशन कहां होता है?

अपनी Gruntfile की डायरेक्ट्री में grunt/ नाम का फ़ोल्डर बनाएं. डिफ़ॉल्ट रूप से, प्लगिन में उस फ़ोल्डर में मौजूद फ़ाइलें शामिल होती हैं जो आपके इस्तेमाल किए जाने वाले टास्क के नाम से मेल खाती हैं. हमारी डायरेक्ट्री का स्ट्रक्चर कुछ ऐसा होना चाहिए:

- 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” फ़ाइल. यह एक खास फ़ाइल है जो टास्क के उपनामों को रजिस्टर करती है. यह कुछ ऐसा है जो हमें पहले registerTask फ़ंक्शन के ज़रिए, Gruntfile के हिस्से के तौर पर करना पड़ता था. ये हैं:

grunt/aliases.yaml

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

और बस काम हो गया! अपने टर्मिनल में नीचे दिया गया निर्देश चलाएं:

$ grunt

अगर सब कुछ ठीक से काम करता है, तो यह अब "डिफ़ॉल्ट" टास्क में दिखेगा और सब कुछ क्रम से चलने लगेगा. अब हमने अपनी मुख्य Gruntfile को कोड की तीन लाइनों में बदल दिया है, जिसकी हमें कभी भी हर टास्क कॉन्फ़िगरेशन को छूने और बाहरी रूप से निकालने की ज़रूरत नहीं पड़ती. लेकिन दोस्त, अब भी सब कुछ बनाना काफ़ी धीमा है. आइए, देखते हैं कि हम इसे बेहतर बनाने के लिए क्या कर सकते हैं.

ऐप्लिकेशन बनाने में लगने वाला समय कम करना

भले ही, आपके वेब ऐप्लिकेशन का रनटाइम और लोड होने में लगने वाला समय, आपके कारोबार के लिए, बिल्ड को लागू करने में लगने वाले समय से कहीं ज़्यादा ज़रूरी होता है. इसके बावजूद, धीमी गति से काम करने में समस्या का सामना करना पड़ता है. इससे grunt-conrib-watch जैसे प्लगिन की मदद से या किसी Git के तेज़ी से कमिट होने के बाद, ऑटोमैटिक बिल्ड को एक्ज़ीक्यूट करना मुश्किल हो जाएगा और असल में बिल्ड चलाने के लिए "पेनल्टी" लागू हो जाएगी – बिल्ड टाइम जितना तेज़ होगा, आपका वर्कफ़्लो उतना ही तेज़ होगा. अगर आपके प्रोडक्शन बिल्ड को चलने में 10 मिनट से ज़्यादा समय लगता है, तो आप बिल्ड सिर्फ़ तब चलाएंगे, जब आपको उसे चलाना पड़े. इसके बाद, आपको कॉफ़ी के लिए भटकना पड़ेगा. यह प्रॉडक्टिविटी किलर है. हम तेज़ी से काम करने वाले हैं.

सिर्फ़ ऐसी फ़ाइलें बनाएं जिनमें बदलाव किया गया हो: नया

अपनी साइट के शुरुआती निर्माण के बाद, यह संभावना है कि आपने प्रोजेक्ट में कुछ ही फ़ाइलों को छू लिया होगा, जब आप फिर से निर्माण करने लगेंगे. मान लें कि हमारे उदाहरण में, आपने src/img/ डायरेक्ट्री में एक इमेज बदली है – इमेज को फिर से ऑप्टिमाइज़ करने के लिए imagemin चलाना सही रहेगा. हालांकि, ऐसा करना सिर्फ़ उस एक इमेज के लिए ही सही रहेगा. यही नहीं, concat और uglify को फिर से चलाने से आपके सीपीयू के ढेर सारे काम बर्बाद हो जाते हैं.

किसी टास्क को चुनिंदा तरीके से पूरा करने के लिए, $ grunt के बजाय हमेशा $ grunt imagemin को अपने टर्मिनल से चलाया जा सकता है. हालांकि, इससे बेहतर तरीका भी है. इसे ग्रंट-न्यूयर कहते हैं.

Grunt-newer में एक लोकल कैश मेमोरी होती है. इसमें यह जानकारी सेव होती है कि असल में कौनसी फ़ाइलें बदली गई हैं. साथ ही, यह आपके टास्क को सिर्फ़ उन फ़ाइलों के लिए एक्ज़ीक्यूट करता है जिनमें वाकई बदलाव हुआ था. चलिए, अब जानते हैं कि इसे कैसे चालू किया जाता है.

हमारी aliases.yaml फ़ाइल याद है? इसे इससे बदलें:

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

को:

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

बस अपने किसी भी टास्क के लिए “newer:” तैयार करना होता है जो आपके सोर्स और डेस्टिनेशन फ़ाइलों को सबसे पहले ग्रंट-नए प्लगिन की मदद से पाइप कर देता है. इससे यह तय होता है कि कौनसी फ़ाइल (अगर कोई है) है, तो टास्क चलाया जाना चाहिए.

एक साथ कई टास्क चलाएं: ग्रंट-एक साथ

grunt-concurrent एक ऐसा प्लगिन है जो तब वाकई काम का होता है, जब आपके पास ऐसे कई टास्क होते हैं जो एक-दूसरे से अलग होते हैं और बहुत ज़्यादा समय लेते हैं. यह आपके डिवाइस में मौजूद सीपीयू की संख्या का इस्तेमाल करता है और साथ-साथ कई काम करता है.

सबसे अच्छी बात यह है कि इसका कॉन्फ़िगरेशन बेहद आसान है. यह मानते हुए कि 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, दोनों को साथ-साथ चलाने के लिए दो अलग-अलग सीपीयू कोर पर दो थ्रेड स्पॉन्सर करेगा. हां!

हालांकि, एक खास बात: हो सकता है कि हमारे बुनियादी उदाहरण में, एक ही समय पर एक साथ कई ऐप्लिकेशन बनाने से आपका बिल्ड ज़्यादा तेज़ न हो. इसकी वजह अलग-अलग थ्रेड में ग्रंट के अलग-अलग इंस्टेंस को पैदा करके बनाया गया ओवरहेड है: मेरे मामले में, कम से कम 300 मि॰से॰ प्रो स्पॉन की संख्या.

इसमें कितना समय लगा? टाइम-ग्रंट

अब हम अपने हर टास्क को ऑप्टिमाइज़ कर रहे हैं. इसलिए, यह जानना बहुत मददगार होगा कि हर टास्क को पूरा करने में कितना समय लगता है. अच्छी बात यह है कि इसके लिए भी एक प्लगिन मौजूद है: time-grunt.

Time-grunt, कोई क्लासिकल ग्रंट प्लगिन नहीं है, जिसे npm टास्क के तौर पर लोड किया जाता है. इसकी जगह, एक ऐसा प्लगिन है जिसे सीधे load-grunt-config की तरह इस्तेमाल किया जा रहा है. हम अपनी Gruntfile में समय की जानकारी की ज़रूरत को शामिल करेंगे, जैसा कि हमने load-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 को फिर से चलाने की कोशिश करें और हर टास्क (साथ ही पूरे बिल्ड के साथ) के लिए, आपको काम करने के समय का अच्छी तरह से फ़ॉर्मैट किया गया जानकारी पैनल दिखेगा:

घुरघुराने का समय

सिस्टम से मिलने वाली सूचनाएं अपने-आप मिलने की सुविधा

अब आपके पास बहुत ज़्यादा ऑप्टिमाइज़ किया गया ऐसा ग्रंट बिल्ड है जो तेज़ी से काम करता है और आपने इसे किसी तरीके से अपने-आप बनाया है (जैसे, ग्रंट-कंट्रिब-वॉच के ज़रिए या कमिट करके फ़ाइलें देखकर), तो क्या यह अच्छा नहीं होगा कि आपका सिस्टम आपको तब सूचना दे, जब आपका नया बिल्ड इस्तेमाल के लिए तैयार हो या जब कुछ बुरा हुआ हो? grunt-notify से मिलें.

डिफ़ॉल्ट रूप से, grunt-notify की मदद से आपके OS पर उपलब्ध किसी भी सूचना सिस्टम का इस्तेमाल करके, Grunt की गड़बड़ियों और चेतावनियों के लिए अपने-आप सूचनाएं मिल जाती हैं. जैसे: Growl for OS X या Windows, Mountain Lion's and Mavericks’ Notification Center, और सूचनाएं भेजने की सुविधा. दिलचस्प बात यह है कि इस सुविधा को पाने के लिए, आपको 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 प्रोजेक्ट के पूरे और काम करने वाले उदाहरण की कॉपी पाने के लिए, यहां क्लिक करें.