Gulp Under the Hood: Akış Tabanlı Bir Görev Otomasyon Aracı Oluşturma
Yayınlanan: 2022-03-11Günümüzde ön uç geliştiriciler, rutin işlemleri otomatikleştirmek için birden fazla araç kullanıyor. En popüler çözümlerden üçü Grunt, Gulp ve Webpack'tir. Bu araçların her biri farklı felsefeler üzerine inşa edilmiştir, ancak aynı ortak hedefi paylaşırlar: ön uç oluşturma sürecini kolaylaştırmak. Örneğin, Grunt konfigürasyon odaklıyken Gulp neredeyse hiçbir şeyi zorlamaz. Aslında, Gulp, geliştirme süreçlerinin - çeşitli derleme görevlerinin - akışını uygulamak için geliştiricinin kod yazmasına güvenir.
Bu araçlardan birini seçmeye gelince benim kişisel favorim Gulp. Sonuç olarak, basit, hızlı ve güvenilir bir çözümdür. Bu makalede, kendi Gulp benzeri aracımızı uygulamada bir adım atarak Gulp'un kaputun altında nasıl çalıştığını göreceğiz.
Gulp API'si
Gulp yalnızca dört basit işlevle gelir:
- yudum.görev
- gulp.src
- gulp.dest
- yudum. izle
Bu dört basit işlev, çeşitli kombinasyonlarda Gulp'un tüm gücünü ve esnekliğini sunar. 4.0 sürümünde, Gulp iki yeni fonksiyon tanıttı: gulp.series ve gulp.parallel. Bu API'ler, görevlerin seri veya paralel olarak çalıştırılmasına izin verir.
Bu dört işlevden ilk üçü, herhangi bir Gulp dosyası için kesinlikle gereklidir. görevlerin komut satırı arayüzünden tanımlanmasına ve çağrılmasına izin verir. Dördüncüsü, dosyalar değiştiğinde görevlerin yürütülmesine izin vererek Gulp'u gerçekten otomatik yapan şeydir.
gulpfile
Bu temel bir gulpfile:
gulp.task('test', function{ gulp.src('test.txt') .pipe(gulp.dest('out')); });
Basit bir test görevini açıklar. Çağrıldığında, mevcut çalışma dizinindeki test.txt dosyası ./out dizinine kopyalanmalıdır. Gulp'u çalıştırarak bir deneyin:
touch test.txt # Create test.txt gulp test
.pipe yönteminin .pipe
bir parçası olmadığına dikkat edin, bu düğüm akışı API'sidir, okunabilir bir akışı ( gulp.src('test.txt')
tarafından oluşturulur) yazılabilir bir akışla ( gulp.dest('out')
tarafından oluşturulur) birbirine bağlar. gulp.dest('out')
). Gulp ve eklentiler arasındaki tüm iletişim akışlara dayalıdır. Bu, gulpfile kodunu çok zarif bir şekilde yazmamızı sağlar.
Tanışma Fişi
Artık Gulp'un nasıl çalıştığına dair bir fikrimiz olduğuna göre, kendi Gulp benzeri aracımızı oluşturalım: Plug.
Plug.task API ile başlayacağız. Görevleri kaydetmemize izin vermeli ve görev adı komut parametrelerinde geçirilirse görevler yürütülmelidir.
var plug = { task: onTask }; module.exports = plug; var tasks = {}; function onTask(name, callback){ tasks[name] = callback; }
Bu, görevlerin kaydedilmesine izin verecektir. Şimdi bu görevi çalıştırılabilir hale getirmemiz gerekiyor. İşleri basit tutmak için ayrı bir görev başlatıcısı yapmayacağız. Bunun yerine onu eklenti uygulamamıza dahil edeceğiz.
Tek yapmamız gereken komut satırı parametrelerinde belirtilen görevleri çalıştırmak. Ayrıca, tüm görevler kaydedildikten sonra bir sonraki yürütme döngüsünde yapmaya çalıştığımızdan emin olmamız gerekir. Bunu yapmanın en kolay yolu, görevleri bir zaman aşımı geri aramasında veya tercihen process.nextTick'te çalıştırmaktır:
process.nextTick(function(){ var taskName = process.argv[2]; if (taskName && tasks[taskName]) { tasks[taskName](); } else { console.log('unknown task', taskName) } });
Plugfile.js dosyasını şu şekilde oluşturun:
var plug = require('./plug'); plug.task('test', function(){ console.log('hello plug'); })
… ve çalıştırın.
node plugfile.js test
Şunları gösterecektir:
hello plug
alt görevler
Gulp ayrıca görev kaydında alt görevleri tanımlamaya da izin verir. Bu durumda, plug.task 3 parametre almalıdır, ad, alt görevler dizisi ve geri arama işlevi. Bunu uygulayalım.
Görev API'sini şu şekilde güncellememiz gerekecek:
var tasks = {}; function onTask(name) { if(Array.isArray(arguments[1]) && typeof arguments[2] === "function"){ tasks[name] = { subTasks: arguments[1], callback: arguments[2] }; } else if(typeof arguments[1] === "function"){ tasks[name] = { subTasks: [], callback: arguments[1] }; } else{ console.log('invalid task registration') } } function runTask(name){ if(tasks[name].subTasks){ tasks[name].subTasks.forEach(function(subTaskName){ runTask(subTaskName); }); } if(tasks[name].callback){ tasks[name].callback(); } } process.nextTick(function(){ if (taskName && tasks[taskName]) { runTask(taskName); } });
Şimdi plugfile.js'miz şöyle görünüyorsa:
plug.task('subTask1', function(){ console.log('from sub task 1'); }) plug.task('subTask2', function(){ console.log('from sub task 2'); }) plug.task('test', ['subTask1', 'subTask2'], function(){ console.log('hello plug'); })
… çalıştırıyor
node plugfile.js test
… şunları göstermelidir:
from sub task 1 from sub task 2 hello plug
Gulp'un alt görevleri paralel olarak çalıştırdığını unutmayın. Ancak işleri basit tutmak için, uygulamamızda sırayla alt görevleri çalıştırıyoruz. Gulp 4.0, bu makalenin ilerleyen bölümlerinde uygulayacağımız iki yeni API işlevi kullanılarak bunun kontrol edilmesini sağlar.
Kaynak ve Hedef
Dosyaların okunmasına ve yazılmasına izin vermezsek, fiş pek işe yaramaz. Şimdi plug.src
uygulayacağız. Gulp'taki bu yöntem, bir dosya maskesi, bir dosya adı veya bir dizi dosya maskesi olan bir argüman bekler. Okunabilir bir Düğüm akışı döndürür.
Şimdilik, src
uygulamamızda sadece dosya adlarına izin vereceğiz:
var plug = { task: onTask, src: onSrc }; var stream = require('stream'); var fs = require('fs'); function onSrc(fileName){ var src = new stream.Readable({ read: function (chunk) { }, objectMode: true }); //read file and send it to the stream fs.readFile(path, 'utf8', (e,data)=> { src.push({ name: path, buffer: data }); src.push(null); }); return src; }
Burada isteğe bağlı bir parametre olan objectMode: true
kullandığımızı unutmayın. Bunun nedeni, düğüm akışlarının varsayılan olarak ikili akışlarla çalışmasıdır. JavaScript nesnelerini akışlar aracılığıyla geçirmemiz/almamız gerekirse, bu parametreyi kullanmamız gerekir.
Gördüğünüz gibi yapay bir nesne oluşturduk:
{ name: path, //file name buffer: data //file content }
… ve onu dereye aktardı.
Diğer taraftan, plug.dest yöntemi bir hedef klasör adı almalı ve .src akışından nesneleri alacak olan yazılabilir bir akış döndürmelidir. Bir dosya nesnesi alınır alınmaz hedef klasöre kaydedilecektir.
function onDest(path){ var writer = new stream.Writable({ write: function (chunk, encoding, next) { if (!fs.existsSync(path)) fs.mkdirSync(path); fs.writeFile(path +'/'+ chunk.name, chunk.buffer, (e)=> { next() }); }, objectMode: true }); return writer; }
Plugfile.js dosyamızı güncelleyelim:

var plug = require('./plug'); plug.task('test', function(){ plug.src('test.txt') .pipe(plug.dest('out')) })
… test.txt oluştur
touch test.txt
… ve çalıştırın:
node plugfile.js test ls ./out
test.txt ./out klasörüne kopyalanmalıdır.
Gulp'un kendisi de aşağı yukarı aynı şekilde çalışır, ancak bizim suni dosya nesnelerimiz yerine vinil nesneler kullanır. Yalnızca dosya adı ve içeriği değil, geçerli klasör adı, dosyanın tam yolu vb. gibi ek meta bilgileri de içerdiğinden çok daha uygundur. Tüm içerik arabelleğini içermeyebilir, ancak bunun yerine okunabilir bir içerik akışına sahiptir.
Vinil: Dosyalardan Daha İyi
Vinil nesneler olarak temsil edilen dosyaları değiştirmemize izin veren harika bir vinyl-fs kütüphanesi var. Esasen dosya maskesine dayalı olarak okunabilir, yazılabilir akışlar oluşturmamıza izin verir.
Vinyl-fs kütüphanesini kullanarak fiş fonksiyonlarını yeniden yazabiliriz. Ama önce vinyl-fs yüklememiz gerekiyor:
npm i vinyl-fs
Bu yüklendiğinde, yeni Plug uygulamamız şuna benzer:
var vfs = require('vinyl-fs') function onSrc(fileName){ return vfs.src(fileName); } function onDest(path){ return vfs.dest(path); } // ...
… ve denemek için:
rm out/test.txt node plugFile.js test ls out/test.txt
Sonuçlar yine aynı olmalıdır.
Gulp Eklentileri
Plug hizmetimiz Gulp akışı kuralı kullandığından, Plug aracımızla birlikte yerel Gulp eklentilerini kullanabiliriz.
Bir deneyelim. Gulp-rename'i kurun:
npm i gulp-rename
… ve kullanmak için plugfile.js dosyasını güncelleyin:
var plug = require('./app.js'); var rename = require('gulp-rename'); plug.task('test', function () { return plug.src('test.txt') .pipe(rename('renamed.txt')) .pipe(plug.dest('out')); });
Plugfile.js'yi şimdi çalıştırmak yine de aynı sonucu vermeli, tahmin etmişsinizdir.
node plugFile.js test ls out/renamed.txt
Değişiklikleri İzleme
Son fakat en az olmayan yöntem gulp.watch
Bu yöntem, dosya dinleyicisini kaydetmemize ve dosyalar değiştiğinde kayıtlı görevleri başlatmamıza izin verir. Hadi uygulayalım:
var plug = { task: onTask, src: onSrc, dest: onDest, watch: onWatch }; function onWatch(fileName, taskName){ fs.watchFile(fileName, (event, filename) => { if (filename) { tasks[taskName](); } }); }
Denemek için bu satırı plugfile.js'ye ekleyin:
plug.watch('test.txt','test');
Şimdi, test.txt dosyasının her değişikliğinde, dosya, adı değiştirilmiş olarak çıkış klasörüne kopyalanacaktır.
Seri ve Paralel
Artık Gulp'un API'sindeki tüm temel işlevler uygulandığına göre, işleri bir adım daha ileri götürelim. Gulp'un gelecek sürümü daha fazla API işlevi içerecek. Bu yeni API, Gulp'u daha güçlü hale getirecek:
- yudum.paralel
- gulp.series
Bu yöntemler, kullanıcının görevlerin çalıştırıldığı sırayı kontrol etmesine izin verir. Alt görevleri paralel olarak kaydetmek için mevcut Gulp davranışı olan gulp.parallel kullanılabilir. Öte yandan, gulp.series, alt görevleri birbiri ardına sıralı bir şekilde çalıştırmak için kullanılabilir.
Geçerli klasörde test1.txt ve test2.txt olduğunu varsayalım. Bu dosyaları paralel olarak dışarıdaki klasöre kopyalamak için bir eklenti dosyası yapalım:
var plug = require('./plug'); plug.task('subTask1', function(){ return plug.src('test1.txt') .pipe(plug.dest('out')) }) plug.task('subTask2', function(){ return plug.src('test2.txt') .pipe(plug.dest('out')) }) plug.task('test-parallel', plug.parallel(['subTask1', 'subTask2']), function(){ console.log('done') }) plug.task('test-series', plug.series(['subTask1', 'subTask2']), function(){ console.log('done') })
Uygulamayı basitleştirmek için, akışını döndürmek için alt görev geri çağırma işlevleri yapılır. Bu, akış yaşam döngüsünü izlememize yardımcı olacaktır.
API'mizi değiştirmeye başlayacağız:
var plug = { task: onTask, src: onSrc, dest: onDest, parallel: onParallel, series: onSeries };
Görev başlatıcımızın alt görevlerle düzgün bir şekilde ilgilenmesine yardımcı olmak için ek görev meta bilgileri eklememiz gerektiğinden, onTask işlevini de güncellememiz gerekecek.
function onTask(name, subTasks, callback){ if(arguments.length < 2){ console.error('invalid task registration',arguments); return; } if(arguments.length === 2){ if(typeof arguments[1] === 'function'){ callback = subTasks; subTasks = {series: []}; } } tasks[name] = subTasks; tasks[name].callback = function(){ if(callback) return callback(); }; } function onParallel(tasks){ return { parallel: tasks }; } function onSeries(tasks){ return { series: tasks }; }
İşleri basitleştirmek için, görevleri paralel veya seri olarak çalıştırmak için asenkron işlevlerle uğraşan bir yardımcı program kitaplığı olan async.js'yi kullanacağız:
var async = require('async') function _processTask(taskName, callback){ var taskInfo = tasks[taskName]; console.log('task ' + taskName + ' is started'); var subTaskNames = taskInfo.series || taskInfo.parallel || []; var subTasks = subTaskNames.map(function(subTask){ return function(cb){ _processTask(subTask, cb); } }); if(subTasks.length>0){ if(taskInfo.series){ async.series(subTasks, taskInfo.callback); }else{ async.parallel(subTasks, taskInfo.callback); } }else{ var stream = taskInfo.callback(); if(stream){ stream.on('end', function(){ console.log('stream ' + taskName + ' is ended'); callback() }) }else{ console.log('task ' + taskName +' is completed'); callback(); } } }
Bir akış tüm mesajları işlediğinde ve kapatıldığında yayılan, alt görevin tamamlandığının bir göstergesi olan düğüm akışı 'sonuna' güveniyoruz. async.js ile büyük bir geri arama karmaşası ile uğraşmak zorunda değiliz.
Denemek için önce alt görevleri paralel olarak çalıştıralım:
node plugFile.js test-parallel
task test-parallel is started task subTask1 is started task subTask2 is started stream subTask2 is ended stream subTask1 is ended done
Ve aynı alt görevleri seri olarak çalıştırın:
node plugFile.js test-series
task test-series is started task subTask1 is started stream subTask1 is ended task subTask2 is started stream subTask2 is ended done
Çözüm
İşte bu kadar, Gulp'un API'sini uyguladık ve artık Gulp eklentilerini kullanabiliriz. Tabii ki Plug in gerçek projeler kullanmayın, çünkü Gulp burada uyguladıklarımızdan daha fazlasıdır. Umarım bu küçük alıştırma, Gulp'un kaputun altında nasıl çalıştığını anlamanıza yardımcı olur ve onu daha akıcı bir şekilde kullanmamıza ve eklentilerle genişletmemize izin verir.