İçeriğe atla

Frontend Build Araçları Tarihi: jQuery, Grunt, Bower

Webpack'ten önce dosyaları Grunt ile birleştirir, jQuery spagettisiyle boğuşurduk. Frontend tooling'in manuel dosya yönetiminden build sistemlerine evrimi.

Ayhan Sipahi Ayhan Sipahi

Frontend tooling, birleştir-ve-minify problemi olarak başladı: elle yönetilen script sırası, deploy için shell script’leri ve manuel sürecin aşılmasıyla gelen ilk task runner dalgası (Make, sonra Grunt, sonra Gulp). Bu erken tooling üzerindeki baskı build hızından değil, bağımlılık sıralaması ve tarayıcılar arası uyumluluktan geliyordu: yüklenen bir site ile sessiz JavaScript hataları üreten bir site arasındaki fark.

Hikâye, elle yazılmış script’lerin ve tarayıcıya özel hack’lerin build öncesi döneminde (1995-2006) başlıyor; jQuery’nin bir uyumluluk katmanı olarak gelişiyle devam ediyor ve 2012’de Bower’la, yani frontend tarafındaki ilk ciddi paket yönetimi denemesiyle bitiyor.

jQuery Öncesi Karanlık Çağ (1995-2006)#

Modern tooling öncesi web development tamamen farklı bir dünyaydı. JavaScript ciddi uygulamalar için kullanacağınız bir dil değildi. İddialıysanız image rollover’lar ve form validation için kullanılırdı.

Manuel Dosya Yönetimi Kabusları#

2000’lerin başında, tipik bir proje yapısı şöyleydi:

website/
├── index.html
├── contact.html
├── js/
│  ├── utils.js
│  ├── validation.js
│  ├── slideshow.js
│  └── main.js
├── css/
│  ├── reset.css
│  ├── layout.css
│  └── styles.css
└── images/

Ve HTML’iniz şu canavarlık gibiydi:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="css/reset.css">
    <link rel="stylesheet" href="css/layout.css">
    <link rel="stylesheet" href="css/styles.css">
</head>
<body>
    <!-- content -->
    
    <script src="js/utils.js"></script>
    <script src="js/validation.js"></script>
    <script src="js/slideshow.js"></script>
    <script src="js/main.js"></script>
</body>
</html>

Problemler anında ve acı vericiydi:

  1. Sıra bağımlılığı cehennemi: main.js’i utils.js’den önce taşı ve her şey bozulur
  2. Global namespace kirliliği: Her değişken global scope’ta yaşardı
  3. Dependency management yok: Hangi script’in hangi script’e ihtiyacı olduğunu manuel takip etmek zorundaydınız
  4. Performance: JavaScript dosyaları için 15 ayrı HTTP request normaldi
  5. Cache busting: Dosya yollarına manuel olarak ?v=1.2.3 eklemek
  6. Development vs production: Farklı environment’lar için farklı HTML dosyaları

Development ve production arasındaki küçük farklar büyük debug seanslarına yol açıyordu. Case-sensitive olmayan bir geliştirme sunucusu, SlideShow.js ile slideshow.js arasındaki farkı gizliyordu; sorun ancak canlıya çıktıktan sonra ortaya çıkıyordu.

Tarayıcı Uyumluluğu: Internet Explorer Yılları#

Tarayıcılar arası uyumluluk, her teknik kararı şekillendiren birincil kısıttı. 2000’lerin ortasında Internet Explorer 6’nın pazar payı %60 civarındaydı ve tarayıcının web standartlarına dair kendi yorumu vardı.

O dönemde yaygın kod türü:

function addEvent(element, event, handler) {
    if (element.addEventListener) {
        element.addEventListener(event, handler, false);
    } else if (element.attachEvent) {
        element.attachEvent('on' + event, handler);
    } else {
        element['on' + event] = handler;
    }
}

function getXMLHttpRequest() {
    if (window.XMLHttpRequest) {
        return new XMLHttpRequest();
    } else if (window.ActiveXObject) {
        try {
            return new ActiveXObject('Msxml2.XMLHTTP');
        } catch (e) {
            return new ActiveXObject('Microsoft.XMLHTTP');
        }
    }
    return null;
}

