Gruntfile'ınızı güçlendirin

Derleme yapılandırmanızdan en iyi şekilde yararlanma

Paul Bakaus
Paul Bakaus

Giriş

Grunt dünyası sizin için yeniyse, Chris Coyier’in “Grunt gibi şeylerin tuhaf ve zor olduğunu düşünen kişiler için ağlama” adlı mükemmel makalesiyle başlayabilirsiniz. Chris'in tanıtımından sonra kendi Grunt projenizi kurmuş ve Grunt'ın sunduğu güçten bir kısmını deneyeceksiniz.

Bu makalede, sayısız Grunt eklentisinin gerçek proje kodunuzda yaptıkları işe değil, Grunt derleme işleminin kendisine odaklanacağız. Aşağıdaki konularda size pratik fikirler vereceğiz:

  • Gruntfile'ınızı nasıl düzenli ve derli toplu
  • Derleme sürenizi önemli ölçüde nasıl kısaltmanız gerektiği,
  • Ve bir derleme gerçekleştiğinde nasıl bildirim alacağınız.

Hızlı bir sorumluluk reddi beyanı: Grunt, görevi yerine getirmek için kullanabileceğiniz birçok araçtan yalnızca biridir. Gulp daha çok senin stilinse, harika! Mevcut seçenekleri değerlendirdikten sonra da kendi araç zincirinizi oluşturmak isterseniz sorun olmaz! Güçlü ekosistemi ve uzun süredir var olan kullanıcı tabanı nedeniyle bu makalede Grunt'a odaklanmayı seçtik.

Gruntfile'ınızı düzenleme

İster çok sayıda Grunt eklentisi dahil ediyor ister Gruntfile dosyanıza çok sayıda manuel görev yazmak zorunda kalsanız bile, zamanla Gruntfile'ınızın kullanımı son derece zor ve bakımı zor olabilir. Neyse ki tam olarak bu soruna odaklanan çok sayıda eklenti var: Gruntfile dosyanızı tekrar düzenli hale getirme.

Optimizasyondan önce Gruntfile

Herhangi bir optimizasyon yapmadan önce Gruntfile'ımız şu şekilde görünüyor:

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']);

};

Şimdi "Hey! Çok daha kötüsünü bekliyordum. Bu aslında sürdürülebilir bir şey." Muhtemelen haklısınız. Kolaylık olması açısından, fazla özelleştirme içermeyen yalnızca üç eklentiyi dahil ettik. Orta büyüklükte bir proje oluşturmak için gerçek bir Gruntfile yapımının kullanılması bu makalede sonsuz kaydırma gerektirir. Şimdi neler yapabileceğimize bakalım!

Grunt eklentilerinizi otomatik olarak yükleyin

Projenizde kullanmak istediğiniz yeni bir Grunt eklentisi eklerken, eklentiyi npm bağımlılığı olarak package.json dosyanıza eklemeniz, ardından Gruntfile içine yüklemeniz gerekir. “grunt-contrib-concat” eklentisi için bu uzantı şöyle görünebilir:

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

Şimdi eklentiyi npm aracılığıyla kaldırıp package.json dosyanızı güncelleyip Gruntfile dosyanızı güncellemeyi unutursanız derlemeniz bozulur. load-grunt-tasks adlı şık eklenti tam da bu noktada yardımcı oluyor.

Daha önce Grunt eklentilerimizi şu şekilde manuel olarak yüklememiz gerekiyordu:

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

Load-grunt-tasks ile bunu aşağıdaki tek satırlık satıra daraltabilirsiniz:

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

Eklentiye ihtiyaç duyulduktan sonra, package.json dosyanızı analiz eder, bağımlılıklardan hangilerinin Grunt eklentisi olduğunu belirler ve tümünü otomatik olarak yükler.

Grunt yapılandırmanızı farklı dosyalara bölme

load-grunt-tasks, Gruntfile dosyanızı kod açısından küçültür ve karmaşıklaştırır. Ancak, büyük bir uygulamayı yapılandırdıkça çok büyük bir dosya haline gelir. load-grunt-config burada devreye girer. load-grunt-config, Gruntfile yapılandırmanızı göreve göre bölmenize olanak tanır. Dahası, yük-grunt-görevleri ve işlevselliğini de kapsıyor.

Ancak önemli: Gruntfile dosyanızı bölmek her zaman işe yaramayabilir. Görevleriniz arasında paylaşılan çok sayıda yapılandırma varsa (ör. Grunt şablonunu çok kullanıyorsanız) biraz dikkatli olmalısınız.

load-grunt-config ile Gruntfile.js'niz aşağıdaki gibi görünür:

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

Evet, tüm dosya bundan ibaret! Şimdi görev yapılandırmalarımız nerede bulunuyor?

