Kısım II · İnşaat · Bölüm 29
Araçlar: Node, npm ve derleme zinciri
Paketler, betikler, paketleyici ve denetleyici (Tooling: Node, npm, bundlers)
Neden burada: Modüller çoğalınca (Bölüm 27) bir noktada kendi yazmadığın modüllere ihtiyaç duyarsın: tarih biçimleme, ekran çizme, sunucuyla konuşma. Başkalarının yazdığı hazır kodu projene çekmek, o kodun sürümünü izlemek, kendi kodunu tarayıcıya uygun hale getirmek, hataları ve biçim tutarsızlıklarını otomatik yakalamak: bütün bunları yapan araçlar bu bölümün konusu. Sonraki üç bölüm (TypeScript, React, Expo) bu araçların üstünde çalışır; yani bir sonraki projede gördüğün package.json, node_modules ve npm run dev satırlarının anlamı burada çıkıyor.
Neye dayanıyor: Bölüm 25.5: Node.js, yani tarayıcı dışında JavaScript çalıştıran program. Bölüm 27: modül, import ve bağımlılık. Bölüm 28: depo ve .gitignore; hangi dosyaların depoya girip girmeyeceği burada somutlaşıyor. Bölüm 18.5: terminalde komut çalıştırmak; bu bölümdeki her şey npm komutlarıyla yapılır.
- Kat 10Bizim uygulamamızBizim uygulamamızın kaynak kodu; araçlar onu çalıştırılabilir hale getirir.
- Kat 9Web ve tarayıcı: HTML, CSS, JavaScriptAraç zincirinin çıktısı, tarayıcıya giden HTML, CSS ve JavaScript dosyalarıdır.
- Kat 8Ağ ve internetBilgisayarların birbirine bit göndermesi; IP, TCP, DNS, HTTP. Bölüm 19–20.
- Kat 7İşletim sistemi (macOS, Windows, Linux)Dosya, süreç, bellek dağıtır; terminal buradan konuşur. Bölüm 13–18.
- Kat 6Gömülü yazılım (firmware) ve açılışDüğmeye basınca ilk çalışan program. Bölüm 12.
- Kat 5Programlama dilleri ve derleyiciBu bölümün araçları (Node, npm, Vite) bu kattaki dillerin etrafındaki alet çantasıdır: kodu yazmaya, düzenlemeye, çevirmeye yarar. Bölüm 21.
- Kat 4Makine dili ve assemblyİşlemcinin doğrudan anladığı komutlar. Bölüm 7.
- Kat 3İşlemci ve bellek (CPU, RAM)Komutları çalıştıran makine ve verinin durduğu yer. Bölüm 6, 8.
- Kat 2Devreler: hesap ve hafızaToplayıcılar ve hatırlayan devreler. Bölüm 4–5.
- Kat 1Transistör ve mantık kapılarıElektrikle açılıp kapanan anahtarlar. Bölüm 3.
- Kat 0Elektrik ve bit (zemin)Var ya da yok, 1 ya da 0. Bölüm 2.
Bu bölümün sonunda şunları bileceksin: package.json dosyasının neyi tarif ettiğini; node_modules klasörünün nereden geldiğini ve neden Git'e girmediğini; ^1.2.3 gibi bir sürüm numarasının neyi söylediğini; kilit dosyasının ne işe yaradığını; npm run dev yazınca arka planda ne olduğunu; paketleyici ile dönüştürücünün ne yaptığını; denetleyici ile biçimlendiricinin farkını; ve başkasının yazdığı kodu projene almanın neden bir güven kararı olduğunu. Bu bölüm bir araç tanıtımı gibi görünebilir; ama aslında Bölüm 27'deki "bağımlılık" fikrinin, başkalarının kodu için ölçeklenmiş halidir.
29.1 Paket, npm, package.json, node_modules; sürümleme ve kilit dosyası
Paket ve paket yöneticisi
Paket (package), bir ya da birkaç modülden oluşan, adı ve sürüm numarası olan, paylaşıma hazır bir kod kümesidir. Aklında şu zincir olsun: modül bir dosyadır (27.1); paket, bir araya getirilip ad ve sürüm verilmiş modüller topluluğudur. Bir paket başka paketlere de dayanabilir; böylece paketlerden oluşan bir ağ oluşur.
npm (Node Package Manager) Node.js'in standart paket yöneticisidir (package manager). Node.js belgelerine göre npm, projenin bağımlılıklarını (kütüphaneler ve paketler gibi, uygulamanın çalışması için gereken hazır kod parçaları) kurar, günceller ve indirmelerini yönetir; Eylül 2022'de npm kayıt defterinde 2,1 milyondan fazla paket listelendiği bildirilmiştir; bu da onu dünyanın tek bir dildeki en büyük kod deposu yapar. Başlangıçta Node.js için yapılmış olsa da ön yüz JavaScript'inde de kullanılır. Yarn ve pnpm, npm'in alternatifleridir.1 Paketlerin toplandığı yere kayıt defteri (registry) denir: indirdiğin her şey oradan gelir. Ağ katmanı açısından bu Bölüm 20'deki bir HTTP isteğinden ibarettir.
package.json: projenin kimlik kartı
Her npm projesinin kökünde package.json adlı bir dosya bulunur. npm belgelerinin vurguladığı bir ayrıntı: bu dosya gerçek JSON olmalıdır, yalnızca JavaScript nesne yazımı değil.2 (JSON'a Bölüm 25.3'te ilk bakışı yaptık; Bölüm 35'te ayrıntısına girilecek.) Yeni bir klasörde npm init -y yazdım; çıktı:
{
"name": "demo29",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Alan alan: name ve version, belgeye göre, paketi yayımlayacaksan zorunludur ve birlikte benzersiz bir kimlik oluşturur; yayımlamayacaksan isteğe bağlıdır.2 main paketin ana giriş noktasıdır (Bölüm 27.4'teki "giriş noktası"); belirtilmezse paketin kökündeki index.js varsayılır.2 type alanı, Node.js'in .js dosyalarını nasıl yorumlayacağını söyler;2 bunu Bölüm 27'de gördün: "type": "module" yazarsan dosyalar ES modülü sayılır. scripts alanını 29.2'de açıyoruz.
Bağımlılıkları kurmak
Aynı klasörde npm install ms yazdım (ms, "1 saat" gibi süreleri milisaniyeye çeviren küçük bir paket). Çıktı: added 1 package. Sonra package.json'a şu eklendi:
"dependencies": {
"ms": "^2.1.3"
}
Node.js belgelerine göre npm 5'ten beri npm install <paket> paketi otomatik olarak package.json'ın dependencies bölümüne ekler. Bayraklarla (--save-dev ya da kısaca -D) ekleme yeri değiştirilebilir. Ayrım şu: dependencies, uygulamayla birlikte üretime giden paketlerdir; devDependencies ise geliştirme araçlarıdır (test kitaplığı gibi) ve üretimde gerekmez.1 npm belgesi de aynı şeyi söyler: test çerçevelerini, dönüştürücüleri ve benzeri "geliştirme zamanı" araçlarını dependencies'e koyma, devDependencies'e koy.2 Ben de npm install --save-dev prettier yazdım ve devDependencies'te "prettier": "^3.10.1" belirdi.
node_modules: paketlerin indiği klasör
Bu komut klasöre bir de node_modules klasörü yarattı. Belgede yazdığı gibi, npm install projenin ihtiyaç duyduğu her şeyi node_modules klasörüne kurar, yoksa oluşturur.1 Kodunda import ms from "ms" yazdığında (yol yok, yalnızca ad) Node (ve paketleyici) ismin bir paket olduğunu anlar ve onu orada arar. Bu, Bölüm 27'deki göreli yoldan ("./puan.js") farklı olarak çıplak ad ile içe aktarmadır.
Gerçek boyutlara bakalım, çünkü bu sezgi kurar. Bir sonraki bölümlerde kullanacağımız türden bir proje için npm create vite@latest uyg -- --template react-ts komutuyla bir Vite + React + TypeScript iskeleti üretip npm install çalıştırdım. İskeletin package.json'ında doğrudan 9 bağımlılık vardı (2'si dependencies, 7'si devDependencies). Ama package-lock.json dosyasını sayınca 97 paket kurulduğu görüldü, ve node_modules klasörü 141 MB tuttu. Dokuz paket yazdın, doksan yedi paket geldi: çünkü her paket kendi bağımlılıklarını da getirir, onlar da kendilerininkini. Buna dolaylı bağımlılık (transitive dependency) denir. Aşağıda (29.5) bunun neden önemli olduğunu göreceksin. Ve neden node_modules'u Git'e koymadığını da şimdi anlıyorsun (28.7): 141 MB başkasının kodu, package.json'dan her an yeniden üretilebilir; geçmişe sokmak depoyu şişirir.
Sürümleme: MAJOR.MINOR.PATCH
^2.1.3 neyi söylüyor? Önce anlamsal sürümleme (semantic versioning, semver). npm belgelerinin önerisi: bir paketin sürümünü 1.0.0'dan başlat ve şöyle artır:3
| Değişiklik türü | Ad | Kural | Örnek |
|---|---|---|---|
| İlk sürüm | — | 1.0.0 ile başla | 1.0.0 |
| Geriye uyumlu hata düzeltmeleri | patch (yama) | Üçüncü rakamı artır | 1.0.1 |
| Geriye uyumlu yeni özellikler | minor (küçük) | Ortadakini artır, sonuncuyu sıfırla | 1.1.0 |
| Geriye uyumluluğu bozan değişiklikler | major (büyük) | Birinciyi artır, diğerlerini sıfırla | 2.0.0 |
Yani rakamlar bir sözdür: ilk rakam değiştiyse eski kodun bozulabilir; ortadaki değiştiyse eski kodun çalışmaya devam eder, ama yeni imkânlar var; sonuncu değiştiyse yalnızca hata düzeltildi. Bu, Bölüm 27'deki arayüz fikrinin sürüm numarasındaki karşılığı: arayüz bozuluyorsa major artar. (Hukuk benzetmesi: bir kanunda esaslı değişiklik ile redaksiyon düzeltmesi arasındaki fark.)
Bir bağımlılığı yazarken hangi güncellemeleri kabul edeceğini söylersin. Belgeye göre: ~1.0.4 yama sürümlerini kabul eder (1.0.x), ^1.0.4 küçük sürümleri de kabul eder (1.x.x), * ya da x büyük sürümleri de.3 Yani "ms": "^2.1.3", "2.1.3 ya da 2.x.x'te bu sürümden yeni olan" demektir; ama 3.0.0'ı kabul etmez (bozulabilir). Aralığın içinde kalmak ve tam olarak hangi sürümün kurulacağını seçmek ise bir sonraki kavram.
Kilit dosyası
İki kişi aynı package.json'ı yarın farklı zamanlarda kurarsa, ^2.1.3 bugün 2.1.3'ü, haftaya yayımlanmış 2.2.0'ı getirebilir; iki kişi iki farklı ortamda çalışır: "bende çalışıyor" sorunu. Çözüm kilit dosyası (lock file), npm'de package-lock.json. npm belgelerine göre bu dosya npm node_modules ağacını ya da package.json'ı değiştirdiği her işlemde otomatik üretilir ve üretilen tam ağacı tarif eder; böylece sonraki kurulumlar aradaki bağımlılık güncellemelerinden bağımsız olarak özdeş ağaçlar üretir.4 Dosyanın depoya konması amaçlanır; takım arkadaşlarının, dağıtımların ve sürekli bütünleştirmenin (CI) tam olarak aynı bağımlılıkları kurmasını garanti eder.4 Denememde oluşan kilit dosyasından bir parça:
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhj..."
}
Satır satır: version kurulan tam sürüm; resolved paketin indirildiği adres; integrity paketin içeriğinden hesaplanmış bir özet (hash). Bölüm 28'de Git'in içerikten ad ürettiğini gördün: bu aynı fikir. İndirilen paketin özeti kilitle uyuşmazsa içerik değişmiş demektir. Bir ayrım: npm install gerekirse kilidi günceller; npm ci ise otomasyon ve temiz kurulum içindir: kilit yoksa ya da package.json ile uyuşmuyorsa hata verir ve hiçbir şeyi güncellemez, ayrıca varsa eski node_modules'u silip baştan kurar.5
Expo + TypeScript projemiz (Bölüm 32) de bir npm projesi olacak: kökünde package.json (React Native, Expo, ileride TanStack Query gibi paketler), package-lock.json (depoya girer) ve node_modules (.gitignore'da, girmez). Bir agent yeni bir paket eklediğinde, git diff'te iki şey görmelisin: package.json'a bir satır ve package-lock.json'da çok sayıda satır. Eklenen paketin adına bak; neden gerektiğini sor (29.5).
29.2 Betikler ve geliştirme sunucusu
Betikler
package.json'daki scripts alanı, belgeye göre, paketin yaşam döngüsünün çeşitli anlarında çalışan komutların sözlüğüdür: anahtar olay adı, değer çalıştırılacak komuttur.2 Bu ne işe yarar? Uzun, hatırlaması zor komutlara kısa ad vermek, ve projeye bakan herkesin aynı komutla aynı işi yapması. npm run <ad> ile çalıştırılır; pre ve post önekli betikler sırasıyla öncesinde ve sonrasında kendiliğinden çalışır.6 Yukarıda ürettiğim Vite iskeletinin betikleri şöyleydi:
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "oxlint",
"preview": "vite preview"
}
Okuyalım: npm run dev geliştirme sunucusunu başlatır (vite komutu). npm run build önce tsc -b ile tip denetimi yapar (Bölüm 30), && ile ("öncekinden hata gelmediyse") sonra vite build ile üretim paketini yapar. lint denetleyiciyi çalıştırır (29.4), preview üretilen çıktıyı yerelde göstermek içindir. Kısa bir pratik not: start ve test gibi bazı betik adları npm run olmadan da çalışır; geri kalanlar için npm run gerekir.
Betiklerin güzelliği şu: komutun kendisi değişse bile (örneğin yarın denetleyici değiştirilse) ekip yine npm run lint yazar. Bu, Bölüm 27'deki derin modül fikrinin araç düzeyindeki hali: arayüz küçük (npm run lint), içerik büyük ve gizli.
Geliştirme sunucusu
Geliştirme sunucusu (dev server), kodu yazarken yerel bilgisayarında çalışan ve tarayıcıya dosyaları sunan geçici bir sunucudur. Bölüm 27'de bir uyarıyı hatırla: modüller file:// ile çalışmaz, bir HTTP sunucusu gerekir. Geliştirme sunucusu tam olarak o sunucudur: adres çubuğuna localhost ve bir port numarası yazarsın (Bölüm 19'daki port kavramı); bilgisayarın kendi kendine konuşması. Vite belgesi sunucuyu şöyle tarif eder: yerel ES modülleri üzerinde zengin geliştirmeler sağlayan, özellikle çok hızlı sıcak modül değişimi (Hot Module Replacement, HMR) olan bir geliştirme sunucusu.7 HMR, bir dosyayı kaydettiğinde sayfanın tamamını yeniden yüklemeden yalnızca o modülün güncellenmesidir; böylece her kayıtta tam yeniden yükleme ve bekleme olmaz.8 Geliştirme sunucusu yalnızca geliştirme içindir; kullanıcıların uygulaması bununla yayımlanmaz (29.3 ve Bölüm 43).
29.3 Paketleyici (Vite) ve dönüştürücü
Paketleyici neden gerekli
Bölüm 27'de modülleri tarayıcıda doğrudan çalıştırabileceğini gördün. Peki neden başka araç? Vite'in kendi anlatımı iki neden verir. Birincisi geliştirme: tarayıcı kendi başına modülleri yükleyebilir, ama uygulama büyüdükçe geleneksel araçlar (bunlara paketleyici (bundler) denir) her şeyi baştan işleyip tek bir dosyaya sığdırıyordu; uygulama büyüdükçe bekleme uzuyordu.8 İkincisi üretim: Vite belgesine göre, geliştirmede çıplak ES modülleri iyi çalışsa da, iç içe import'lardan doğan ek ağ gidiş gelişleri yüzünden bunu üretimde yayımlamak hâlâ verimsizdir; bu yüzden üretim için paketleme gereklidir.8 MDN de aynı noktayı belirtir: paketleyiciler kodu makul boyutlu parçalara bölme, küçültme (minification), ölü kodu atma ve ağaç silkeleme (tree shaking) gibi başka optimizasyonları da yapar.9
Hatırla (Bölüm 20): tarayıcı her dosya için ayrı bir HTTP isteği yapar ve her istek gecikme demektir. İki yüz modülü tek tek istemek, birkaç tek dosyayı istemekten çok daha yavaştır. Paketleyici bu iki yüz dosyayı birleştirir.
Vite
Vite (Vite) (Fransızca "hızlı", "vit" diye okunur) belgelere göre daha hızlı ve yalın bir geliştirme deneyimi hedefleyen bir derleme aracıdır ve iki parçadan oluşur: yukarıda anlattığım geliştirme sunucusu ve kodu, üretim için yüksek oranda optimize edilmiş statik dosyalar üretecek şekilde Rolldown ile paketleyen bir derleme komutu.7 Neden ayrı bir geliştirme/üretim ayrımı? Vite'in geliştirme stratejisi: kütüphaneleri bir kez önceden paketler, kendi yazdığın kaynak kodunu ise tarayıcı istedikçe ve yerel ES modülleri üzerinden sunar; böylece sunucu uygulama ne kadar büyük olursa olsun hemen başlar.8 (Vite'in iç yapısı yıllar içinde değişti: eskiden esbuild ve Rollup kullanırken artık ikisini Rust'la yazılmış Rolldown'da birleştirdiğini belgesi anlatıyor;8 detay seni ilgilendirmez, ama belgelerdeki bu isimlere yabancı kalmayasın.) Vite'in iskelet komutu npm create vite@latest'tır.7
Aynı Vite iskeletinde npm run build yazdım. Gerçek çıktı:
vite v8.3.8 building client environment for production...
✓ 20 modules transformed.
dist/index.html 0.45 kB │ gzip: 0.29 kB
dist/assets/index-D64VDMd1.css 4.10 kB │ gzip: 1.47 kB
dist/assets/index-GpEN3EET.js 222.44 kB │ gzip: 69.28 kB
✓ built in 194ms
Okuyalım: Vite 20 modülü dönüştürdü ve bunlar tek bir index-….js dosyasında birleşti: yaklaşık 222 kB (kilobyte, Bölüm 2.3'teki birimler); gzip sütunu sıkıştırılmış, ağda gidecek boyutu gösterir (yaklaşık 69 kB). Dosya adlarındaki rastgele görünen GpEN3EET parçası içeriğin özetidir: içerik değişince ad değişir, böylece tarayıcının önbelleği (Bölüm 8'in fikri) eski dosyayı yanlışlıkla kullanmaz. Üretilen dist klasörü bir web sunucusuna konulabilecek düz HTML, CSS ve JavaScript'tir: yani Kat 9'un malzemesi. Kaynak kodu ise (.tsx dosyaları) Kat 10'daki bizim yazdığımız şeydir.
Dönüştürücü
Dönüştürücü (transpiler), bir dildeki kodu aynı seviyede başka bir dile ya da sürüme çeviren araçtır. (Derleyici Bölüm 9'da yüksek seviyeli kodu makine koduna çeviriyordu; dönüştürücü yüksek seviyeden yüksek seviyeye çevirir.) Babel belgesinin tanımı: Babel, ağırlıklı olarak ECMAScript 2015+ kodunu mevcut ve eski tarayıcılarda çalışabilecek geriye uyumlu bir JavaScript sürümüne çeviren bir araç zinciridir.10 Belgenin örneği: yeni ok fonksiyonu [1,2,3].map(n => n + 1), eski sürümde function(n) { return n + 1; } olur.10 Bugün dönüştürücünün iki ana işi var: (1) eski tarayıcılar için yeni sözdizimini eski sözdizimine çevirmek; (2) tarayıcının hiç anlamadığı sözdizimini JavaScript'e çevirmek. İkinciye iki örneğimiz sıradaki bölümlerde: TypeScript (Bölüm 30) ve JSX (Bölüm 31) tarayıcıda çalışmaz; araç zinciri onları saf JavaScript'e dönüştürür. Node.js belgesi de farkı belirtir: tarayıcıda hangi tarayıcının kullanılacağı bilinmediği için eski sürümler için Babel gerekebilir; Node.js'te ise ortamı sen kontrol ettiğin için çoğu zaman gerekmez.11 Vite içinde bu dönüştürme sana görünmeden yapılır; senin görevin npm run build yazmaktır.
.tsx/.ts/.js dosyaların ──► dönüştürücü ──► paketleyici ──► dist/ (HTML+CSS+JS)
(Kat 10: senin yazdığın) (TS/JSX → JS) (birleştir, (Kat 9: tarayıcının
küçült) anladığı diller)
Bu zincirin tamamına derleme zinciri (build toolchain) ya da derleme (build) denir. Zincirin her halkası Bölüm 27'deki "derin modül" gibidir: tek komut (npm run build), arkasında büyük bir iş.
29.4 Denetleyici ve biçimlendirici (ESLint, Prettier)
İki araç, ikisi de kodu çalıştırmadan okur ama farklı iş yapar. Ayrımı karıştırma.
Denetleyici: hataları ve kötü kalıpları bulur
Denetleyici (linter), kodu çalıştırmadan inceleyip olası hataları ve kötü kalıpları raporlar. ESLint belgesine göre ESLint, ECMAScript/JavaScript kodunda bulunan kalıpları tanıyıp raporlayan bir araçtır; amacı kodu daha tutarlı yapmak ve hataları önlemektir; tamamen takılabilirdir: her kural bir eklentidir.12 Örnekler: hiç kullanılmayan bir değişken, hiçbir zaman gerçekleşmeyecek bir if dalı. Bunlar derleyicinin hata saymadığı ama büyük olasılıkla yazım hatası olan şeylerdir. Bizim Vite iskeletinde npm run lint betiği oxlint adlı hızlı bir denetleyiciyi çağırıyordu; ESLint yerleşik standart, ama araçlar değişir ve yeni iskeletler başkalarını seçebilir. Önemli olan işlev: kodu inceleyip uyarmak.
Biçimlendirici: görünümü tek tipe getirir
Biçimlendirici (formatter) kodun görünümünü (girinti, tırnak biçimi, satır uzunluğu) otomatik düzenler; anlamını değiştirmemeyi hedefler. Prettier belgesine göre Prettier'ın ilk gereksinimi, biçimlendirmeden önceki kodla birebir aynı davranışı olan geçerli kod üretmektir.13 Neden gerekli: Prettier'ın kendi ifadesiyle onu benimsemenin en büyük sebebi, üslup üzerine süregiden tartışmaları durdurmaktır: ortak bir üslup rehberinin değerli olduğu genel kabul görür, ama oraya varmak acı verici ve tatsızdır; Prettier tamamen otomatik tek "üslup rehberi"dir.14 Yani önemli olan "çift tırnak mı tek tırnak mı"nın doğru cevabı değil; herkesin aynısını kullanması ve bunun için kimsenin zaman harcamaması.
| Denetleyici (linter) | Biçimlendirici (formatter) | |
|---|---|---|
| Soru | Bu kodda hata ya da kötü kalıp var mı? | Bu kod tek tip görünüyor mu? |
| Ne yapar | Raporlar (bazen düzeltir) | Dosyayı yeniden yazar |
| Anlamı değiştirir mi | Raporlar; kuralın düzeltmesi değiştirebilir | Hayır, davranış aynı kalır |
| Örnek | ESLint, oxlint | Prettier |
Pratikte ikisi betik olur: npm run lint ve (varsa) npm run format. Ekipler bunları commit öncesi ya da çekme isteğindeki otomatik kontrollerde (Bölüm 28.5'teki "Checks" sekmesi) çalıştırır. Ousterhout'un tutarlılık önerisi tam burayı işaret eder: ona göre kuralları listeleyen bir belge yazmak, bunları denetleyen bir araç yapmak ve araçtan geçmeyen kodun depoya girmesine izin vermemek uygulanabilir yoldur.15 (Bu, ikincil özetten aldığım bir cümledir; kitabın 17. bölümüne dayanır.)
Agent'larla çalışırken denetleyici ve biçimlendirici hakem rolünü üstlenir: agent kodu yazar, npm run lint ve tip denetimi geçmiyorsa iş bitmemiştir. Bu kuralı Bölüm 46'daki AGENTS.md dosyasına yazacağız: "Bir iş bitti demeden önce npm run lint ve npm run build çalıştır." Kerem açısından değer şu: agent'ın kodunu satır satır anlamasan bile, araçlar "şu satır bir hataya benziyor" diyebilir.
29.5 Tedarik zinciri riski
Başkasının kodunu çalıştırmak bir güven kararıdır
29.1'de dokuz paket yazdım, doksan yedi paket geldi. Bu doksan yedi paketin her biri, bilgisayarında senin yetkinle çalışabilen koddur; üstelik her birini farklı bir kişi ya da ekip yazdı ve yarın güncelleyebilir. Buna tedarik zinciri riski (supply chain risk) denir: yazılımın içine giren her dış parçanın bir güven bağıdır ve zincir en zayıf halkası kadar güvenlidir. Node.js'in güvenlik belgesi saldırıyı şöyle tanımlar: bir Node.js uygulamasına tedarik zinciri saldırısı, bağımlılıklarından biri (doğrudan ya da dolaylı) ele geçirildiğinde gerçekleşir. Uygulamanın bağımlılıkları belirlerken fazla gevşek davranması (istenmeyen güncellemelere izin vermesi) ya da yazım hatalarına açık olması (yazım hatası sömürüsü, typosquatting) sebep olabilir.16
Mekanizma şöyle: saldırgan yukarı akıştaki (upstream) bir paketin kontrolünü ele geçirir, içine kötü amaçlı kod koyarak yeni bir sürüm yayımlar; uygulama o pakete sürümü sıkı sınırlamadan bağlıysa paket otomatik olarak kötü sürüme güncellenir ve uygulama tehlikeye girer.16 İşte 29.1'deki ^ işaretinin (küçük sürüm güncellemelerini otomatik kabul) arkasındaki risk bu. Belge olası saldırı yollarını şöyle sıralar: yazım hatası sömürüsü, kilit dosyası zehirlemesi, ele geçirilmiş bakımcılar, kötü amaçlı paketler ve bağımlılık karışıklığı.16 Bir ayrıntı daha: bağımlılığı tam sürüme sabitlemek bile onun dolaylı bağımlılıklarını sabitlemez; yani risk devam eder.16
Önlemler
Aynı belge önlemleri sıralar; bu bölümdeki kavramlarla eşleştirelim:16
- Kilit dosyası kullan (29.1): her bağımlılığı, doğrudan ve dolaylı, sabitler.
npm cikullan: kilit dosyasını zorunlu kılar;package.jsonile uyuşmazlık sessizce yok sayılmak yerine hata verir. - Otomatik güvenlik taraması: sürekli bütünleştirmede
npm auditgibi araçlarla bilinen açıkları denetle.16npm auditkomutu, projenin bağımlılıklarındaki bilinen güvenlik açıklarını raporlar (denememde "found 0 vulnerabilities" yazdı; bu, bugünkü bilinen açıkların listesinde bulunmadığı anlamına gelir, güvenli olduğunun kanıtı değildir).17 package.json'daki adları dikkatle kontrol et: bir harf farkı kötü amaçlı paketi getirebilir.- Betikleri kapat:
--ignore-scriptsile npm'in kurulum sırasında rastgele betik çalıştırmasını önleyebilirsin. - Bekleme süresi: belgeye göre
--min-release-age(npm 11.10.0 ve üstü) yakın zamanda yayımlanmış paketleri kurmamayı sağlar; ele geçirilmiş paketlerin çoğu saatler içinde tespit edilip kaldırıldığı için bir günlük bekleme bile kısa ömürlü saldırıların çoğuna maruz kalmayı önler.16 - Az bağımlılık: belgenin maddelerinden biri olmasa da mantıksal sonuç: her yeni paket yeni bir güven kararıdır. Çok küçük bir iş için paket eklemeden önce düşün.
Bir yapay zekâ agent'ı, çözmesi gereken küçük bir iş için kolayca "şu paketi kuralım" diyebilir; bazen var olmayan ya da adı benzer bir paketi (yukarıdaki yazım hatası sömürüsü tam bu tuzağa uyar) önerebilir. Kural: agent yeni paket eklediğinde package.json değişikliğini sen oku. Paketin adı doğru mu? Gerçekten gerekli mi (örneğin tarayıcının kendi özelliği ya da bizim yazabileceğimiz üç satır yeter mi)? Kimin tarafından yazılmış, ne kadar kullanılıyor? Bu bir uzmanlık gerektirmez, yalnızca bir duraklama gerektirir. Güvenlik konusuna Bölüm 41'de daha geniş döneceğiz.
Bölüm 25.5 (Node.js): Node, tarayıcının dışındaki JavaScript çalıştırıcısıdır; npm ve Vite onun üstünde çalışan araçlardır. Node belgesi tarayıcı ve Node arasındaki farkı da söyler: tarayıcıda DOM'a erişirsin ama dosya sistemine değil; Node'da dosya sistemi var, document ve window yok; ve Node 12'den beri hem CommonJS hem ES modüllerini destekler.11 Bölüm 27 (modüler kod): bağımlılık, bağlaşım ve derin modül fikirleri burada "başkasının kodu" için geçerli: bir paketi kullanmak, onun arayüzüne bağlanmaktır. package.json'daki sürüm aralıkları arayüz sözünün (semver) yazılı halidir. Bölüm 28 (Git): package.json ve package-lock.json depoya girer; node_modules ve derleme çıktısı dist/ girmez.
Bu bölümde neler kuruldu
Bir paket, adı ve sürümü olan paylaşılabilir kod kümesidir; npm paketleri kayıt defterinden projeye kuran paket yöneticisidir. package.json projenin kimlik kartıdır: dependencies üretime giden, devDependencies yalnızca geliştirmede gereken paketler; node_modules paketlerin indiği klasördür ve depoya girmez; paketlerin kendi paketlerini getirmesi (dolaylı bağımlılık) yüzünden 9 yazılan 97 kurulur. Anlamsal sürümleme (major.minor.patch) arayüzün bozulup bozulmadığını söyler; ^ ve ~ kabul edilen güncelleme aralığını belirler; kilit dosyası tam sürümleri sabitler ve depoya girer; npm ci bunu zorunlu kılar. Betikler uzun komutlara kısa ad verir; geliştirme sunucusu kodu yerelde sunar ve HMR ile anında günceller. Paketleyici (Vite) modülleri üretim için birleştirir ve küçültür; dönüştürücü yeni ya da tarayıcının anlamadığı sözdizimini (TypeScript, JSX) JavaScript'e çevirir. Denetleyici hata ve kötü kalıpları raporlar; biçimlendirici görünümü tek tipe getirir. Tedarik zinciri riski, kullandığın her dış paketin bir güven kararı olmasıdır; kilit dosyası, npm ci, tarama, dikkatli adlandırma ve az bağımlılık önlemlerdir. Terimler sözlükte, Bölüm 29 başlığı altında.
npm Docs, package.json: yalnızca name, version, scripts, dependencies ve devDependencies başlıklarını oku; geri kalanı başvuru kaynağıdır. Sonra npm Docs, About semantic versioning (iki dakikalık okuma) ve Vite, Getting Started ve Why Vite. Derinleşmek için Node.js Learn'ün Security Best Practices sayfasındaki Supply chain attacks bölümü.
Takıldığın her yeri yapay zekâ öğretmenine sor. Bir öneri: boş bir klasörde npm init -y, ardından küçük bir paket kur (ör. npm install ms), sonra package.json, package-lock.json ve node_modules'u aç ve öğretmenden her dosyada ne gördüğünü satır satır sana anlatmasını iste. Sonra node_modules'u silip npm install yaz: her şeyin package.json ve kilit dosyasından geri geldiğini göreceksin.
Bölüm sonu soruları
Kitaba bakmadan cevapla. Kâğıt kalem serbest.
package.json'da devDependencies bölümüne ne konur?dependencies üretime giden paketlerdir; devDependencies test kitaplığı, dönüştürücü gibi yalnızca geliştirmede gereken araçlar için. Bkz. 29.1."ms": "^2.1.3" yazarsan hangi sürümü npm kabul eder?^ geriye uyumlu (minor ve patch) güncellemeleri kabul eder; major artınca (3.0.0) uyumluluk bozulabileceğinden kabul etmez. Bkz. 29.1.package-lock.json neden depoya konulur?npm run dev yazınca genellikle ne başlar?dev betiği genellikle geliştirme sunucusunu başlatır: kodu yerelde sunar ve değişiklikleri anında yansıtır (HMR). Bkz. 29.2.npm ci neye karşı korur?npm ci onu zorunlu kılar. Böylece ele geçirilmiş yeni bir sürüm sessizce kurulmaz. Bkz. 29.5.package.json, package-lock.json ve node_modules arasındaki farkı anlat; hangisi Git'e girer, hangisi girmez ve neden?Örnek cevabı göster
package.json neye ihtiyacın olduğunu aralıklarla söyler (ör. ^2.1.3), depoya girer. package-lock.json gerçekte hangi tam sürümlerin (dolaylı bağımlılıklar dahil) kurulduğunu sabitler, depoya girer ki herkes aynısını kursun. node_modules indirilen paketlerin kendisidir, çok büyüktür (bizim iskelette 141 MB) ve ikisinden de yeniden üretilebilir; bu yüzden .gitignore'a yazılır, depoya girmez.
Örnek cevabı göster
Önce package.json değişikliğine: paketin adı doğru mu (yazım hatası sömürüsü), gerçekten gerekli mi, ya da tarayıcının kendi özelliği veya birkaç satır kodla çözülür mü? Sonra paket kimin, ne kadar kullanılıyor? Kilit dosyasında çok satır değişmesi normaldir (dolaylı bağımlılıklar), ama her yeni paket yeni bir güven kararıdır: kötü niyetli ya da ele geçirilmiş bir sürüm uygulamaya girebilir. Gerekirse npm audit ve kilit dosyası/npm ci ile sürümleri sabit tutarım.
Kaynaklar
- Node.js Learn, An introduction to the npm package manager: npm, kayıt defteri,
npm install,--save-dev,dependenciesvedevDependenciesfarkı,node_modules. - npm Docs, package.json:
name,version,main,type,scripts,dependencies,devDependencies. - npm Docs, About semantic versioning: major/minor/patch kuralı,
~ve^. - npm Docs, package-lock.json: kilit dosyasının amacı ve depoya konması.
- npm Docs, npm ci:
npm installile farkı. - npm Docs, scripts:
npm run,pre/postbetikleri. - Vite, Getting Started: Vite'in iki parçası,
npm create vite@latest. - Vite, Why Vite: geliştirmede yerel ES modülleri, üretimde paketleme gerekçesi, HMR, Rolldown.
- MDN, JavaScript modules: paketleyicilerin bölme, küçültme ve ağaç silkeleme işlevi.
- Babel, Docs: Babel nedir, ok fonksiyonu örneği.
- Node.js Learn, Differences between Node.js and the Browser.
- ESLint, Getting Started: ESLint'in tanımı.
- Prettier, Rationale: doğruluk (aynı davranış) ilkesi.
- Prettier, Why Prettier?: üslup tartışmalarını bitirmek.
- Alexander Sivji, A Philosophy of Software Design: okuma notları (ikincil özet): tutarlılık bölümü.
- Node.js Learn, Security Best Practices, "Supply chain attacks": saldırı biçimleri ve önlemler.
- npm Docs, npm audit: bilinen açıkların taranması.
- Yazarın doğrudan denemesi: Node v22.22.1, npm 9.2.0 ile
npm init -y,npm install ms,npm install --save-dev prettier;npm create vite@latest uyg -- --template react-tsvenpm run build(Vite 8.3, 97 paket, 141 MB, 222 kB çıktı). Bölümdeki çıktılar bu denemedendir.