Her basit operasyon browser detection ve fallback’ler gerektiriyordu. Feature’lar inşa etmekten çok compatibility layer’ları yazmaya zaman harcıyorduk.

jQuery Devrimi (2006-2012)#

jQuery 2006’da geldiğinde, büyü gibi hissettirdi. John Resig browser uyumluluğu problemini “daha az yaz, daha fazla yap” diyen zarif bir API ile çözmüştü. Ama daha da önemlisi, jQuery daha sonra modern tooling’de temel olacak konseptleri tanıttı.

Abstraction’ın Gücü#

// jQuery öncesi
function fadeOut(element) {
    var opacity = 1;
    var timer = setInterval(function() {
        if (opacity <= 0.1) {
            clearInterval(timer);
            element.style.display = 'none';
        }
        element.style.opacity = opacity;
        element.style.filter = 'alpha(opacity=' + opacity * 100 + ")";
        opacity -= opacity * 0.1;
    }, 50);
}

// jQuery ile
$('#myElement').fadeOut();

Değişimin görünen kısmı sözdizimiydi; asıl kısmı felsefiydi. jQuery, abstraction layer’larının gücü feda etmeden karmaşıklığı gizleyebileceğini kanıtladı. Bu konsept daha sonra her frontend framework’ü etkileyecekti.

jQuery Plugin’leri: İlk Package Ecosystem#

jQuery plugin’leri web’in ilk gerçek package ecosystem’iydi. Datepicker mi lazım? $('#myInput').datepicker(). Carousel mi istiyorsun? $('.carousel').slick().

Ama tooling perspektifinden işin ilginç kısmı buradaydı: jQuery plugin’lerini yönetmek vanilla JavaScript ile sahip olduğumuz aynı problemleri daha büyük ölçekte ortaya çıkardı.

2010’da tipik bir jQuery projesi şöyleydi:

<script src="js/jquery-1.4.2.min.js"></script>
<script src="js/jquery.ui.core.js"></script>
<script src="js/jquery.ui.datepicker.js"></script>
<script src="js/jquery.slick.min.js"></script>
<script src="js/jquery.validation.min.js"></script>
<script src="js/jquery.fancy-input.js"></script>
<script src="js/app.js"></script>

jQuery’nin Ölçek Sorunları#

jQuery projeleri büyüdükçe, saf JavaScript developer’larının karşılaşmadığı yeni problemler ortaya çıktı:

Plugin Çakışmaları: İki plugin de $.fn.slider’ı modify ediyordu ve hangisinin kazandığını debug etmek kabustu.

Version Cehennemi: jQuery UI 1.8, jQuery 1.4 üzerine kurulmuştu; yeni çekirdek sürümlerin desteği ancak sonraki ara sürümlerle geldi. jQuery’yi upgrade etmek potansiyel olarak her plugin’i bozabilirdi.

Performance Sorunları: 15 jQuery plugin’i yüklemek, her plugin’in fonksiyonalitesinin sadece %10’unu kullanıyor olabileceğiniz halde 300KB JavaScript indirmek demekti.

Module System Yok: Her şey hala globaldi. $.myPlugin, $.myOtherPlugin ile çakışıyordu ve büyük uygulamalarda scope sorunlarını debug etmek eziyetti.

2011 dönemi tipik bir production uygulamasından örnek:

$(document).ready(function() {
    // 12 farklı plugin'i initialize et
    $('.datepicker').datepicker();
    $('.slider').slider();
    $('.tooltip').tooltip();
    $('.validate').validate();
    $('.tabs').tabs();
    $('.accordion').accordion();
    $('.modal').modal();
    $('.carousel').carousel();
    $('.autocomplete').autocomplete();
    $('.sortable').sortable();
    $('.draggable').draggable();
    $('.charts').charts();
    
    // Sonra 500 satır custom JavaScript
    // her yerde global state'i modify ediyor
});

