Webpack veya Browserify & Gulp: Hangisi Daha İyi?
Yayınlanan: 2022-03-11Web uygulamaları giderek daha karmaşık hale geldikçe, web uygulamanızı ölçeklenebilir hale getirmek son derece önemli hale geliyor. Geçmişte geçici JavaScript ve jQuery yazmak yeterliyken, günümüzde bir web uygulaması oluşturmak çok daha fazla disiplin ve aşağıdakiler gibi resmi yazılım geliştirme uygulamaları gerektirir:
- Kodunuzda yapılan değişikliklerin mevcut işlevselliği bozmadığından emin olmak için birim testleri
- Tutarlı kodlama stilini hatasız sağlamak için astarlama
- Geliştirme yapılarından farklı üretim yapıları
Web ayrıca kendi benzersiz geliştirme zorluklarından bazılarını da sağlar. Örneğin, web sayfaları çok sayıda eşzamansız istekte bulunduğundan, web uygulamanızın performansı, her biri kendi küçük ek yüküne (başlıklar, el sıkışmaları vb.) sahip yüzlerce JS ve CSS dosyası istemek zorunda kalmaktan önemli ölçüde düşebilir. Bu özel sorun genellikle dosyaları bir araya toplayarak çözülebilir, bu nedenle yüzlerce ayrı dosya yerine yalnızca tek bir paketlenmiş JS ve CSS dosyası talep edersiniz.
ES5 uyumluluğunu korurken ES6 kodundan yararlanmak için yerel JS ve CSS'yi derleyen SASS ve JSX gibi dil önişlemcilerinin yanı sıra Babel gibi JS aktarıcılarını kullanmak da oldukça yaygındır.
Bu, web uygulamasının mantığını yazmakla hiçbir ilgisi olmayan önemli sayıda görev anlamına gelir. İşte burada görev yürütücüler devreye girer. Görev yürütücünün amacı, uygulamanızı yazmaya odaklanırken gelişmiş bir geliştirme ortamından yararlanabilmeniz için tüm bu görevleri otomatikleştirmektir. Görev çalıştırıcı yapılandırıldıktan sonra, tek yapmanız gereken bir terminalde tek bir komut çağırmak.
Gulp'u bir görev yürütücüsü olarak kullanacağım çünkü çok geliştirici dostu, öğrenmesi kolay ve kolayca anlaşılabilir.
Gulp'a Hızlı Bir Giriş
Gulp'un API'si dört işlevden oluşur:
-
gulp.src
-
gulp.dest
-
gulp.task
-
gulp.watch
Burada, örneğin, bu dört işlevden üçünü kullanan örnek bir görev verilmiştir:
gulp.task('my-first-task', function() { gulp.src('/public/js/**/*.js') .pipe(concat()) .pipe(minify()) .pipe(gulp.dest('build')) });
İlk my-first-task
gerçekleştirildiğinde, /public/js/**/*.js
glob modeliyle eşleşen tüm dosyalar küçültülür ve ardından bir build
klasörüne aktarılır.
Bunun güzelliği .pipe()
zincirlemesindedir. Bir dizi girdi dosyası alırsınız, bunları bir dizi dönüşümden geçirirsiniz ve ardından çıktı dosyalarını döndürürsünüz. İşleri daha da kolaylaştırmak için minify()
gibi gerçek borulama dönüşümleri genellikle NPM kitaplıkları tarafından yapılır. Sonuç olarak, borudaki dosyaları yeniden adlandırmanın ötesinde kendi dönüşümlerinizi yazmanız pratikte çok nadirdir.
Gulp'u anlamanın bir sonraki adımı, görev bağımlılıkları dizisini anlamaktır.
gulp.task('my-second-task', ['lint', 'bundle'], function() { ... });
Burada, my-second-task
, yalnızca lint
ve bundle
görevleri tamamlandıktan sonra geri arama işlevini çalıştırır. Bu, endişelerin ayrılmasını sağlar: LESS
CSS
dönüştürmek gibi tek bir sorumlulukla bir dizi küçük görev oluşturursunuz ve bir dizi görev bağımlılığı aracılığıyla diğer tüm görevleri basitçe çağıran bir tür ana görev oluşturursunuz.
Son olarak, değişiklikler için bir glob dosya desenini izleyen ve bir değişiklik algılandığında bir dizi görevi çalıştıran gulp.watch
var.
gulp.task('my-third-task', function() { gulp.watch('/public/js/**/*.js', ['lint', 'reload']) })
Yukarıdaki örnekte, /public/js/**/*.js
ile eşleşen bir dosyada yapılacak herhangi bir değişiklik, lint
bırakma ve reload
görevini tetikleyecektir. gulp.watch
yaygın bir kullanımı, tarayıcıda canlı yeniden yüklemeleri tetiklemektir; bu, geliştirme için o kadar yararlı bir özelliktir ki, bir kez deneyimledikten sonra onsuz yaşayamazsınız.
Ve böylece, gulp
hakkında gerçekten bilmeniz gereken her şeyi anlıyorsunuz.
Web Paketi Nereye Uyar?
CommonJS modelini kullanırken, JavaScript dosyalarını gruplamak, onları birleştirmek kadar basit değildir. Bunun yerine, dosyanın üst kısmında bir dizi request veya import
deyimi olan bir giriş noktanız ( require
index.js
veya app.js
olarak adlandırılır) vardır:
ES5
var Component1 = require('./components/Component1'); var Component2 = require('./components/Component2');
ES6
import Component1 from './components/Component1'; import Component2 from './components/Component2';
app.js
kalan koddan önce bağımlılıkların çözülmesi gerekir ve bu bağımlılıkların çözülmesi gereken başka bağımlılıkları olabilir. Ayrıca, uygulamanızda birden çok yerde aynı bağımlılığa require
, ancak bu bağımlılığı yalnızca bir kez çözmek istersiniz. Tahmin edebileceğiniz gibi, birkaç seviye derinlikte bir bağımlılık ağacınız olduğunda, JavaScript'inizi gruplama süreci oldukça karmaşık hale gelir. Bu, Browserify ve Webpack gibi paketleyicilerin devreye girdiği yerdir.
Geliştiriciler Neden Gulp Yerine Web Paketi Kullanıyor?
Webpack bir paketleyici iken Gulp bir görev yürütücüdür, bu nedenle bu iki aracın yaygın olarak birlikte kullanıldığını görmeyi bekleyebilirsiniz. Bunun yerine, özellikle React topluluğu arasında Gulp yerine Webpack kullanmaya yönelik artan bir eğilim var. Bu neden?
Basitçe söylemek gerekirse, Webpack o kadar güçlü bir araçtır ki, normalde bir görev yürütücü aracılığıyla yapacağınız görevlerin büyük çoğunluğunu zaten gerçekleştirebilir. Örneğin, Webpack zaten paketiniz için küçültme ve kaynak haritaları için seçenekler sunuyor. Ayrıca Webpack, hem canlı yeniden yüklemeyi hem de çalışırken yeniden yüklemeyi destekleyen webpack-dev-server
server adlı özel bir sunucu aracılığıyla ara katman yazılımı olarak çalıştırılabilir (bu özelliklerden daha sonra bahsedeceğiz). Yükleyicileri kullanarak, ES6'dan ES5'e aktarım ve CSS ön ve son işlemcileri de ekleyebilirsiniz. Bu, gerçekten sadece birim testleri ve linting'i Webpack'in bağımsız olarak üstesinden gelemeyeceği ana görevler olarak bırakır. En az yarım düzine potansiyel yudum görevini ikiye indirdiğimiz düşünüldüğünde, birçok geliştirici bunun yerine doğrudan NPM komut dosyalarını kullanmayı tercih ediyor, çünkü bu, projeye Gulp eklemenin ek yükünü ortadan kaldırıyor (buna daha sonra değineceğiz) .
Webpack'i kullanmanın en büyük dezavantajı, yapılandırmanın oldukça zor olmasıdır; bu, bir projeyi hızlı bir şekilde başlatıp çalıştırmaya çalışıyorsanız çekici değildir.
3 Görev Çalıştırıcı Kurulumumuz
Üç farklı görev koşucusu kurulumu ile bir proje kuracağım. Her kurulum aşağıdaki görevleri gerçekleştirecektir:
- İzlenen dosya değişikliklerinde canlı yeniden yükleme ile bir geliştirme sunucusu kurun
- JS ve CSS dosyalarımızı (ES6'dan ES5'e aktarma, SASS'den CSS'ye dönüştürme ve kaynak haritalar dahil) izlenen dosya değişikliklerinde ölçeklenebilir bir şekilde paketleyin
- Birim testlerini bağımsız bir görev olarak veya izleme modunda çalıştırın
- Linting'i bağımsız bir görev olarak veya izleme modunda çalıştırın
- Yukarıdakilerin tümünü terminalde tek bir komutla yürütme yeteneği sağlayın
- Küçültme ve diğer optimizasyonlarla birlikte bir üretim paketi oluşturmak için başka bir komuta sahip olun
Üç kurulumumuz olacak:
- Gulp + Tarayıcı
- Gulp + Web Paketi
- Web paketi + NPM Komut Dosyaları
Uygulama ön uç için React kullanacaktır. Başlangıçta, çerçeveden bağımsız bir yaklaşım kullanmak istedim, ancak yalnızca bir HTML dosyası gerektiğinden ve React, CommonJS modeliyle çok iyi çalıştığından, React'i kullanmak görev yürütücünün sorumluluklarını basitleştirir.
Proje ihtiyaçlarınıza en uygun kurulum türü konusunda bilinçli bir karar verebilmeniz için her kurulumun avantajlarını ve dezavantajlarını ele alacağız.
Her yaklaşım için bir tane olmak üzere üç dallı bir Git deposu kurdum (bağlantı). Her kurulumu test etmek şu kadar basittir:
git checkout <branch name> npm prune (optional) npm install gulp (or npm start, depending on the setup)
Her daldaki kodu detaylı olarak inceleyelim…
Ortak Kod
Klasör Yapısı
- app - components - fonts - styles - index.html - index.js - index.test.js - routes.js
index.html
Basit bir HTML dosyası. React uygulaması <div></div>
içine yüklenir ve biz sadece tek bir JS ve CSS dosyası kullanırız. Aslında, bundle.css
geliştirme kurulumumuzda, package.css dosyasına bile ihtiyacımız olmayacak.
index.js
Bu, uygulamamızın JS giriş noktası görevi görür. Esasen, daha önce bahsettiğimiz id app
div
React Router'ı yüklüyoruz.
route.js
Bu dosya rotalarımızı tanımlar. /
, /about
ve /contact
url'leri sırasıyla HomePage
, AboutPage
ve ContactPage
bileşenlerine eşlenir.
index.test.js
Bu, yerel JavaScript davranışını test eden bir dizi birim testidir. Gerçek bir üretim kalitesi uygulamasında, React'e özgü davranışı test ederek, React bileşeni başına (en azından durumu değiştirenler) bir birim testi yazarsınız. Ancak, bu yazının amaçları doğrultusunda, izleme modunda çalışabilen bir işlevsel birim testinin olması yeterlidir.
bileşenler/App.js
Bu, tüm sayfa görünümlerimiz için kapsayıcı olarak düşünülebilir. Her sayfa, sayfa görünümünün kendisini değerlendiren this.props.children
yanı sıra bir <Header/>
bileşeni içerir (tarayıcıda /contact
konumundaysa eski/ ContactPage
).
component/home/HomePage.js
Bu bizim ev manzaramız. Bootstrap'in ızgara sistemi duyarlı sayfalar oluşturmak için mükemmel olduğundan react-bootstrap
kullanmayı seçtim. Önyüklemenin doğru kullanımıyla, daha küçük görünüm pencereleri için yazmanız gereken medya sorgularının sayısı önemli ölçüde azalır.
Kalan bileşenler ( Header
, AboutPage
, ContactPage
) benzer şekilde yapılandırılmıştır ( react-bootstrap
işaretlemesi, durum manipülasyonu yok).
Şimdi stil hakkında daha fazla konuşalım.
CSS Stil Yaklaşımı
React bileşenlerini biçimlendirmek için tercih ettiğim yaklaşım, stilleri yalnızca o belirli bileşene uygulanacak şekilde kapsamlandırılmış, bileşen başına bir stil sayfasına sahip olmaktır. React bileşenlerimin her birinde, üst düzey div
bileşenin adıyla eşleşen bir sınıf adının olduğunu fark edeceksiniz. Örneğin, HomePage.js
işaretlemesi şu şekilde sarılır:
<div className="HomePage"> ... </div>
Ayrıca aşağıdaki gibi yapılandırılmış ilişkili bir HomePage.scss
dosyası vardır:
@import '../../styles/variables'; .HomePage { // Content here }
Bu yaklaşım neden bu kadar faydalı? Son derece modüler CSS ile sonuçlanır ve istenmeyen basamaklı davranış sorununu büyük ölçüde ortadan kaldırır.
İki React bileşenimiz olduğunu varsayalım, Component1
ve Component2
. Her iki durumda da h2
yazı tipi boyutunu geçersiz kılmak istiyoruz.
/* Component1.scss */ .Component1 { h2 { font-size: 30px; } } /* Component2.scss */ .Component2 { h2 { font-size: 60px; } }
Bileşen1 ve h2
yazı tipi boyutu, Component1
bitişik mi yoksa bir Component2
diğerinin içine yerleştirilmiş mi olduğundan bağımsızdır. İdeal olarak, bu, bir bileşenin stilinin tamamen bağımsız olduğu anlamına gelir; yani, bileşenin, işaretlemenizin neresine yerleştirilirse yerleştirilsin, tamamen aynı görüneceği anlamına gelir. Gerçekte, her zaman bu kadar basit değil, ama kesinlikle doğru yönde büyük bir adım.
Bileşen başına stillere ek olarak, global.scss
genel stil sayfası içeren bir styles
klasörüne ve belirli bir sorumluluğu ele alan SASS bölümlerine sahip olmayı seviyorum (bu durumda, fontlar ve değişkenler için sırasıyla _fonts.scss
ve _variables.scss
). Genel stil sayfası, tüm uygulamanın genel görünümünü ve verdiği hissi tanımlamamıza izin verirken, yardımcı bölümler gerektiğinde bileşen başına stil sayfaları tarafından içe aktarılabilir.
Artık her daldaki ortak kod derinlemesine araştırıldığına göre, odağımızı ilk görev yürütücü / paketleyici yaklaşımına kaydıralım.
Gulp + Tarayıcı Kurulumu
gulpfile.js
Bu, 22 içe aktarma ve 150 satır kod ile şaşırtıcı derecede büyük bir gulpfile ortaya çıkıyor. Bu nedenle, kısa olması adına, yalnızca js
, css
, server
, watch
ve default
görevlerini ayrıntılı olarak inceleyeceğim.
JS paketi
// Browserify specific configuration const b = browserify({ entries: [config.paths.entry], debug: true, plugin: PROD ? [] : [hmr, watchify], cache: {}, packageCache: {} }) .transform('babelify'); b.on('update', bundle); b.on('log', gutil.log); (...) gulp.task('js', bundle); (...) // Bundles our JS using Browserify. Sourcemaps are used in development, while minification is used in production. function bundle() { return b.bundle() .on('error', gutil.log.bind(gutil, 'Browserify Error')) .pipe(source('bundle.js')) .pipe(buffer()) .pipe(cond(PROD, minifyJS())) .pipe(cond(!PROD, sourcemaps.init({loadMaps: true}))) .pipe(cond(!PROD, sourcemaps.write())) .pipe(gulp.dest(config.paths.baseDir)); }
Bu yaklaşım birkaç nedenden dolayı oldukça çirkindir. Birincisi, görev üç ayrı bölüme ayrılmıştır. İlk olarak, bazı seçenekleri geçerek ve bazı olay işleyicileri tanımlayarak Browserify paket nesnenizi b
yaratırsınız. Ardından, adlandırılmış bir işlevi satır içine almak yerine geri çağrısı olarak iletmesi gereken Gulp görevinin kendisine sahipsiniz (çünkü b.on('update')
aynı geri aramayı kullanır). Bu, sadece bir gulp.src
ve bazı değişiklikleri aktardığınız bir Gulp görevinin zarafetine sahip değildir.
Başka bir sorun, bunun bizi tarayıcıda html
, css
ve js
yeniden yüklemek için farklı yaklaşımlara sahip olmaya zorlamasıdır. Gulp watch
görevimize baktığımızda:
gulp.task('watch', () => { livereload.listen({basePath: 'dist'}); gulp.watch(config.paths.html, ['html']); gulp.watch(config.paths.css, ['css']); gulp.watch(config.paths.js, () => { runSequence('lint', 'test'); }); });
Bir HTML dosyası değiştirildiğinde, html
görevi yeniden çalıştırılır.
gulp.task('html', () => { return gulp.src(config.paths.html) .pipe(gulp.dest(config.paths.baseDir)) .pipe(cond(!PROD, livereload())); });
NODE_ENV
production
değilse, son kanal livereload()
'u çağırır ve bu da tarayıcıda bir yenilemeyi tetikler.
Aynı mantık CSS izle için de kullanılır. Bir CSS dosyası değiştirildiğinde, css
görevi yeniden çalıştırılır ve css
görevindeki son boru, livereload()
tetikler ve tarayıcıyı yeniler.
Ancak, js
saati, js
görevini hiç çağırmaz. Bunun yerine, Browserify'ın olay işleyicisi b.on('update', bundle)
yeniden yüklemeyi tamamen farklı bir yaklaşım (yani, etkin modül değişimi) kullanarak işler. Bu yaklaşımdaki tutarsızlık rahatsız edicidir, ancak artımlı yapılara sahip olmak için ne yazık ki gereklidir. bundle
işlevinin sonunda saf livereload()
, bu, herhangi bir JS dosyası değişikliğinde tüm JS paketini yeniden oluşturur. Böyle bir yaklaşım açıkça ölçeklenmez. Ne kadar çok JS dosyanız varsa, her bir yeniden birleştirme o kadar uzun sürer. Aniden, 500 ms'lik geri toplamalarınız 30 saniye sürmeye başlar ve bu da çevik gelişimi gerçekten engeller.
CSS paketi
gulp.task('css', () => { return gulp.src( [ 'node_modules/bootstrap/dist/css/bootstrap.css', 'node_modules/font-awesome/css/font-awesome.css', config.paths.css ] ) .pipe(cond(!PROD, sourcemaps.init())) .pipe(sass().on('error', sass.logError)) .pipe(concat('bundle.css')) .pipe(cond(PROD, minifyCSS())) .pipe(cond(!PROD, sourcemaps.write())) .pipe(gulp.dest(config.paths.baseDir)) .pipe(cond(!PROD, livereload())); });
Buradaki ilk sorun, hantal satıcı CSS içermesidir. Projeye yeni bir satıcı CSS dosyası eklendiğinde, içe aktarmayı gerçek kaynak kodumuzdaki ilgili bir yere eklemek yerine gulp.src
dizisine bir öğe eklemek için gulpfile'mizi değiştirmeyi unutmamalıyız.
Diğer ana konu, her borudaki kıvrımlı mantıktır. Borularımda koşullu mantık kurmak için gulp-cond
adlı bir NPM kitaplığı eklemek zorunda kaldım ve sonuç çok okunaklı değil (her yerde üçlü parantez!).
Sunucu Görevi
gulp.task('server', () => { nodemon({ script: 'server.js' }); });
Bu görev çok basittir. Esasen, bir düğüm ortamında server.js çalıştıran server.js
nodemon server.js
komut satırı çağırma etrafındaki bir sarmalayıcıdır. node
yerine nodemon
kullanılır, böylece dosyada yapılacak herhangi bir değişiklik dosyanın yeniden başlamasına neden olur. Varsayılan olarak nodemon
, herhangi bir JS dosyası değişikliğinde çalışan işlemi yeniden başlatır, bu nedenle kapsamını sınırlamak için bir nodemon.json
dosyası eklemek önemlidir:
{ "watch": "server.js" }
Sunucu kodumuzu gözden geçirelim.
server.js
const baseDir = process.env.NODE_ENV === 'production' ? 'build' : 'dist'; const port = process.env.NODE_ENV === 'production' ? 8080: 3000; const app = express();
Bu, sunucunun temel dizinini ve bağlantı noktasını düğüm ortamına göre ayarlar ve bir ekspres örneği oluşturur.
app.use(require('connect-livereload')({port: 35729})); app.use(express.static(path.join(__dirname, baseDir)));
Bu, connect-livereload
canlı yükleme ara yazılımı (canlı yeniden yükleme kurulumumuz için gerekli) ve statik ara yazılım (statik varlıklarımızı işlemek için gerekli) ekler.

app.get('/api/sample-route', (req, res) => { res.send({ website: 'Toptal', blogPost: true }); });
Bu sadece basit bir API rotasıdır. Tarayıcıda localhost:3000/api/sample-route
giderseniz şunu göreceksiniz:
{ website: "Toptal", blogPost: true }
Gerçek bir arka uçta, API yollarına ayrılmış tüm bir klasörünüz, DB bağlantıları kurmak için ayrı dosyalar vb. Bu örnek rota, sadece kurduğumuz ön ucun üzerine kolayca bir arka uç oluşturabileceğimizi göstermek için dahil edildi.
app.get('*', (req, res) => { res.sendFile(path.join(__dirname, './', baseDir ,'/index.html')); });
Bu bir tümünü yakalama rotasıdır, yani tarayıcıya hangi url'yi yazarsanız yazın, sunucu yalnız index.html
sayfamızı döndürür. Daha sonra, istemci tarafında rotalarımızı çözmek React Router'ın sorumluluğundadır.
app.listen(port, () => { open(`http://localhost:${port}`); });
Bu, ekspres örneğimize belirttiğimiz bağlantı noktasını dinlemesini ve tarayıcıyı belirtilen URL'de yeni bir sekmede açmasını söyler.
Şimdiye kadar sunucu kurulumu hakkında sevmediğim tek şey:
app.use(require('connect-livereload')({port: 35729}));
Gulpfile'mizde zaten gulp-livereload
kullandığımızı düşünürsek, bu, livereload'ın kullanılması gereken iki ayrı yer yapar.
Şimdi, son fakat en az değil:
Varsayılan Görev
gulp.task('default', (cb) => { runSequence('clean', 'lint', 'test', 'html', 'css', 'js', 'fonts', 'server', 'watch', cb); });
Bu, terminale sadece gulp
yazarken çalışan görevdir. Bir tuhaflık, görevlerin sırayla çalışmasını sağlamak için runSequence
kullanma ihtiyacıdır. Normalde, bir dizi görev paralel olarak yürütülür, ancak bu her zaman istenen davranış değildir. Örneğin, dosyaları onlara taşımadan önce hedef klasörlerimizin boş olduğundan emin olmak için clean
görevi html
önce çalıştırmamız gerekir. gulp 4 yayınlandığında, gulp.series
ve gulp.parallel
yöntemlerini doğal olarak destekleyecektir, ancak şimdilik kurulumumuzda bu küçük tuhaflıkla ayrılmamız gerekiyor.
Bunun ötesinde, bu aslında oldukça zarif. Uygulamamızın tüm oluşturma ve barındırma işlemleri tek bir komutla gerçekleştirilir ve iş akışının herhangi bir bölümünü anlamak, çalıştırma sırasındaki tek bir görevi incelemek kadar basittir. Ayrıca, uygulamayı oluşturmaya ve barındırmaya yönelik daha ayrıntılı bir yaklaşım için tüm diziyi daha küçük parçalara bölebiliriz. Örneğin, lint
ve test
görevlerini çalıştıran validate
adında ayrı bir görev oluşturabiliriz. Veya server
çalıştıran ve watch
bir host
görevimiz olabilir. Görevleri düzenleme yeteneği, özellikle uygulamanız ölçeklendiğinden ve daha otomatikleştirilmiş görevler gerektirdiğinden çok güçlüdür.
Geliştirme ve Üretim Yapıları
if (argv.prod) { process.env.NODE_ENV = 'production'; } let PROD = process.env.NODE_ENV === 'production';
yargs
NPM kütüphanesini kullanarak Gulp'a komut satırı bayrakları sağlayabiliriz. Burada gulpfile'a, eğer terminalde gulp
--prod
geçirilirse, düğüm ortamını üretime ayarlaması talimatını veriyorum. PROD
değişkenimiz daha sonra gulpfile'ımızdaki geliştirme ve üretim davranışını ayırt etmek için koşullu olarak kullanılır. Örneğin, tarayıcı yapılandırmamıza browserify
seçeneklerden biri:
plugin: PROD ? [] : [hmr, watchify]
Bu, browserify
üretim modunda herhangi bir eklenti kullanmamasını ve diğer ortamlarda hmr
ve watchify
eklentilerini kullanmasını söyler.
Bu PROD
koşulu çok kullanışlıdır çünkü bizi üretim ve geliştirme için sonuçta çok fazla kod tekrarı içerecek ayrı bir gulpfile yazmaktan kurtarır. Bunun yerine, üretimde varsayılan görevi çalıştırmak için gulp --prod
--prod veya üretimde yalnızca html
görevini çalıştırmak için gulp html --prod
gibi şeyler yapabiliriz. Öte yandan, Gulp ardışık .pipe(cond(!PROD, livereload()))
gibi ifadelerle doldurmanın en okunaklı olmadığını daha önce görmüştük. Sonunda, boole değişkeni yaklaşımını kullanmak veya iki ayrı gulpfile oluşturmak isteyip istemediğiniz bir tercih meselesidir.
Şimdi, Gulp'u görev yürütücümüz olarak kullanmaya devam ettiğimizde, ancak Browserify'ı Webpack ile değiştirdiğimizde ne olacağını görelim.
Gulp + Web Paketi Kurulumu
Aniden gulpfile'ımız 12 içe aktarma ile yalnızca 99 satır uzunluğunda, önceki kurulumumuzdan oldukça fazla bir azalma! Varsayılan görevi kontrol edersek:
gulp.task('default', (cb) => { runSequence('lint', 'test', 'build', 'server', 'watch', cb); });
Artık tam web uygulaması kurulumumuz dokuz yerine yalnızca beş görev gerektiriyor, bu çarpıcı bir gelişme.
Ayrıca, canlı yükleme ihtiyacını ortadan livereload
. watch
görevimiz artık basitçe:
gulp.task('watch', () => { gulp.watch(config.paths.js, () => { runSequence('lint', 'test'); }); });
Bu, yudum izleyicimizin herhangi bir geri toplama davranışını tetiklemediği anlamına gelir. Ek bir avantaj olarak, artık index.html
dosyasını app
dist
veya build
aktarmamıza gerek yok.
Odağımızı görev azaltımına geri döndürerek, html
, css
, js
ve fonts
görevlerimizin tümü tek bir build
göreviyle değiştirildi:
gulp.task('build', () => { runSequence('clean', 'html'); return gulp.src(config.paths.entry) .pipe(webpack(require('./webpack.config'))) .pipe(gulp.dest(config.paths.baseDir)); });
Yeterince basit. clean
ve html
görevlerini sırayla çalıştırın. Bunlar tamamlandıktan sonra, giriş noktamızı alın, Webpack'e aktarın, yapılandırmak için bir webpack.config.js
dosyasından geçirin ve elde edilen paketi baseDir
(düğüm env'sine bağlı olarak dist
veya build
).
Webpack yapılandırma dosyasına bir göz atalım:
webpack.config.js
Bu oldukça büyük ve göz korkutucu bir yapılandırma dosyasıdır, bu yüzden module.exports
ayarlanan bazı önemli özellikleri açıklayalım.
devtool: PROD ? 'source-map' : 'eval-source-map',
Bu, Webpack'in kullanacağı kaynak haritaların türünü ayarlar. Webpack, kaynak haritaları kutudan çıktığı haliyle desteklemekle kalmaz, aslında çok çeşitli kaynak harita seçeneklerini de destekler. Her seçenek, kaynak harita ayrıntısı ile yeniden oluşturma hızı arasında farklı bir denge sağlar (değişikliklerin yeniden birleştirilmesi için geçen süre). Bu, hızlı yeniden yüklemeler elde etmek için geliştirme için "ucuz" bir kaynak haritası seçeneği ve üretimde daha pahalı bir kaynak haritası seçeneği kullanabileceğimiz anlamına gelir.
entry: PROD ? './app/index' : [ 'webpack-hot-middleware/client?reload=true', // reloads the page if hot module reloading fails. './app/index' ]
Bu bizim paket giriş noktamız. Bir dizinin iletildiğine dikkat edin, yani birden çok giriş noktasına sahip olmak mümkündür. Bu durumda, beklenen giriş noktamız app/index.js
ve etkin modül yeniden yükleme kurulumumuzun bir parçası olarak kullanılan webpack-hot-middleware
giriş noktasına sahibiz.
output: { path: PROD ? __dirname + '/build' : __dirname + '/dist', publicPath: '/', filename: 'bundle.js' },
Bu, derlenmiş paketin çıkarılacağı yerdir. En kafa karıştırıcı seçenek publicPath
. Paketinizin sunucuda barındırılacağı yerin temel URL'sini ayarlar. Örneğin, publicPath
/public/assets
ise, paket sunucuda /public/assets/bundle.js
altında görünecektir.
devServer: { contentBase: PROD ? './build' : './app' }
Bu, sunucuya, projenizdeki hangi klasörü sunucunun kök dizini olarak kullanacağını söyler.
Webpack'in projenizde oluşturulan paketi sunucudaki paketle nasıl eşleştirdiği konusunda kafanız karışırsa, aşağıdakileri hatırlamanız yeterlidir:
-
path
+filename
: Paketin proje kaynak kodunuzdaki tam konumu -
contentBase
(kök olarak,/
) +publicPath
: Paketin sunucudaki konumu
plugins: PROD ? [ new webpack.optimize.OccurenceOrderPlugin(), new webpack.DefinePlugin(GLOBALS), new ExtractTextPlugin('bundle.css'), new webpack.optimize.DedupePlugin(), new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}) ] : [ new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin() ],
Bunlar, Webpack'in işlevselliğini bir şekilde geliştiren eklentilerdir. Örneğin, webpack.optimize.UglifyJsPlugin
küçültme işleminden sorumludur.
loaders: [ {test: /\.js$/, include: path.join(__dirname, 'app'), loaders: ['babel']}, { test: /\.css$/, loader: PROD ? ExtractTextPlugin.extract('style', 'css?sourceMap'): 'style!css?sourceMap' }, { test: /\.scss$/, loader: PROD ? ExtractTextPlugin.extract('style', 'css?sourceMap!resolve-url!sass?sourceMap') : 'style!css?sourceMap!resolve-url!sass?sourceMap' }, {test: /\.(svg|png|jpe?g|gif)(\?\S*)?$/, loader: 'url?limit=100000&name=img/[name].[ext]'}, {test: /\.(eot|woff|woff2|ttf)(\?\S*)?$/, loader: 'url?limit=100000&name=fonts/[name].[ext]'} ]
Bunlar yükleyiciler. Esasen, require()
ifadeleri aracılığıyla yüklenen dosyaları önceden işlerler. Yükleyicileri birbirine zincirleyebilmeniz açısından Gulp borularına biraz benzerler.
Loader nesnelerimizden birini inceleyelim:
{test: /\.scss$/, loader: 'style!css?sourceMap!resolve-url!sass?sourceMap'}
test
özelliği Webpack'e, bir dosya sağlanan normal ifade modeliyle eşleşirse, bu durumda /\.scss$/
belirtilen yükleyicinin geçerli olduğunu söyler. loader
özelliği, yükleyicinin gerçekleştirdiği eyleme karşılık gelir. Burada ters sırada yürütülen style
, css
,solve resolve-url
ve sass
birbirine zincirliyoruz.
loader3!loader2!loader1
sözdizimini pek zarif bulmadığımı itiraf etmeliyim. Sonuçta, ne zaman bir programdaki herhangi bir şeyi sağdan sola okumak zorunda kalırsınız? Buna rağmen, yükleyiciler web paketinin çok güçlü bir özelliğidir. Aslında, az önce bahsettiğim yükleyici, SASS dosyalarını doğrudan JavaScript'imize aktarmamıza izin veriyor! Örneğin, satıcımızı ve global stil sayfalarımızı giriş noktası dosyamıza aktarabiliriz:
index.js
import React from 'react'; import {render} from 'react-dom'; import {Router, browserHistory} from 'react-router'; import routes from './routes'; // CSS imports import '../node_modules/bootstrap/dist/css/bootstrap.css'; import '../node_modules/font-awesome/css/font-awesome.css'; import './styles/global.scss'; render(<Router history={browserHistory} routes={routes} />, document.getElementById('app'));
Benzer şekilde, Header bileşenimizde, bileşenin ilişkili stil sayfasını içe aktarmak için import './Header.scss'
ekleyebiliriz. Bu, diğer tüm bileşenlerimiz için de geçerlidir.
Bence bu, JavaScript geliştirme dünyasında neredeyse devrim niteliğinde bir değişiklik olarak kabul edilebilir. Yükleyicimiz tüm bunları bizim için hallettiğinden, CSS paketleme, küçültme veya kaynak haritaları hakkında endişelenmenize gerek yok. Sıcak modül yeniden yükleme bile CSS dosyalarımız için çalışır. Ardından, aynı dosyada JS ve CSS içe aktarma işlemlerini gerçekleştirebilmek, geliştirmeyi kavramsal olarak daha basit hale getirir: Daha fazla tutarlılık, daha az bağlam değiştirme ve daha kolay akıl yürütme.
Bu özelliğin nasıl çalıştığına dair kısa bir özet vermek gerekirse: Web paketi, CSS'yi JS paketimizin içine yerleştirir. Aslında, Webpack bunu resimler ve yazı tipleri için de yapabilir:
{test: /\.(svg|png|jpe?g|gif)(\?\S*)?$/, loader: 'url?limit=100000&name=img/[name].[ext]'}, {test: /\.(eot|woff|woff2|ttf)(\?\S*)?$/, loader: 'url?limit=100000&name=fonts/[name].[ext]'}
URL yükleyici, Webpack'e resimlerimizi ve yazı tiplerini 100 KB'nin altındaysa veri url'leri olarak satır içi yapmasını, aksi takdirde bunları ayrı dosyalar olarak sunmasını söyler. Tabii cutoff boyutunu da 10 KB gibi farklı bir değere ayarlayabiliriz.
Ve bu, kısaca Webpack konfigürasyonudur. Adil miktarda kurulum olduğunu kabul edeceğim, ancak onu kullanmanın faydaları olağanüstü. Browserify'ın eklentileri ve dönüşümleri olmasına rağmen, ek işlevsellik açısından Webpack yükleyicileriyle karşılaştırılamazlar.
Webpack + NPM Komut Dosyaları Kurulumu
Bu kurulumda, görevlerimizi otomatikleştirmek için bir gulpfile'a güvenmek yerine doğrudan npm betikleri kullanıyoruz.
paket.json
"scripts": { "start": "npm-run-all --parallel lint:watch test:watch build", "start:prod": "npm-run-all --parallel lint test build:prod", "clean-dist": "rimraf ./dist && mkdir dist", "clean-build": "rimraf ./build && mkdir build", "clean": "npm-run-all clean-dist clean-build", "test": "mocha ./app/**/*.test.js --compilers js:babel-core/register", "test:watch": "npm run test -- --watch", "lint": "esw ./app/**/*.js", "lint:watch": "npm run lint -- --watch", "server": "nodemon server.js", "server:prod": "cross-env NODE_ENV=production nodemon server.js", "build-html": "node tools/buildHtml.js", "build-html:prod": "cross-env NODE_ENV=production node tools/buildHtml.js", "prebuild": "npm-run-all clean-dist build-html", "build": "webpack", "postbuild": "npm run server", "prebuild:prod": "npm-run-all clean-build build-html:prod", "build:prod": "cross-env NODE_ENV=production webpack", "postbuild:prod": "npm run server:prod" }
Geliştirme ve üretim derlemelerini çalıştırmak için sırasıyla npm start
ve npm run start:prod
girin.
Bu, 99 ila 150 satırlık kodu 19 NPM komut dosyasına indirdiğimiz veya üretim komut dosyalarını hariç tutarsak 12 (çoğu yalnızca düğüm ortamı üretime ayarlanmış geliştirme komut dosyalarını yansıtır) göz önüne alındığında, kesinlikle bizim gulpfile'den daha kompakttır. ). Dezavantajı, bu komutların Gulp görev meslektaşlarımıza kıyasla biraz şifreli olması ve o kadar anlamlı olmamasıdır. Örneğin, tek bir npm betiğinin belirli komutları seri ve diğerlerini paralel olarak çalıştırmasının (en azından benim bildiğim) yolu yoktur. Ya biri ya da diğeri.
Ancak, bu yaklaşımın büyük bir avantajı var. Doğrudan komut satırından mocha
gibi NPM kitaplıklarını kullanarak, her biri için eşdeğer bir Gulp sarmalayıcı yüklemeniz gerekmez (bu durumda, gulp-mocha
).
NPM kurulumu yerine
- yudum-eslint
- yudum-mocha
- yudum-nodemon
- vb
Aşağıdaki paketleri kuruyoruz:
- eslint
- moka
- düğüm
- vb
Cory House'un gönderisinden alıntı, NPM Komut Dosyaları için Neden Gulp ve Grunt'u Bıraktım :
Gulp'un büyük bir hayranıydım. Ancak son projemde, gulpfile'mde yüzlerce satır ve bir düzine Gulp eklentisi buldum. Gulp kullanarak Webpack, Browsersync, çalışırken yeniden yükleme, Mocha ve çok daha fazlasını entegre etmekte zorlanıyordum. Niye ya? Eh, bazı eklentilerin kullanım durumum için yetersiz belgeleri vardı. Bazı eklentiler, ihtiyacım olan API'nin yalnızca bir kısmını açığa çıkardı. Birinde, yalnızca az sayıda dosyayı izleyeceği garip bir hata vardı. Komut satırına çıktı alırken başka bir soyulmuş renk.
Gulp ile ilgili üç temel konuyu belirtiyor:
- Eklenti yazarlarına bağımlılık
- Hata ayıklamak için sinir bozucu
- ayrık belgeler
Bunların hepsine katılma eğilimindeyim.
1. Eklenti Yazarlarına Bağlılık
eslint
gibi bir kitaplık her güncellendiğinde, ilgili gulp-eslint
kitaplığının ilgili bir güncellemeye ihtiyacı vardır. Kütüphane sorumlusu ilgisini kaybederse, kütüphanenin ilk sürümü yerel sürümle senkronize olmaz. Aynı şey yeni bir kitaplık oluşturulduğunda da geçerlidir. Birisi bir xyz
kütüphanesi yaratırsa ve bu onu yakalarsa, o zaman aniden onu ilk yudum görevlerinizde kullanmak için karşılık gelen bir gulp-xyz
kütüphanesine ihtiyacınız olur.
Bir anlamda, bu yaklaşım sadece ölçeklenmiyor. İdeal olarak, yerel kütüphaneleri kullanabilen Gulp gibi bir yaklaşım isteriz.
2. Hata Ayıklamak için Sinir bozucu
gulp-plumber
gibi kütüphaneler bu sorunu önemli ölçüde hafifletmeye yardımcı olsa da, yine de gulp
hata raporlamanın pek yardımcı olmadığı doğrudur. Bir boru bile işlenmeyen bir istisna atarsa, kaynak kodunuzda soruna neden olan şeyle tamamen alakasız görünen bir sorun için yığın izlemesi alırsınız. Bu, bazı durumlarda hata ayıklamayı bir kabusa dönüştürebilir. Hata şifreli veya yeterince yanıltıcıysa, Google'da veya Stack Overflow'ta hiçbir arama yapmak size gerçekten yardımcı olamaz.
3. Ayrık Belgeler
Çoğu zaman, küçük gulp
kitaplıklarının çok sınırlı belgelere sahip olma eğiliminde olduğunu görüyorum. Bunun, yazarın genellikle kütüphaneyi öncelikle kendi kullanımı için yapmasından kaynaklandığından şüpheleniyorum. Ek olarak, hem Gulp eklentisi hem de yerel kitaplığın kendisi için belgelere bakmak zorunda kalmak yaygındır, bu da çok sayıda bağlam değiştirme ve yapılacak iki kat daha fazla okuma anlamına gelir.
Çözüm
Her seçeneğin avantajları ve dezavantajları olmasına rağmen, Webpack'in Browserify'a ve NPM komut dosyalarının Gulp'a tercih edildiği bana oldukça açık görünüyor. Gulp, NPM komut dosyalarından kesinlikle daha anlamlı ve kullanımı kolaydır, ancak eklenen tüm soyutlamalarda bedeli ödersiniz.
Her kombinasyon, uygulamanız için mükemmel olmayabilir, ancak çok sayıda geliştirme bağımlılığından ve sinir bozucu bir hata ayıklama deneyiminden kaçınmak istiyorsanız, NPM komut dosyalarına sahip Webpack tam size göre. Umarım bu makaleyi bir sonraki projeniz için doğru araçları seçmede faydalı bulursunuz.
- Kontrolü Koruyun: Web Paketi ve Tepki Kılavuzu, Pt. 1
- Gulp Under the Hood: Akış Tabanlı Bir Görev Otomasyon Aracı Oluşturma