Gruntfile dosyanızın dizininde grunt/ adında bir klasör oluşturun. Eklenti varsayılan olarak bu klasörde, kullanmak istediğiniz görevin adıyla eşleşen dosyaları içerir. Dizin yapımız aşağıdaki gibi görünmelidir:

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

Şimdi görevlerimizin her birinin görev yapılandırmasını doğrudan ilgili dosyalara yerleştirelim (bunların çoğunlukla orijinal Gruntfile dosyasından kopyalayıp yeni bir yapıya yapıştırdığını göreceksiniz):

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 yapılandırma blokları sizin işiniz değilse load-grunt-tasks, bunun yerine YAML veya CoffeeScript söz dizimini kullanmanıza bile olanak tanır. Şimdi, gerekli son dosyamızı YAML'de, yani "aliases" dosyasını yazalım. Bu, daha önce Gruntfile'ın bir parçası olarak registerTask işleviyle yapmamız gereken bir şey olan görev takma adlarını kaydeden özel bir dosyadır. Bizimkiler:

grunt/aliases.yaml

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

Hepsi bu kadar! Terminalinizde aşağıdaki komutu çalıştırın:

$ grunt

Her şey yolundaysa artık "varsayılan" göreve bakılacak ve her şey sıralı olarak çalıştırılacaktır. Artık ana Gruntfile'ımızı hiçbir zaman dokunmadığımız üç satırlık koda indirgediğimiz ve her görev yapılandırmasını hiçbir zaman dışlamadığımıza göre, burada işimiz bitti. Ama adamım, her şeyi inşa etmek hâlâ oldukça yavaş. Bunu iyileştirmek için neler yapabileceğimize bakalım.

Derleme sürenizi en aza indirme

Web uygulamanızın çalışma zamanı ve yükleme süresi performansı, bir derlemeyi yürütmek için gereken süreden çok daha fazla iş açısından kritik olsa da yavaş derleme hâlâ sorunludur. Bu yöntem, grunt-contrib-watch gibi eklentilerle veya bir Git kaydından sonra yeterince hızlı bir şekilde otomatik derleme yürütmeyi zorlaştıracak ve derlemenin gerçekten çalıştırılması için bir "ceza" sağlayacaktır. Derleme süresi ne kadar kısa olursa iş akışınız da o kadar çevik olur. Üretim derlemenizin çalışması 10 dakikadan uzun sürerse derlemeyi yalnızca ihtiyacınız olduğunda çalıştırır ve çalışırken kahve içmek için orada dolaşırsınız. Tam bir üretkenlik katili. Hızlandırmamız gereken şeyler var.

Yalnızca gerçekten değişen dosyaları derleme: grunt-newer

Siteniz ilk oluşturulduktan sonra, yeniden oluşturma aşamasına geldiğinizde projedeki birkaç dosyaya dokunmuş olmanız muhtemeldir. Örneğimizde, src/img/ dizinindeki bir resmi değiştirdiğinizi varsayalım. Görselleri yeniden optimize etmek için imagemin çalıştırmak mantıklıdır, ancak bu sadece tek bir resim için olacaktır ve tabii ki concat'in ve uglify'ın yeniden çalıştırılması değerli CPU döngülerinin boşa harcanması anlamına gelir.

Elbette elinizdeki bir görevi sadece seçerek yürütmek için $ grunt yerine her zaman kendi terminalinizden $ grunt imagemin çalıştırabilirsiniz, ancak bunun daha akıllıca bir yolu da vardır. Buna hoş-daha yeni deniyor.

Grunt-newer, hangi dosyaların gerçekten değiştiğiyle ilgili bilgileri depoladığı yerel bir önbelleğe sahiptir ve yalnızca değişen dosyalar için görevlerinizi yürütür. Şimdi bu hedefleme türünün nasıl etkinleştirileceğine bakalım.

aliases.yaml dosyamızı hatırlıyor musunuz? Bunu aşağıdaki şekilde değiştirin:

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

şuna ekleyin:

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

Görevlerinizin herhangi birine "daha yeni:" ifadesini eklemeniz yeterlidir. Kaynak ve hedef dosyalarınızı önce grunt-newer eklentisi aracılığıyla iletir. Bu eklenti daha sonra görevin hangi dosyaların (varsa) çalıştırılacağını belirler.

Birden fazla görevi paralel olarak çalıştırın: grunt-concurrent

grunt-concurrent, birbirinden bağımsız birçok göreviniz olduğunda çok fazla zaman alan bir eklentidir. Cihazınızdaki CPU sayısını kullanır ve birçok görevi paralel olarak yürütür.

Hepsinden önemlisi, yapılandırması son derece basittir. load-grunt-config komutunu kullandığınızı varsayarsak aşağıdaki yeni dosyayı oluşturun:

grunt/concurrent.js

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