Takımlar büyüdükçe bunu yönetmek imkansız hale geldi. Güvenli refactor yapamıyordunuz çünkü herhangi bir değişiklik başka bir developer’ın global state’e bağımlı kodunu bozabilirdi.

”Build Tool”ların İlk Denemeleri#

2009-2010’da, akıllı developer’lar pattern’leri fark etmeye ve çözümler inşa etmeye başladılar. Bugünün build tool’larının yanında ilkeldiler; yine de manuel dosya yönetiminin sürdürülemeyeceğinin ilk kabulüydüler.

Shell Script’ler ve Makefile’lar#

İlk “build tool”lar tam anlamıyla shell script’lerdi:

#!/bin/bash
# build.sh

echo "Building application..."

# CSS dosyalarını birleştir
cat css/reset.css css/layout.css css/styles.css > dist/app.css

# JS dosyalarını birleştir
cat js/utils.js js/validation.js js/main.js > dist/app.js

# Minify et (YUI Compressor için Java kuruluysa)
java -jar yuicompressor-2.4.7.jar dist/app.css -o dist/app.min.css
java -jar yuicompressor-2.4.7.jar dist/app.js -o dist/app.min.js

echo "Build complete!"

Sofistike takımlar Makefile kullanırdı:

CSS_FILES = css/reset.css css/layout.css css/styles.css
JS_FILES = js/utils.js js/validation.js js/main.js

dist/app.css: $(CSS_FILES)
	cat $(CSS_FILES) > dist/app.css

dist/app.min.css: dist/app.css
	java -jar yuicompressor-2.4.7.jar dist/app.css -o dist/app.min.css

dist/app.js: $(JS_FILES)
	cat $(JS_FILES) > dist/app.js

dist/app.min.js: dist/app.js
	java -jar yuicompressor-2.4.7.jar dist/app.js -o dist/app.min.js

clean:
	rm -rf dist/*

build: dist/app.min.css dist/app.min.js

Çözemediğimiz Problemler#

Bu erken build denemeleri temel concatenation ve minification problemlerini çözdü, ama daha derin sorunları ortaya çıkardı:

Dependency Resolution: Hala dosyaların sırasını manuel yönetiyorduk. main.js, utils.js’ten bir fonksiyona bağımlıysa, utils.js’i önce listelemeyi hatırlamak zorundaydık.

Incremental Build’ler: Her değişiklik her şeyi yeniden build etmek demekti. utils.js’te tek karakter değişikliği bütün JavaScript bundle’ını yeniden build’lemeyi tetikliyordu.

Asset Management: Resimler, fontlar ve diğer asset’ler hala elle yönetiliyordu. Cache busting manuel olarak dosya isimlerini güncellemeyi gerektiriyordu.

Development Workflow: Build adımı o kadar yavaştı ki çoğu developer development sırasında atlıyordu. Bu da ayrı dosyalarla çalışırken işe yarayıp concatenate edilince bozulan klasik “benim makinemde çalışıyor” problemlerine yol açıyordu.

Bower: İlk Package Manager#

2012’de Twitter, Bower’ı release etti ve gelecek gibi hissettirdi. Sonunda frontend bağımlılıkları için gerçek bir package manager’ımız vardı:

bower install jquery
bower install jquery-ui
bower install bootstrap

ZIP dosyaları indirip manuel olarak projenize kopyalamak yerine, Bower bağımlılıkları otomatik halledecekti. Projeniz bir bower.json dosyası alıyordu:

{
  "name": "my-awesome-app",
  "dependencies": {
    "jquery": "~1.8.0",
    "jquery-ui": "~1.9.0",
    "bootstrap": "~2.3.0"
  }
}

Vaat ve Problemler#

Bower indirme ve versiyon yönetimi problemini çözdü, ama yenilerini yarattı:

Flat Dependency Tree: Bower her şeyi bower_components/ içine nesting olmadan koyuyordu. İki package farklı jQuery versiyonlarına bağımlıysa, Bower size seçim yaptırıyordu ve bu seçim genelde bir şeyleri bozuyordu.

Build Integration Yok: Bower dosyaları indiriyordu, ama hala bunları HTML’inize veya build script’lerinize manuel eklemeniz gerekiyordu.

Version Çakışmaları: Flat dependency tree, dependency çakışmalarının developer’a itilmesi demekti. “Package A’nın jQuery 1.8’e ihtiyacı var, Package B’nin jQuery 1.9’a, sen hallet.”

2012’de tipik bir Bower projesi şöyleydi:

<!-- Development -->
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/underscore/underscore.js"></script>
<script src="bower_components/backbone/backbone.js"></script>
<script src="bower_components/bootstrap/js/bootstrap.js"></script>
<script src="js/models/user.js"></script>
<script src="js/views/userView.js"></script>
<script src="js/app.js"></script>

Production için concatenate ve minify etmek üzere hala bir build process’e ihtiyacınız oluyordu ve script sırasını hala manuel yönetmeniz gerekiyordu.

Geriye Bakış: Modern Tooling’i Şekillendiren Dersler#

Bu dönemi yaşamak, endüstriye modern tooling tasarımını doğrudan etkileyen değerli dersler öğretti:

Otomasyon Zorunluluğu#

Manuel dosya yönetimi gerektiren her çözüm, projeler büyüdükçe sonunda çöküyordu. Bu, modern build tool’ların automation-first yaklaşımına yol açtı.

Sıra Bağımlılığının Bedeli#

Script’lerin load sırasını manuel yönetmek zorunda olmak sürekli bug kaynağıydı. Bu kavrayış, bağımlılıkları explicit yapan module sistemlerine (CommonJS, AMD, ES modules) yol açtı.

Bağımlılık Çakışmaları#

Bower’ın flat dependency yaklaşımı çözdüğünden fazla problem yarattı. Bu deneyim, npm’in nested dependency tree tasarımını ve pnpm’in sofistike resolution algoritmasını şekillendirdi.

Geliştirici Deneyimi#

Development ve production environment’ları arasındaki fark sürekli sürtünme yaratıyordu. Modern tool’lar hot reloading ve anlık geri bildirim döngüleriyle dev/prod parity’yi önceliklendiriyor.

Lock-in Olmayan Abstraction#

jQuery’nin başarısı, altta yatan DOM’a doğrudan erişime izin verirken karmaşıklığı gizlemesinden geliyordu. Modern framework’ler aynı kalıbı sürdürüyor: escape hatch’leri olan güçlü abstraction’lar.

Sonraki Devrimin Zeminleri#

2012’ye kadar, frontend tooling’in çözmesi gereken çekirdek problemlerin çoğunu belirlemiştik:

  • Dependency management: Manuel script sıralaması sürdürülemezdi
  • Asset optimization: Concatenation ve minification açıkça gerekliydi
  • Development workflow: Development ve production arasındaki fark çok genişti
  • Browser compatibility: Abstraction layer’ları esastı
  • Code organization: Global scope pollution sona ermek zorundaydı

Bu kısıtların hiçbiri ortadan kalkmadı; sadece adres değiştirdi. Module sistemleri script sırasını tool’un içine taşıdı, lock dosyaları versiyon çakışmalarını node_modules içine hapsetti ve dev/prod farkı bugün “yalnızca production build’inde bozulan plugin” olarak yaşıyor. Değişen şey, varsayılan cevabın artık toolchain ile birlikte gelmesi: yanlış yapmanın bedeli boş bir sayfa yerine tek satırlık bir config düzeltmesi.

Sırada Grunt ve Gulp var; script sırasını insan elinden alan module sistemleri (RequireJS, Browserify) ve bundle’ı uygulamanın kendisi haline getiren bundler kuşağı da onlarla birlikte geliyor.

Kaynaklar#

Frontend Tooling'in Evrimi: Bir Developer'ın Perspektifi

jQuery dosya birleştirmesinden Rust-powered bundler'lara - frontend tooling'in gerçek production problemlerini nasıl çözmek için evrildiğinin anlatılmamış hikayesi, öğrenilen dersler ve pratik deneyimlerle.

İlerleme 1/4 yazı tamamlandı

İlgili yazılar