"first" ve "second" adlı paralel yürütme kanalları oluşturduk. Öncelikle concat görevinin çalıştırılması gerekiyor ve bu süre zarfında örneğimizde çalıştırılacak başka bir şey yok. İkinci parçamıza hem uglify hem de imagemin'i ekledik, çünkü bu ikisi birbirinden bağımsızdır ve her ikisi de önemli ölçüde zaman alır.

Bunun tek başına herhangi bir etkisi yoktur. Varsayılan görev takma adımızı, doğrudan işler yerine eşzamanlı işlere işaret edecek şekilde değiştirmemiz gerekiyor. Yeni grunt/aliases.yaml içeriği şöyledir:

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

Grunt derlemenizi yeniden çalıştırırsanız eşzamanlı eklenti önce concat görevini çalıştırır ve ardından iki farklı CPU çekirdeğinde iki iş parçacığı oluşturarak hem imagemin hem de uglify'ı paralel olarak çalıştırır. Yaşasın!

Ama bir tavsiyede bulunalım: Temel örneğimizde hırıltılar, büyük ihtimalle derlemenizi çok daha hızlı hale getirmeyecektir. Bunun nedeni, farklı iş parçacıklarında farklı Grunt örneklerinin üretilmesiyle oluşturulan ek yüktür: Benim durumumda en az +300 ms profesyonel üretilir.

Ne kadar zaman aldı? - hırıltı

Artık her görevimizi optimize ettiğimize göre, her bir görevin ne kadar süre gerektirdiğini anlamak gerçekten yardımcı olabilir. Neyse ki bunun için bir eklenti var: time-grunt.

time-grunt, npm görevi olarak yüklediğiniz klasik bir grunt eklentisi değil, load-grunt-config öğesine benzer şekilde doğrudan eklediğiniz bir eklentidir. load-grunt-config dosyasında yaptığımız gibi, Gruntfile dosyamıza bir zaman sınırlama gereksinimi ekleyeceğiz. Gruntfile dosyamız şimdi şöyle görünmelidir:

module.exports = function(grunt) {

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

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

};

Sizi hayal kırıklığına uğrattığım için üzgünüm. İşte oldu. Grunt'ı Terminalinizden yeniden çalıştırmayı deneyin. Her görev (ve ayrıca toplam derleme) için yürütme süresiyle ilgili güzel biçimlendirilmiş bir bilgi paneli göreceksiniz:

Hırıltılı zaman

Otomatik sistem bildirimleri

Grunt derlemeniz hızlı bir şekilde çalışıyor ve otomatik olarak oluşturmanız şartıyla (örneğin, homurdanma izleme özelliğiyle veya kaydetme işleminden sonra dosyaları izleyerek) hızlı şekilde çalışan, yüksek düzeyde optimize edilmiş bir Grunt derlemeniz olduğuna göre, yeni derlemeniz kullanılmaya hazır olduğunda veya kötü bir şey olduğunda sisteminizin sizi bilgilendirmesi harika olmaz mıydı? grunt-notify ile tanışın.

grunt-notify varsayılan olarak işletim sisteminizdeki tüm bildirim sistemlerini kullanarak tüm Grunt hataları ve uyarıları için otomatik bildirimler sağlar: Growl for OS X veya Windows, Mountain Lion’s ve Mavericks Notification Center ve Notify-send. Şaşırtıcı bir şekilde, bu işlevi elde etmek için tek ihtiyacınız olan eklentiyi npm'den yüklemek ve Gruntfile dosyanıza yüklemektir (yukarıda grunt-load-config kullanıyorsanız bu adımın otomatik olduğunu unutmayın!).

İşletim sisteminize bağlı olarak aşağıdaki gibi görünecektir:

Bildir

Hatalara ve uyarılara ek olarak, son görevimizin yürütülmesi tamamlandıktan sonra çalışacak şekilde yapılandıralım. Görevleri dosyalar arasında bölmek için grunt-load-config yöntemini kullandığınızı varsayarsak ihtiyacımız olan dosya şudur:

grunt/notify.js

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

Yapılandırma nesnemizin ilk düzeyinde, anahtarın, onu bağlamak istediğimiz görevin adıyla eşleşmesi gerekir. Bu örnek, derleme zincirimizdeki son görev olan imagemin görevi yürütüldükten hemen sonra mesajın görünmesine neden olur.

Özetle

En üstten takip ettiyseniz şu anda titiz ve düzenli, paralel yapma ve seçmeli işleme nedeniyle son derece hızlı olan ve bir sorun olduğunda sizi bilgilendiren bir derleme işleminin sahibisiniz demektir.

Grunt'ı ve eklentilerini daha da geliştiren başka bir gem keşfederseniz lütfen bize bildirin! O zamana kadar iyi eğlenceler.

Güncelleme (14.02.2014): Çalışan bir örnek Grunt projesinin tam kopyasını almak için burayı tıklayın.