---
title: 'Modüller, Araçlar ve Ekosistem'
source: 'https://academia.sh/tr/kurslar/javascript-ekosistemi'
language: tr
updated: '2026-08-17T04:41:29+00:00'
license: 'CC BY-SA 4.0'
lessons: 14
outcomes:
    - 'Modül sistemlerini ve paketlerin çözümlenme biçimini açıklar'
    - 'Bağımlılıkları anlamlı sürümleme kurallarıyla yönetir'
    - 'Derleme ve statik çözümleme araçlarını proje ihtiyacına göre yapılandırır'
---

# Modüller, Araçlar ve Ekosistem

Modül sistemlerinin çalışma biçimi, paket çözümleme ve sürüm yönetimi, bağımlılık riskinin denetimi ile derleme, statik çözümleme ve görev otomasyonu araçlarının sınıfları.

## Modül Sistemleri

Genel kapsam kirliliğinden modül kapsamına geçiş, iki modül sisteminin bağlama ve yükleme modelleri, birlikte çalışabilirlik ve dinamik içe aktarım.

### Modül İhtiyacı

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/modul-ihtiyaci

Genel kapsamın paylaşılmasından doğan ad çakışması sorunu, kapanışla kurulan ad alanı kalıbı ve modül kapsamının bu soruna verdiği yanıt.

Eşzamansız JavaScript ve Çalışma Zamanı kursu, tek bir programın nasıl yürüdüğünü ve
sorunlarının nasıl tanılandığını ele aldı. Bu kurs bir üst düzeye çıkar: bir program tek
dosyada durmadığında, parçaların birbirini nasıl bulduğu ve kimin neyi görebildiği sorusuna.

Soru masum görünür. Kodu iki dosyaya bölmek, dosyaları birbiri ardına yüklemekten ibaret
sanılır. Bu varsayımın nerede kırıldığını görmek, modül kavramının neden dile eklendiğini
de açıklar.

#### Genel Kapsam Ortak Bir Kaynaktır

Dilin ilk yürütme birimi **betik (script)** idi: kaynak metin ayrıştırılır ve en üst
düzeydeki bildirimleri genel kapsama yazılır. İki ayrı dosya, aynı çalışma zamanında art
arda yüklendiğinde ayrı bir dünya kurmaz; ikisi de aynı genel nesneye yazar.

Bu davranış, çalışma zamanının betik değerlendirme arayüzüyle doğrudan gözlenebilir:

```javascript
// dosya: betikler.mjs
import vm from 'node:vm';

// İki ayrı "betik", aynı genel kapsamda çalıştırılıyor.
vm.runInThisContext('var surum = "cizim-1.0"; function bicimle(d) { return "cizim: " + d; }');
vm.runInThisContext('var surum = "rapor-2.0"; function bicimle(d) { return "rapor: " + d; }');

console.log(globalThis.surum);
console.log(globalThis.bicimle('x'));
```

```
$ node betikler.mjs
rapor-2.0
rapor: x
```

İlk betiğin `surum` değişkeni ve `bicimle` fonksiyonu kayboldu. Hata yok, uyarı yok:
ikinci bildirim birincinin üzerine yazdı. Program çalışmaya devam eder ve yanlış sonuç
üretir.

Sorunun büyüklüğü dosya sayısıyla değil, **ad sayısıyla** artar. Yirmi dosyanın her biri
beş genel ad tanımlıyorsa yüz adlık ortak bir havuz oluşur ve bu havuzun çakışmasız
kalması yalnızca yazarların dikkatine bağlıdır. Programlama Temelleri kursunda kurulan
kapsam kavramı burada işletim düzeyinde bir soruna dönüşür: paylaşılan yazılabilir bir ad
alanı, yalıtımsız bir kaynaktır.

İkinci sorun sıralamadır. Bir dosya diğerinin tanımladığı fonksiyonu kullanıyorsa yükleme
sırası önemlidir, ama bu sıra kodun kendisinde yazılı değildir; onu yükleyen tarafın
bilmesi gerekir. Bağımlılık bilgisi kaynakta değil, kaynağın dışında durur.

#### Kapanışla Kurulan Ad Alanı

Dilde modül sözdizimi yokken bu iki soruna verilen yanıt, kapanışların (closure) kapsam
yalıtımı özelliğini kullanmaktı. Bir fonksiyon tanımlanıp hemen çağrılır, iç adlar o
fonksiyonun kapsamında kalır, dışarıya yalnızca seçilmiş bir nesne verilir:

```javascript
// dosya: iife.mjs
var Olcer = (function () {
  var ayirac = /\s+/;
  function sozcukler(metin) { return metin.trim().split(ayirac); }
  return { sozcukler: sozcukler };
})();

console.log(Olcer.sozcukler('bir iki  uc'));
console.log(typeof ayirac);
```

```
$ node iife.mjs
[ 'bir', 'iki', 'uc' ]
undefined
```

`ayirac` dışarıdan görünmez; yalnızca `Olcer` adı genel kapsama yazılır. Bu kalıp, JavaScript'te
Nesneler ve Fonksiyonlar kursundaki hemen çağrılan fonksiyon kalıbının doğrudan
uygulamasıdır ve gerçek bir kazanç sağlar: yüz genel ad yerine bir genel ad.

Kazandırdığı kadarını da kaybettirir. Ad alanı nesnesinin kendisi hâlâ genel kapsamdadır
ve çakışabilir. Bağımlılıklar yine sözleşmeyle taşınır: `Olcer`, kendisinden önce bir
başka ad alanının yüklenmiş olmasını bekliyorsa bunu yazılı olarak bildiremez. Dahası
hangi adın nereden geldiği, kaynağa bakılarak değil, tüm dosyalar taranarak bulunur.

#### Modül Kapsamı

Modül, bu sorunları dilin ayrıştırma düzeyinde çözer. Bir kaynak metin **modül (module)**
olarak ayrıştırıldığında en üst düzeydeki bildirimleri genel kapsama değil, o modüle özgü
bir kapsama yazılır:

```javascript
// dosya: kapsam.mjs
const surum = 'metin-olcer-1.0';
function bicimle(d) { return 'olcer: ' + d; }

console.log(typeof globalThis.surum);   // modül kapsamındaki ad genel nesnede yok
console.log(typeof globalThis.bicimle);
console.log(this);                      // modülün en üst düzeyinde this
```

```
$ node kapsam.mjs
undefined
undefined
undefined
```

Üç çıktı da aynı şeyi söyler: modülün en üst düzeyi, genel kapsam değildir. `surum` ve
`bicimle` adları dışarıdan görünmez; en üst düzeydeki `this` genel nesneye değil,
tanımsıza bağlanır.

Ad alanı kalıbıyla arasındaki fark, yalıtımın kaynağıdır. Kapanış kalıbında yalıtım
programcının kurduğu bir yapıydı ve bozulabilirdi; modülde yalıtım ayrıştırıcının
kararıdır ve bozulamaz.

#### Betik ile Modülün Ayrımı

Aynı kaynak metin, iki farklı kural kümesiyle ayrıştırılabilir. Ayrım yalnızca kapsamla
sınırlı değildir:

| Ölçüt | Betik | Modül |
|---|---|---|
| En üst düzey bildirimler | Genel kapsama yazılır | Modül kapsamında kalır |
| En üst düzeydeki `this` | Genel nesne | Tanımsız |
| Katı kip | Açıkça istenmelidir | Her zaman etkindir |
| İçe/dışa aktarım sözdizimi | Geçersiz | Geçerli |
| Değerlendirme zamanı | Karşılaşıldığı anda | Bağımlılıklar çözüldükten sonra |

Son satır belirleyicidir ve sonraki derste ayrıntılandırılacaktır. Bir metnin hangi kurala
göre ayrıştırılacağı, çalışma zamanının okuduğu bir işaretle belirlenir: dosya uzantısı,
bildirim dosyasındaki tip alanı ya da betiği yükleyen tarafın verdiği tür bilgisi. Aynı
karakter dizisi, bu işarete göre farklı anlam kazanır.

#### Bir Modül Sisteminden Beklenenler

Bir modül sisteminin çözmesi gereken sorunlar, yukarıdaki iki denemeden çıkarılabilir:

**Yalıtım.** Modülün iç adları dışarıdan görünmemeli; dışarıya ne verdiğini modülün kendisi
seçmeli.

**Açık arayüz.** Bir modülün neye bağlı olduğu ve ne sunduğu, kaynağın kendisinde yazılı
olmalı. Bağımlılık bilgisi yükleyicinin belleğinde değil, kodda durmalı.

**Belirlenimci çözümleme.** Bir bağımlılık adı, her ortamda aynı dosyaya karşılık gelmeli.
Ad çözümleme kuralı yazılı ve öngörülebilir olmalı.

**Tek değerlendirme.** Aynı modül birden çok yerden istendiğinde bir kez değerlendirilmeli;
tuttuğu durum tüm kullanıcılar için aynı olmalı. Bu güvence, Bilgisayarlar Nasıl Çalışır
kursundaki bağlama ve yükleme dersinde tanıtılan simge çözümlemesinin dil düzeyindeki
karşılığıdır: bir ad, tek bir tanıma bağlanır.

Bu dört güvence, kurs boyunca incelenecek her mekanizmanın ölçütüdür. Paket çözümlemesi
üçüncüsünün, önbellekleme dördüncüsünün gerçekleştirimidir.

#### Kursun Örneği

Kurs boyunca tek bir somut örnek geliştirilecektir: `metin-olcer` adlı küçük bir kütüphane
paketi. İşi, verilen metnin sözcük ve cümle sayısını, ortalama sözcük uzunluğunu ve en uzun
sözcüğünü hesaplamaktır. Tek dosyalık bir betik olarak başlar; modüllere bölünür, dışa
aktarım yüzeyi tanımlanır, bir bağımlılık ekler, sürüm işaretlenir, bir çalışma alanının
parçası olur ve sonunda derleme ile çözümleme araçlarına bağlanır.

İlk hâli tek dosyadır ve hiçbir şey dışa aktarmaz:

```javascript
// dosya: olcer.mjs
const SOZCUK_AYIRACI = /[^\p{L}\p{N}]+/u;
const CUMLE_AYIRACI = /[.?]+/;

function sozcuklereBol(metin) {
  return metin.split(SOZCUK_AYIRACI).filter((s) => s.length > 0);
}

function cumlelereBol(metin) {
  return metin.split(CUMLE_AYIRACI).map((c) => c.trim()).filter((c) => c.length > 0);
}

function ortalamaUzunluk(sozcukler) {
  if (sozcukler.length === 0) return 0;
  return sozcukler.reduce((t, s) => t + s.length, 0) / sozcukler.length;
}

function olc(metin) {
  const sozcukler = sozcuklereBol(metin);
  return {
    sozcukSayisi: sozcukler.length,
    cumleSayisi: cumlelereBol(metin).length,
    ortalamaUzunluk: ortalamaUzunluk(sozcukler),
  };
}

console.log(olc('Modul kendi kapsamini tasir. Betik genel kapsamda calisir.'));
```

```
$ node olcer.mjs
{ sozcukSayisi: 8, cumleSayisi: 2, ortalamaUzunluk: 6.125 }
```

Bu dosyada üç ayrı sorumluluk iç içedir: metni parçalara ayırma, sayısal ölçüm hesaplama ve
sonucu sunma. Üçü de aynı kapsamı paylaştığı için birbirinin adlarını doğrudan görür. Dosya
büyüdükçe bu görünürlük bir tasarım kararı olmaktan çıkıp bir kaza kaynağına dönüşür:
`SOZCUK_AYIRACI` sabiti yalnızca bölme işlevlerini ilgilendirdiği hâlde tüm dosyaya açıktır.

Bir sonraki derste bu dosya sorumluluklarına göre bölünecek ve bölünmenin dışa aktarım
yüzeyine etkisi incelenecektir. Bölme işleminin asıl sorusu, hangi kodun hangi dosyaya
gideceği değil, **hangi adın dışarıdan görülebilir kalacağıdır**.

#### Özet

- Betik olarak ayrıştırılan kaynağın en üst düzey bildirimleri genel kapsama yazılır; iki
  dosya aynı adı tanımladığında ikincisi birincisini sessizce geçersizleştirir.
- Kapanışla kurulan ad alanı kalıbı genel ad sayısını azaltır, ancak yalıtımı programcının
  disiplinine bırakır ve bağımlılıkları kodda bildirmez.
- Modül olarak ayrıştırılan kaynağın en üst düzey bildirimleri modül kapsamında kalır; katı
  kip her zaman etkindir ve en üst düzeydeki `this` tanımsızdır.
- Bir modül sistemi dört güvence verir: yalıtım, açık arayüz, belirlenimci çözümleme ve tek
  değerlendirme.

#### Sonraki Adım

Modül kapsamı, adların dışarı sızmasını engeller; ama bir modülün başka bir modüle bilerek
ne verdiğini henüz tanımlamadık. Sonraki ders, dilin standart modül sözdizimini ele alır:
dışa aktarım biçimleri, içe aktarımın bağ kurma davranışı ve bu sözdiziminin statik
yapısının neden yalnızca bir yazım tercihi olmadığı.

### ES Modülleri

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/es-modulleri

Adlandırılmış ve varsayılan dışa aktarım, içe aktarımın canlı bağ davranışı, bağlamanın değerlendirmeden önce gelmesi ve statik yapının ağaç sarsmaya verdiği imkân.

Önceki ders tek dosyalık ölçüm betiğini bırakırken asıl soruyu ortaya koymuştu: dosya
bölünürken hangi adın dışarıdan görülebilir kalacağına kim karar verir? Dilin standart
modül sözdiziminde bu kararı modülün kendisi verir ve kararını kaynağın içinde yazılı
olarak bildirir.

Bu ders o sözdizimini ve arkasındaki mekanizmayı ele alır. Sözdizim kısa; mekanizma,
kurs boyunca kullanılacak birkaç davranışın kaynağıdır.

#### Dışa Aktarım Yüzeyi

Ölçüm betiği üç sorumluluğa bölünür. Metni parçalara ayırma işi bir modüle, sayısal ölçüm
ikinci bir modüle, ikisini birleştiren giriş noktası üçüncüsüne gider.

```javascript
// dosya: src/bolumleme.mjs
const SOZCUK_AYIRACI = /[^\p{L}\p{N}]+/u;
const CUMLE_AYIRACI = /[.?]+/;

export function sozcuklereBol(metin) {
  return metin.split(SOZCUK_AYIRACI).filter((s) => s.length > 0);
}

export function cumlelereBol(metin) {
  return metin.split(CUMLE_AYIRACI).map((c) => c.trim()).filter((c) => c.length > 0);
}
```

```javascript
// dosya: src/istatistik.mjs
export function ortalamaUzunluk(sozcukler) {
  if (sozcukler.length === 0) return 0;
  const toplam = sozcukler.reduce((t, s) => t + s.length, 0);
  return toplam / sozcukler.length;
}

export function enUzunSozcuk(sozcukler) {
  return sozcukler.reduce((en, s) => (s.length > en.length ? s : en), '');
}
```

```javascript
// dosya: src/index.mjs
import { cumlelereBol, sozcuklereBol } from './bolumleme.mjs';
import { enUzunSozcuk, ortalamaUzunluk } from './istatistik.mjs';

export function olc(metin) {
  const sozcukler = sozcuklereBol(metin);
  return {
    sozcukSayisi: sozcukler.length,
    cumleSayisi: cumlelereBol(metin).length,
    ortalamaUzunluk: ortalamaUzunluk(sozcukler),
    enUzun: enUzunSozcuk(sozcukler),
  };
}

export { cumlelereBol, sozcuklereBol };
```

```javascript
// dosya: dene.mjs
import { olc } from './src/index.mjs';

console.log(olc('Modul kendi kapsamini tasir. Betik genel kapsamda calisir.'));
```

```
$ node dene.mjs
{
  sozcukSayisi: 8,
  cumleSayisi: 2,
  ortalamaUzunluk: 6.125,
  enUzun: 'kapsamini'
}
```

İki ayrıntı önemlidir. Birincisi, `SOZCUK_AYIRACI` sabiti artık yalnızca kendi modülünde
görünür; `export` almadığı için dışarıdan erişilemez. Görünürlük, dosya sınırının değil,
bildirimin özelliğidir.

İkincisi, `index.mjs` dosyasının son satırı iki adı **yeniden dışa aktarır**. Giriş
noktası, alt modüllerin bir bölümünü kendi yüzeyine taşıyarak paketin dışarıya sunduğu
arayüzü tek yerde toplar. `ortalamaUzunluk` ve `enUzunSozcuk` bu listeye alınmadı: iç
hesaplama ayrıntısı olarak kaldılar. Dışa aktarım yüzeyi bir tasarım kararıdır ve
kütüphanenin ileride ne kadar değişebileceğini belirler.

#### Adlandırılmış ve Varsayılan Dışa Aktarım

İki biçim vardır. **Adlandırılmış dışa aktarım (named export)** bir adı yüzeye koyar;
tüketici o adı yazarak alır. **Varsayılan dışa aktarım (default export)** adsız tek bir
değer sunar; tüketici ona istediği adı verir.

```javascript
export function olc(metin) { /* ... */ }        // adlandırılmış
export default function olc(metin) { /* ... */ }  // varsayılan
```

Ayrım yalnızca yazım kolaylığı değildir. Adlandırılmış dışa aktarımda ad, modülün
sözleşmesinin parçasıdır: yanlış yazılan bir ad bağlama aşamasında yakalanır. Varsayılan
dışa aktarımda ad tüketicinin seçimidir; iki dosyanın aynı değeri farklı adlarla
çağırması mümkündür ve otomatik araçlar bu adı izleyemez. Kütüphane arayüzlerinde
adlandırılmış biçim, adın kararlı kalması gereken durumlar için daha güçlü bir sözleşme
kurar.

#### İçe Aktarılan Ad Bir Bağdır

İçe aktarma, değeri kopyalamaz; dışa aktaran modüldeki bildirime bir **canlı bağ (live
binding)** kurar. Dışa aktaran modül değeri değiştirdiğinde, içe aktaran taraf yeni değeri
görür.

```javascript
// dosya: kayit.mjs
console.log('kayit.mjs gövdesi çalıştı');
export let cagriSayisi = 0;
export function say() { cagriSayisi += 1; }
```

```javascript
// dosya: ana.mjs
console.log('ana.mjs gövdesinin ilk satırı');
import { cagriSayisi, say } from './kayit.mjs';

console.log('içe aktarılan değer:', cagriSayisi);
say();
say();
console.log('iki çağrıdan sonra:', cagriSayisi);
```

```
$ node ana.mjs
kayit.mjs gövdesi çalıştı
ana.mjs gövdesinin ilk satırı
içe aktarılan değer: 0
iki çağrıdan sonra: 2
```

`cagriSayisi` sayısal bir değerdir, yani değiştirilemez bir ilkel; buna rağmen ikinci okuma
2 verdi. Okunan şey değer değil, bağdır.

Bağ tek yönlüdür. İçe aktaran taraf bağa yazamaz:

```javascript
// dosya: atama.mjs — kayit.mjs yukarıdaki dosyadır
import { cagriSayisi } from './kayit.mjs';

cagriSayisi = 5;
```

```
$ node atama.mjs 2>&1 | grep -E 'Error' | head -1
TypeError: Assignment to constant variable.
```

Bu kısıt, bir modülün durumunu yalnızca kendi kodunun değiştirebileceğini garanti eder.
Dışarıdan değişiklik isteniyorsa modül bunun için bir fonksiyon dışa aktarmalıdır.

#### Bağlama Değerlendirmeden Önce Gelir

Çıktıdaki satır sırası dikkat çekicidir: `kayit.mjs` gövdesi, `ana.mjs` dosyasının kendi
konsol yazımından **önce** çalıştı. Oysa içe aktarma deyimi o yazımdan sonra geliyordu.

Bunun nedeni, modül yüklemenin üç ayrı aşamada yürümesidir:

1. **Ayrıştırma ve çözümleme.** Kaynak okunur, içe aktarma belirteçleri (specifier)
   dosyalara çevrilir; bulunan her dosya için işlem yinelenir. Sonuç bir modül çizgesidir.
2. **Bağlama.** Her içe aktarılan ad, dışa aktaran modüldeki bildirimle eşleştirilir.
   Karşılığı bulunamayan ad burada hata verir.
3. **Değerlendirme.** Modül gövdeleri, bağımlılıktan bağımlıya doğru bir kez çalıştırılır.

Bilgisayarlar Nasıl Çalışır kursundaki bağlama ve yükleme dersinde bağlayıcının yaptığı iş
buydu: simgeleri çözmek ve eksik olanı bildirmek. Modül sisteminin ikinci aşaması aynı işi
kaynak düzeyinde yapar. Sonucu da benzerdir — eksik ad, kod çalışmadan yakalanır:

```javascript
// dosya: hatali.mjs — kayit.mjs yalnızca cagriSayisi ve say adlarını dışa aktarır
import { cagriSayisi, sayac } from './kayit.mjs';

console.log('bu satır hiç çalışmaz', cagriSayisi, sayac);
```

```
$ node hatali.mjs 2>&1 | grep 'SyntaxError'
SyntaxError: The requested module './kayit.mjs' does not provide an export named 'sayac'
```

Hata türü dikkat çekicidir: sözdizim hatası. Modül grafiğinin tutarlılığı, program
başlamadan denetlenen bir özelliktir. Aynı şey eksik dosya için de geçerlidir:

```javascript
// dosya: eksik-bagimlilik.mjs
console.log('bu satır çalışmaz');
import './olmayan-modul.mjs';
```

```
$ node eksik-bagimlilik.mjs 2>&1 | grep '^Error' | sed "s|$(pwd -P)/||g"
Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'olmayan-modul.mjs' imported from eksik-bagimlilik.mjs
```

Konsola hiçbir şey yazılmadı. `sed` çağrısı yalnızca iletideki mutlak dizin yolunu
kısaltmak içindir; yol, çalıştırdığınız dizine göre değişir.

#### Ad Alanı Nesnesi

Bir modülün tüm dışa aktarımları tek bir nesnede toplanarak da alınabilir:

```javascript
// dosya: adalani.mjs — src/index.mjs yukarıda tanımlanan giriş noktasıdır
import * as olcer from './src/index.mjs';

console.log(Object.keys(olcer));
console.log(Object.isFrozen(olcer));
console.log(olcer[Symbol.toStringTag]);
```

```
$ node adalani.mjs
[ 'cumlelereBol', 'olc', 'sozcuklereBol' ]
false
Module
```

Anahtarların sırası, kaynaktaki yazım sırası değil, sözlük sırasıdır: ad alanı nesnesinin
içeriği kaynak metnin düzeninden bağımsız, belirlenimci bir şekilde üretilir. Nesne
dondurulmuş değildir ama genişletilemez; üzerine yeni ad eklenemez, var olan adlar
silinemez.

#### Statik Yapının Sonucu

İçe ve dışa aktarma deyimleri yalnızca en üst düzeyde bulunabilir ve belirteçleri sabit
dizgi olmak zorundadır. Bu kısıt, ilk bakışta esnekliği azaltan bir katılık gibi görünür;
karşılığında kod çalıştırılmadan çözümlenebilir hâle gelir.

Somut kazanç, kullanılmayan dışa aktarımların çıktıdan düşürülebilmesidir; buna **ağaç
sarsma (tree shaking)** denir. Bir araç, `index.mjs` dosyasının yalnızca `olc` adını
kullandığını kaynağa bakarak görebilir ve `cumlelereBol` işlevini çıktıya hiç koymayabilir.
Aynı çıkarımı, adların çalışma anında hesaplandığı bir sistemde yapmak genel olarak
olanaksızdır.

İkinci kazanç, araçların modül çizgesini programı çalıştırmadan kurabilmesidir. Kurs
boyunca göreceğiniz paketleyici, çözümleyici ve düzenleyici desteklerinin tümü bu
özelliğe dayanır.

#### Özet

- Dışa aktarım bildirim düzeyinde belirlenir; `export` almayan ad, dosya dışından
  erişilemez ve giriş noktası yeniden dışa aktarımla arayüzü tek yerde toplar.
- Adlandırılmış dışa aktarımda ad sözleşmenin parçasıdır ve yanlış yazımı bağlama
  aşamasında yakalanır; varsayılan dışa aktarımda adı tüketici seçer.
- İçe aktarılan ad değerin kopyası değil, dışa aktaran modüldeki bildirime kurulmuş canlı
  bağdır ve içe aktaran taraftan yazılamaz.
- Yükleme üç aşamalıdır: çözümleme, bağlama, değerlendirme. Eksik ad ve eksik dosya
  hataları ilk iki aşamada, hiçbir gövde çalışmadan çıkar.
- Sözdizimin statik olması ağaç sarsmayı ve çizgenin kod çalıştırılmadan kurulmasını
  mümkün kılar.

#### Sonraki Adım

Bu modeli tek seçenek sanmak yanıltıcı olur. Dilin daha eski ve hâlâ yaygın olarak
karşılaşılan ikinci bir modül sistemi vardır; orada içe aktarma bir deyim değil, bir
fonksiyon çağrısıdır ve tüm davranışlar buradan farklılaşır. Sonraki ders o sistemi,
bu sistemle birlikte nasıl yaşadığını ve dairesel bağımlılıkta ikisinin nasıl ayrıştığını
inceler.

### CommonJS

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/commonjs

Fonksiyon çağrısıyla yükleme modeli, modül sarmalayıcısı ve önbellek, değer kopyası ile canlı bağın farkı, dairesel bağımlılıkta iki sistemin davranışı ve birlikte çalışabilirlik.

Önceki ders, içe aktarmanın bir deyim olduğu ve bağlamanın değerlendirmeden önce
tamamlandığı bir modeli kurdu. Dilin bundan önce yaygınlaşmış ikinci bir modül sistemi
vardır ve bu sistem hâlâ çalışan kodun büyük bölümünü taşır.

İkisinin bir arada bulunması bir geçiş kazası değil, kalıcı bir durumdur: bir projede iki
sistemin dosyaları yan yana durur ve birbirini çağırır. Bu dersin sorusu, ikinci sistemin
neyi farklı yaptığı ve iki modelin sınırının nerede olduğudur.

#### Yükleme Bir Fonksiyon Çağrısıdır

Bu sistemde bir modülü almak, `require` adlı bir fonksiyonu çağırmaktır; dışa aktarmak ise
`module.exports` adlı bir nesneye yazmaktır. İkisi de sözdizim değil, sıradan çalışma
zamanı işlemleridir.

Bu, kaynak metnin doğrudan çalıştırılmadığı anlamına gelir. Çalışma zamanı, dosyanın
içeriğini bir fonksiyon gövdesine sarar ve o fonksiyonu beş argümanla çağırır:

```javascript
// dosya: sarmalayici.cjs
console.log(typeof require, typeof module, typeof exports);
console.log(__filename.endsWith('sarmalayici.cjs'), typeof __dirname);
console.log(module.exports === exports);
```

```
$ node sarmalayici.cjs
function object object
true string
true
```

Bu beş ad dilin bir parçası değildir; **modül sarmalayıcısı (module wrapper)**
tarafından sağlanır. Modül kapsamı, bu konunun ilk dersinde ayrıştırma düzeyinde elde
edilen yalıtımın burada sıradan bir fonksiyon kapsamıyla elde edilmiş hâlidir. Sonuç
aynıdır: en üst düzey bildirimler genel kapsama sızmaz.

Son satır ayrıca bir tuzağı gösterir: `exports` başlangıçta `module.exports` ile aynı
nesnedir. `exports.ad = ...` yazmak ikisini de etkiler, ancak `exports = ...` yazmak
yalnızca yerel adı değiştirir ve dışa aktarım yüzeyine dokunmaz. Yüzeyi tümüyle
değiştirmek için `module.exports` atanmalıdır.

Çağrı tabanlı olmanın doğrudan sonucu, yüklemenin koşullu ve hesaplanmış olabilmesidir:

```javascript
// dosya: bicim-duz.cjs
module.exports = (olcum) => `sözcük: ${olcum.sozcukSayisi}`;
```

```javascript
// dosya: secim.cjs — bicim-duz.cjs yukarıdaki dosyadır
const kip = process.argv[2] ?? 'duz';
const bicimlendir = require(`./bicim-${kip}.cjs`);

console.log(bicimlendir({ sozcukSayisi: 8, cumleSayisi: 2 }));
```

```
$ node secim.cjs
sözcük: 8
```

Belirteç çalışma anında hesaplandığı için hangi dosyanın yükleneceği kaynağa bakılarak
bilinemez. Esnekliğin bedeli budur: statik çözümleme yapan araçlar bu bağımlılığı göremez,
ağaç sarsma uygulanamaz.

#### Önbellek ve Tek Değerlendirme

Aynı dosya birden çok kez istendiğinde gövdesi bir kez çalışır. Çalışma zamanı, çözülmüş
dosya yolunu anahtar alan bir önbellek tutar:

```javascript
// dosya: sayac.cjs
console.log('sayac.cjs gövdesi çalıştı');
let sayi = 0;
module.exports = {
  artir() { sayi += 1; return sayi; },
  get deger() { return sayi; },
};
```

```javascript
// dosya: onbellek.cjs — sayac.cjs yukarıdaki dosyadır
const a = require('./sayac.cjs');
const b = require('./sayac.cjs');

console.log('aynı nesne mi:', a === b);
a.artir();
console.log('b üzerinden okunan:', b.deger);
console.log('önbellekte kayıtlı mı:', require.resolve('./sayac.cjs') in require.cache);
```

```
$ node onbellek.cjs
sayac.cjs gövdesi çalıştı
aynı nesne mi: true
b üzerinden okunan: 1
önbellekte kayıtlı mı: true
```

Gövde bir kez çalıştı ve iki çağrı aynı nesneyi verdi. Bu konunun ilk dersinde sayılan dört
güvenceden dördüncüsü — tek değerlendirme — burada gözlenebilir bir tabloyla
gerçekleştirilmiştir. Önbelleğin anahtarı **çözülmüş mutlak yoldur**; aynı dosyaya farklı
yollardan ulaşılırsa, kayıt da farklı olur.

#### Kopya ve Canlı Bağ

`module.exports` sıradan bir nesnedir; içe aktarma da sıradan bir okuma. Bu, önceki dersin
canlı bağ davranışının burada bulunmadığı anlamına gelir:

```javascript
// dosya: kayit.cjs
let cagriSayisi = 0;
function say() { cagriSayisi += 1; }

module.exports = { cagriSayisi, say };
```

```javascript
// dosya: kopya.cjs — kayit.cjs yukarıdaki dosyadır
const { cagriSayisi, say } = require('./kayit.cjs');
const tumu = require('./kayit.cjs');

say();
say();
console.log('yıkımla alınan:', cagriSayisi);
console.log('nesne üzerinden:', tumu.cagriSayisi);
```

```
$ node kopya.cjs
yıkımla alınan: 0
nesne üzerinden: 0
```

İki okuma da sıfır verdi. `module.exports` nesnesi kurulurken `cagriSayisi` değişkeninin o
anki değeri nesneye kopyalandı; sonraki artırmalar yerel değişkeni değiştirdi ama nesneyi
değiştirmedi. Aynı senaryo standart modül sözdiziminde 2 vermişti.

Bu fark, iki sistemin arasında taşınan kodun sessiz hata kaynağıdır. Değişen bir sayaç ya
da bayrak dışa aktarılıyorsa, çağrı tabanlı sistemde okuma anında hesaplanan bir erişimci
kullanılmalıdır — `sayac.cjs` örneğindeki `get deger` tanımı bunun için vardı.

#### Dairesel Bağımlılık

İki modül birbirini içe aktardığında çizge döngü içerir. Hiçbir sistem bu döngüyü
çözemez; yapabilecekleri, döngüyü belirli bir noktada kırmaktır. Kırma noktası iki
sistemde farklıdır.

Çağrı tabanlı sistemde `require`, önbellekte **kısmen dolmuş** bir dışa aktarım nesnesi
bulur ve onu döndürür:

```javascript
// dosya: a.cjs
console.log('a: gövde başladı');
exports.ad = 'A';
const b = require('./b.cjs');
console.log('a: b.ad =', b.ad, '| b.tanim =', typeof b.tanim);
exports.tanim = function () { return 'A tanımı'; };
console.log('a: gövde bitti');
```

```javascript
// dosya: b.cjs
console.log('b: gövde başladı');
const a = require('./a.cjs');
console.log('b: a.ad =', a.ad, '| a.tanim =', typeof a.tanim);
exports.ad = 'B';
exports.tanim = function () { return 'B tanımı'; };
console.log('b: gövde bitti');
```

```
$ node a.cjs
a: gövde başladı
b: gövde başladı
b: a.ad = A | a.tanim = undefined
b: gövde bitti
a: b.ad = B | b.tanim = function
a: gövde bitti
(node:4526) Warning: Accessing non-existent property 'tanim' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
```

`b.cjs`, `a.cjs` dosyasının yarısı çalışmış hâlini gördü: `ad` tanımlıydı, `tanim`
tanımsızdı. Program çöktürmedi; eksik bir değer verdi. Uyarı satırındaki süreç numarası her
çalıştırmada değişir.

Standart modül sözdiziminde bağlama değerlendirmeden önce tamamlandığı için adlar zaten
vardır; sorun, bir adın **değerinin** henüz atanmamış olmasıdır. Fonksiyon bildirimleri
gövde çalışmadan hazır olduğundan sorunsuz kullanılır:

```javascript
// dosya: a.mjs
import { bAdi, bTanim } from './b.mjs';

console.log('a: gövde başladı');
export const aAdi = 'A';
export function aTanim() { return 'A tanımı'; }
console.log('a: b.bAdi =', bAdi, '| bTanim() =', bTanim());
```

```javascript
// dosya: b.mjs
import { aAdi, aTanim } from './a.mjs';

console.log('b: gövde başladı');
console.log('b: aTanim() =', aTanim());
export const bAdi = 'B';
export function bTanim() { return 'B tanımı'; }
```

```
$ node a.mjs
b: gövde başladı
b: aTanim() = A tanımı
a: gövde başladı
a: b.bAdi = B | bTanim() = B tanımı
```

`b.mjs`, henüz gövdesi çalışmamış olan `a.mjs` dosyasının fonksiyonunu çağırabildi. Aynı
düzenekte `const` ile tanımlı bir değere erken erişilirse sonuç sessiz bir tanımsız değil,
hatadır:

```javascript
// dosya: a2.mjs
import { bAdi } from './b2.mjs';

export const aAdi = 'A';
console.log('a2: bAdi =', bAdi);
```

```javascript
// dosya: b2.mjs
import { aAdi } from './a2.mjs';

console.log('b2: aAdi =', aAdi);
export const bAdi = 'B';
```

```
$ node a2.mjs 2>&1 | grep '^ReferenceError'
ReferenceError: Cannot access 'aAdi' before initialization
```

Ayrım özetlenebilir: biri eksik veriyi sessizce geçirir, diğeri erişimi durdurur. İkisinde
de asıl çözüm aynıdır — dairesel bağımlılık bir tasarım kokusudur ve ortak parçanın üçüncü
bir modüle çıkarılmasıyla giderilir.

#### Birlikte Çalışabilirlik

İki sistem aynı projede bulunduğunda hangi dosyanın hangi kurala göre okunacağı bir işaretle
belirlenir: `.mjs` uzantısı standart modülü, `.cjs` uzantısı çağrı tabanlı modülü zorlar;
uzantısı `.js` olan dosyalar için en yakın bildirim dosyasındaki tip alanına bakılır.

Standart modülden çağrı tabanlı modüle erişim doğrudandır:

```javascript
// dosya: eski-bicim.cjs
function bicimle(sayi) { return sayi.toFixed(2); }

module.exports = { bicimle, surum: '1.0.0' };
```

```javascript
// dosya: tuketici.mjs — eski-bicim.cjs yukarıdaki dosyadır
import eski from './eski-bicim.cjs';
import { bicimle } from './eski-bicim.cjs';
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const elle = require('./eski-bicim.cjs');

console.log('varsayılan dışa aktarım:', eski);
console.log('adlandırılmış içe aktarım:', bicimle(3.14159));
console.log('createRequire ile:', elle === eski);
```

```
$ node tuketici.mjs
varsayılan dışa aktarım: { bicimle: [Function: bicimle], surum: '1.0.0' }
adlandırılmış içe aktarım: 3.14
createRequire ile: true
```

`module.exports` nesnesi varsayılan dışa aktarım olarak gelir. Adlandırılmış içe aktarımın
çalışması ise bir çıkarıma dayanır: çalışma zamanı kaynağı tarayarak hangi adların atandığını
tahmin eder. Tarama kapsam çözümlemesi yapmaz, bu yüzden koşul ya da
döngü içindeki atamaları da — hiç çalışmayacak olsalar bile — ada sayar. Tutmadığı yer
hesaplanmış anahtarlardır: adı çalışma zamanında hesaplanan bir atama taranamaz ve orada
yalnızca varsayılan dışa aktarım kullanılabilir.

Ters yön daha kısıtlıdır. `require` eşzamanlı bir çağrıdır: değeri hemen döndürmek
zorundadır. Standart modül çizgesi ise **üst düzey `await` (top-level await)** içerebilir,
yani değerlendirmesi eşzamansız olabilir. Böyle bir çizge eşzamanlı çağrıyla yüklenemez:

```javascript
// dosya: bekleyen.mjs
const gecikme = await new Promise((coz) => setTimeout(() => coz('hazır'), 10));
export const durum = gecikme;
```

```javascript
// dosya: bekleyen-cagri.cjs — bekleyen.mjs yukarıdaki dosyadır
const m = require('./bekleyen.mjs');
console.log(m.durum);
```

```
$ node bekleyen-cagri.cjs 2>&1 | grep '^Error'
Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead. To see where the top-level await comes from, use --experimental-print-required-tla.
```

Sınır teknik bir zorunluluktur, bir eksiklik değil: eşzamanlı bir çağrı, tamamlanması
zamana yayılan bir işi bekleyemez. Hata iletisinin işaret ettiği çözüm, sonraki dersin
konusudur.

#### Özet

- Çağrı tabanlı sistemde içe aktarma bir fonksiyon çağrısı, dışa aktarma bir nesneye
  yazmadır; belirteç çalışma anında hesaplanabilir, bu yüzden statik çözümleme yapılamaz.
- Modül sarmalayıcısı, kapsam yalıtımını sıradan bir fonksiyon kapsamıyla sağlar ve
  `require`, `module`, `exports` adlarını dışarıdan verir.
- Yüklenen modüller çözülmüş yola göre önbelleğe alınır; gövde bir kez çalışır ve tüm
  çağrılar aynı nesneyi alır.
- Dışa aktarılan ilkel değerler kopyadır; canlı bağ davranışı için erişimci tanımlanmalıdır.
- Dairesel bağımlılıkta çağrı tabanlı sistem kısmen dolmuş bir nesne verir, standart sistem
  erken erişimi hata ile durdurur.
- Uzantı ve tip alanı hangi kuralın uygulanacağını belirler; eşzamanlı çağrı, üst düzey
  `await` içeren bir çizgeyi yükleyemez.

#### Sonraki Adım

Eşzamanlı çağrının yapamadığını yapan bir biçim vardır: söz döndüren, çalışma anında
çağrılabilen ve belirteci hesaplanabilen bir içe aktarma. Sonraki ders bu biçimi, kod
bölme ve gecikmeli yükleme kalıplarını ve modül kaydının bu kalıplardaki rolünü ele alır.

### Dinamik İçe Aktarım

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/dinamik-ice-aktarim

Söz döndüren içe aktarma ifadesi, hesaplanmış belirteçle koşullu yükleme, modül kaydının tek değerlendirme güvencesi ve kod bölmenin maliyet dengesi.

Önceki ders, eşzamanlı bir çağrının zamana yayılan bir yüklemeyi bekleyemeyeceğini gösterip
hata iletisinin işaret ettiği çözümü sonraya bırakmıştı. Bu ders o çözümü ele alır: söz
(promise) döndüren, ifade konumunda kullanılabilen ve belirteci çalışma anında hesaplanabilen
bir içe aktarma biçimi.

Biçimin sağladığı esneklik, iki modül sisteminin özelliklerini birleştirir: statik biçimin
belirlenimci çözümlemesi ile çağrı tabanlı biçimin koşulluluğu. Karşılığında yeni bir
sorumluluk getirir — yükleme artık zamana yayılmış bir iştir ve başarısız olabilir.

#### İfade Olarak İçe Aktarma

Dinamik içe aktarım, sonucu bir söz olan bir ifadedir. Fonksiyon gövdesinde, koşul içinde,
döngüde — programın her yerinde kullanılabilir ve her iki modül sisteminden çağrılabilir.
Sözün çözüldüğü değer, önceki derste tanıtılan ad alanı nesnesidir.

```javascript
// dosya: bekleyen.mjs
const gecikme = await new Promise((coz) => setTimeout(() => coz('hazır'), 10));
export const durum = gecikme;
```

```javascript
// dosya: bekleyen-cagri2.cjs — bekleyen.mjs yukarıdaki dosyadır
import('./bekleyen.mjs').then((m) => {
  console.log('eşzamansız yükleme sonucu:', m.durum);
});
console.log('çağrı hemen döndü');
```

```
$ node bekleyen-cagri2.cjs
çağrı hemen döndü
eşzamansız yükleme sonucu: hazır
```

Önceki derste eşzamanlı çağrıyla yüklenemeyen modül, bu biçimle yüklendi. Çıktı sırası
mekanizmayı açıklar: çağrı hemen döndü, gövde çalışmaya devam etti, modül hazır olduğunda
geri çağrı yürütüldü. Eşzamansız JavaScript kursunda kurulan söz modeli burada modül
yüklemeye uygulanmıştır.

Sözdizim bir fonksiyon çağrısına benzer ama fonksiyon değildir: başka bir ada atanamaz,
argümansız çağrılamaz. Belirteci ise sabit olmak zorunda değildir.

#### Hesaplanmış Belirteç

Ölçüm kütüphanesi sonucu farklı biçimlerde sunabilir. Her biçim ayrı bir modüldür ve hangisinin
kullanılacağı çalışma anında belirlenir:

```javascript
// dosya: bicimlendirici/duz.mjs
console.log('duz.mjs yüklendi');

export function bicimlendir(olcum) {
  return `sözcük: ${olcum.sozcukSayisi}, cümle: ${olcum.cumleSayisi}`;
}
```

```javascript
// dosya: bicimlendirici/tablo.mjs
console.log('tablo.mjs yüklendi');

export function bicimlendir(olcum) {
  return Object.entries(olcum)
    .map(([anahtar, deger]) => `${anahtar.padEnd(16)}${deger}`)
    .join('\n');
}
```

```javascript
// dosya: rapor.mjs — yukarıdaki iki biçimlendirici dosyasını kullanır
const olcum = { sozcukSayisi: 8, cumleSayisi: 2 };

async function rapor(bicim) {
  const modul = await import(`./bicimlendirici/${bicim}.mjs`);
  return modul.bicimlendir(olcum);
}

console.log(await rapor('duz'));
console.log('---');
console.log(await rapor('tablo'));
console.log('---');
console.log(await rapor('duz'));
```

```
$ node rapor.mjs
duz.mjs yüklendi
sözcük: 8, cümle: 2
---
tablo.mjs yüklendi
sozcukSayisi    8
cumleSayisi     2
---
sözcük: 8, cümle: 2
```

Üçüncü çağrıda `duz.mjs` yeniden yüklenmedi. Çalışma zamanı, çözülmüş her belirteci bir
**modül kaydında (module registry)** tutar; ikinci istek kayıttaki değerlendirilmiş modülü
döndürür. Önceki iki derste sayılan tek değerlendirme güvencesi, dinamik yüklemede de
geçerlidir. Kayıt anahtarı çözülmüş adrestir: aynı dosyaya iki farklı belirteçle ulaşılsa
bile tek kayıt oluşur.

Esnekliğin bedeli, önceki derste çağrı tabanlı sistem için söylenenin aynısıdır. Belirteç
bir şablon dizgisiyle üretildiğinde hangi dosyanın yükleneceği kaynağa bakılarak
bilinemez. Statik çözümleme yapan bir araç için bu, çizgenin kopması demektir; araç ya
dizinin tamamını çıktıya katar ya da hiçbirini katamaz.

Bu nedenle, seçim sonlu bir kümedense belirteci sabit tutan biçim yeğlenir:

```javascript
// dosya: sabit.mjs — yukarıdaki iki biçimlendirici dosyasını kullanır
const kosul = process.argv[2] === 'tablo';
const modul = kosul ? await import('./bicimlendirici/tablo.mjs') : await import('./bicimlendirici/duz.mjs');
console.log(modul.bicimlendir({ sozcukSayisi: 8, cumleSayisi: 2 }));
```

```
$ node sabit.mjs
duz.mjs yüklendi
sözcük: 8, cümle: 2
```

Davranış aynıdır, ama iki belirteç de sabittir. Araç her iki dosyayı da görebilir ve
ayrı parçalar olarak işaretleyebilir.

#### Yükleme Başarısız Olabilir

Deyim biçiminde eksik bir modül, program başlamadan hata verirdi. Dinamik biçimde yükleme
çalışma anında gerçekleşir; bu yüzden başarısızlık, yakalanabilir bir reddetmedir:

```javascript
// dosya: eksik.mjs
try {
  await import('./bicimlendirici/yok.mjs');
} catch (hata) {
  console.log('yakalandı:', hata.code);
}
console.log('program sürüyor');
```

```
$ node eksik.mjs
yakalandı: ERR_MODULE_NOT_FOUND
program sürüyor
```

Program çökmedi. Bu, isteğe bağlı yeteneklerin yüklenmesinde yararlı bir davranıştır: bir
eklenti bulunamadığında ana işlev çalışmaya devam edebilir. Aynı davranış, bir sorumluluk
da doğurur — zorunlu bir bağımlılık dinamik olarak yükleniyorsa, eksikliği çalışma anına
kadar fark edilmez. Bağlama zamanı ile hata görme zamanı arasındaki ilişki, Bilgisayarlar
Nasıl Çalışır kursundaki bağlama ve yükleme dersinde kurulan ilişkinin aynısıdır.

Ağ üzerinden yüklenen bir modülde reddetmenin nedeni yalnızca eksik dosya değildir:
kesilen bağlantı, zaman aşımı ve hatalı yanıt da aynı yola çıkar. Bu yüzden dinamik
yükleme çağrıları, Eşzamansız JavaScript kursunda tanıtılan yeniden deneme ve geri çekilme
kalıplarının uygulanabileceği noktalardır.

#### Çözülen Değerin Yapısı

Sözün çözüldüğü ad alanı nesnesi, deyim biçimindekiyle aynı nesnedir; varsayılan dışa
aktarım orada `default` adıyla durur. Çağrı tabanlı bir modül dinamik olarak yüklendiğinde
bu nesnenin içeriği, önceki derste anlatılan ad çıkarımının sonucudur:

```javascript
// dosya: eski-bicim.cjs
function bicimle(sayi) { return sayi.toFixed(2); }

module.exports = { bicimle, surum: '1.0.0' };
```

```javascript
// dosya: dinamik-cjs.mjs — eski-bicim.cjs yukarıdaki dosyadır
const m = await import('./eski-bicim.cjs');

console.log('ad alanı anahtarları:', Object.keys(m));
console.log('varsayılan:', m.default.surum, '| adlandırılmış:', m.surum);
```

```
$ node dinamik-cjs.mjs
ad alanı anahtarları: [ 'bicimle', 'default', 'module.exports' ]
varsayılan: 1.0.0 | adlandırılmış: undefined
```

Çıkarım `bicimle` adını buldu, `surum` adını bulamadı: biri kısaltılmış özellik yazımıyla
bir bildirime bağlanmıştı, diğeri doğrudan bir dizgi değeriydi. Sonuç, çağrı tabanlı bir
modülden ad alırken güvenilir tek yolun `default` üzerinden geçmek olduğudur. Anahtar
listesindeki üçüncü ad çalışma zamanının eklediği bir kolaylıktır ve taşınabilir değildir.

Yükleme sözü bir değere de bağlanabilir; bu, bir modülü ilk gerektiğinde yükleyip sonraki
çağrılarda aynı sözü paylaşan tembel başlatma kalıbını verir:

```javascript
// dosya: tembel.mjs — bicimlendirici/tablo.mjs yukarıda tanımlanan modüldür
let bekleyen;

function bicimlendiriciAl() {
  bekleyen ??= import('./bicimlendirici/tablo.mjs');
  return bekleyen;
}

const [a, b] = await Promise.all([bicimlendiriciAl(), bicimlendiriciAl()]);
console.log('aynı söz mü:', bicimlendiriciAl() === bekleyen);
console.log('aynı ad alanı mı:', a === b);
console.log(a.bicimlendir({ sozcukSayisi: 8 }));
```

```
$ node tembel.mjs
tablo.mjs yüklendi
aynı söz mü: true
aynı ad alanı mı: true
sozcukSayisi    8
```

#### Kod Bölme

Dinamik içe aktarımın araç düzeyindeki karşılığı **kod bölmedir (code splitting)**. Bir
paketleyici, dinamik içe aktarım gördüğü yerde çizgeyi keser ve o dalı ayrı bir çıktı
dosyasına — bir **parçaya (chunk)** — koyar. Ana çıktı yalnızca parçanın adresini taşır;
dosya ilk gerektiğinde istenir.

Kazanç, ilk yükleme maliyetinin küçülmesidir. Bir uygulamanın nadiren açılan bölümü, hiç
açılmadığında hiç indirilmez. Ölçüm kütüphanesinde tablo biçimlendiricisi buna örnektir:
çoğu çağrı düz biçimi kullanır.

Maliyet üç kalemdir:

**Ek gidiş dönüş.** Parça, gerektiği anda ayrıca istenir. Kullanıcının beklediği an,
ölçümün en pahalı olduğu andır.

**Yinelenen kod.** İki parça aynı yardımcı modülü kullanıyorsa, araç ya kodu iki parçaya
da kopyalar ya da üçüncü bir ortak parça üretir. İkisi de karmaşıklık ekler.

**Hata yolu.** Her parça bir başarısızlık noktasıdır; her dinamik yükleme çağrısı bir hata
işleyicisi gerektirir.

Bu nedenle bölme, her dinamik içe aktarımın otomatik sonucu değil, ölçüme dayalı bir
karardır. Bölmenin kazandırdığı süre, ek isteğin maliyetinden büyük olmalıdır. Sık
kullanılan bir kod yolunu bölmek başarımı düşürür.

#### Ne Zaman Kullanılmamalı

Dinamik biçim, deyim biçiminin yerini almaz. Modülün her çalıştırmada ve hemen gerektiği
durumlarda deyim biçimi doğru seçimdir: bağımlılık kaynağa yazılmış olur, eksikliği
program başlamadan yakalanır ve araçlar çizgeyi görebilir.

Dinamik biçim üç durumda yerindedir: modül yalnızca belirli bir koşulda gerekiyorsa,
hangi modülün gerekeceği çalışma anında belirleniyorsa, ya da modülün yüklenmesi ilk
açılışın dışına ertelenebiliyorsa. Bunların dışında kullanıldığında, kazanç sağlamadan
çizgeyi araçlardan gizler.

#### Özet

- Dinamik içe aktarım söz döndüren bir ifadedir; her konumda ve her iki modül sisteminden
  çağrılabilir, sözün çözüldüğü değer ad alanı nesnesidir.
- Belirteç çalışma anında hesaplanabilir; hesaplanmış belirteç statik çözümlemeyi
  imkânsızlaştırdığı için sonlu seçimlerde sabit belirteçli biçim yeğlenir.
- Modül kaydı, çözülmüş adres başına tek değerlendirme güvencesini dinamik yüklemede de
  korur.
- Yükleme başarısızlığı yakalanabilir bir reddetmedir; bu, isteğe bağlı yetenekler için
  kazanç, zorunlu bağımlılıklar için gecikmiş hata anlamına gelir.
- Kod bölme ilk yükleme maliyetini düşürür; karşılığında ek istek, yinelenen kod ve yeni
  hata yolları getirir, bu yüzden ölçüme dayalı bir karardır.

#### Sonraki Adım

Buraya kadar tüm belirteçler göreli dosya yollarıydı. Oysa gerçek projelerde içe aktarımların
çoğu `metin-olcer` gibi çıplak adlardır ve bu adların hangi dosyaya karşılık geldiği dosya
sisteminde yazılı değildir. Sonraki konu bu çözümlemeyi ele alır: bir paketin bildirim
dosyası, çıplak adın aranma kuralı ve kurulu ağacın kilit dosyasıyla sabitlenmesi.

## Paket Yönetimi

Bildirim ve kilit dosyası, çıplak belirteçlerin çözümlenmesi, anlamlı sürümleme kuralları, bağımlılık ağacındaki çakışmalar, çalışma alanları ve bağımlılık riskinin yönetimi.

### Paket Kaydı ve Bağımlılıklar

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/paket-kaydi-ve-bagimliliklar

Bildirim dosyasının alanları, çıplak belirtecin dizin ağacında aranması, dışa aktarım haritasıyla kapsülleme, bağımlılık türleri ve kilit dosyasının işlevi.

Modül sistemleri konusundaki tüm belirteçler göreli dosya yollarıydı. Gerçek projelerde
içe aktarımların çoğu `bicim-yardimcisi` gibi çıplak adlardır: ne uzantı taşırlar ne de
dizin bilgisi.

Bu ad ile dosya arasındaki bağı kuran şey, dosya sistemindeki bir kural ve paketin kendi
bildirimidir. Ders, önce paketin ne olduğunu tanımlar, sonra çıplak adın nasıl arandığını
adım adım izler.

#### Bildirim Dosyası

Bir **paket (package)**, kendi bildirimini taşıyan bir dizindir. **Bildirim dosyası
(manifest)** paketin kimliğini, giriş noktalarını ve bağımlılıklarını tanımlar. Linux'a
Giriş kursundaki paket yöneticisi kavramı dersinde tanıtılan üstverinin dil düzeyindeki
karşılığıdır; alanların işlevi de benzerdir.

Ölçüm kütüphanesinin bildirimi — proje kökündeki `package.json`:

```json
{
  "name": "metin-olcer",
  "version": "0.1.0",
  "type": "module",
  "exports": {
    ".": "./src/index.mjs",
    "./bolumleme": "./src/bolumleme.mjs"
  },
  "files": ["src"],
  "dependencies": {
    "bicim-yardimcisi": "^1.4.0"
  }
}
```

Alanların taşıdığı kararlar:

| Alan | Kararı |
|---|---|
| `name` | Paketin kayıt içindeki benzersiz kimliği |
| `version` | Bu yayının sürümü; kimliğin ikinci parçası |
| `type` | Uzantısı `.js` olan dosyaların hangi modül sistemine göre okunacağı |
| `exports` | Dışarıdan hangi yolların içe aktarılabileceği |
| `files` | Yayımlanan arşive hangi dosyaların gireceği |
| `dependencies` | Çalışması için gereken paketler ve kabul edilen sürüm aralıkları |

Son üç alan tasarım kararıdır, teknik zorunluluk değildir. Hangi dosyanın dışarıdan
görüleceğine, hangisinin yayımlanacağına ve hangi sürümlerin kabul edileceğine paketin
yazarı karar verir.

#### Bağımlılık Eklemek

Ölçüm kütüphanesi, ortalama uzunluğu sabit basamakla yazdırmak için küçük bir yardımcıya
bağlanır. Yardımcı paket, kendi bildirimini taşıyan bir dizindir —
`node_modules/bicim-yardimcisi/package.json`:

```json
{
  "name": "bicim-yardimcisi",
  "version": "1.4.0",
  "type": "module",
  "main": "index.mjs"
}
```

```javascript
// dosya: node_modules/bicim-yardimcisi/index.mjs
export function ondalik(sayi, basamak = 2) {
  return sayi.toFixed(basamak);
}
```

Kütüphaneye eklenen rapor modülü bu paketi çıplak adıyla içe aktarır. Aşağıdaki
dosyanın içe aktardığı `src/index.mjs` ve onun kullandığı `src/bolumleme.mjs` ile
`src/istatistik.mjs`, bu konudaki ES Modülleri dersinde yazılmıştı; ölçüm kütüphanesi
aynı ağaç üzerinde büyümeye devam ediyor:

```javascript
// dosya: src/rapor.mjs — src/index.mjs, ölçüm işlevini dışa aktaran giriş noktasıdır
import { ondalik } from 'bicim-yardimcisi';
import { olc } from './index.mjs';

export function rapor(metin) {
  const olcum = olc(metin);
  return `${olcum.sozcukSayisi} sözcük, ortalama ${ondalik(olcum.ortalamaUzunluk)} karakter`;
}
```

```javascript
// dosya: dene.mjs
import { rapor } from './src/rapor.mjs';

console.log(rapor('Modul kendi kapsamini tasir. Betik genel kapsamda calisir.'));
console.log(import.meta.resolve('bicim-yardimcisi').split('/node_modules/')[1]);
```

```
$ node dene.mjs
8 sözcük, ortalama 6.13 karakter
bicim-yardimcisi/index.mjs
```

Burada iki ayrı olgu vardır ve birbirini gerektirmez: paketin **bildirimde yazılı olması**
ve paketin **dizinde bulunması**. Bildirimde yazılı olup dizinde bulunmayan bir paket
çözümleme hatası verir:

```javascript
// dosya: eksik-paket.mjs
import { ondalik } from 'olmayan-paket';

console.log(ondalik(1.5));
```

```
$ node eksik-paket.mjs 2>&1 | grep '^Error' | sed "s|$(pwd -P)/||g"
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'olmayan-paket' imported from eksik-paket.mjs
```

Ters durum daha sinsidir: dizinde bulunup bildirimde yazılı olmayan bir paket, yerelde
sorunsuz çalışır. Çözümleme yalnızca dosya sistemine bakar; bildirimi denetlemez. Paket
başka bir yere kurulduğunda o dosya orada bulunmayabilir ve hata ilk kullanımda ortaya
çıkar. Bir bağımlılık kullanılıyorsa bildirimde de yazılı olmalıdır; bu kural, sonraki
derslerde ele alınacak ağaç düzenlemelerinin altında yatan varsayımdır.

#### Çıplak Belirteç Nasıl Çözülür

Çıplak ad bir dosya yolu değildir; aranması gereken bir addır. Kural tek cümleyle
yazılabilir: içe aktarmayı yapan dosyanın dizininden başlanır, her dizinde `node_modules`
alt dizini içinde bu ada bakılır, bulunamazsa bir üst dizine çıkılır.

Kuralı doğrudan uygulayan bir betik, aramanın gerçekten böyle yürüdüğünü gösterir:

```javascript
// dosya: src/derin/arama.mjs
import { dirname, join } from 'node:path';
import { existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';

// Çıplak belirtecin arandığı dizinler: bulunulan dizinden köke doğru.
let dizin = dirname(fileURLToPath(import.meta.url));
while (true) {
  const aday = join(dizin, 'node_modules', 'bicim-yardimcisi');
  console.log(`${existsSync(aday) ? 'BULUNDU ' : 'yok     '} ${aday}`);
  if (existsSync(aday) || dizin === '/') break;
  dizin = dirname(dizin);
}

console.log('çalışma zamanının çözdüğü:', import.meta.resolve('bicim-yardimcisi'));
```

```
$ node src/derin/arama.mjs | sed "s|$(pwd -P)|.|"
yok      ./src/derin/node_modules/bicim-yardimcisi
yok      ./src/node_modules/bicim-yardimcisi
BULUNDU  ./node_modules/bicim-yardimcisi
çalışma zamanının çözdüğü: file://./node_modules/bicim-yardimcisi/index.mjs
```

`sed` çağrısı çıktıdaki mutlak yolu kısaltır; son satırdaki adres, o kısaltmanın adres
biçimine uygulanmış hâlidir. Aramanın kendisi dört özelliği açığa çıkarır:

**Konuma bağlıdır.** Aynı ad, farklı dizinlerdeki iki dosya için farklı pakete çözülebilir.
Bu, sonraki derslerde ele alınacak sürüm çakışması çözümünün temelidir.

**Kayıt gerektirmez.** Çözümleme tümüyle yereldir; ağa çıkılmaz. Ağ, paketler dizine
yerleştirilirken kullanılır, çözümleme sırasında değil.

**Paketin kendisi konuşur.** Dizin bulunduktan sonra hangi dosyanın yükleneceğini paketin
bildirimi söyler.

**Belirlenimcidir.** Aynı dosya ağacında aynı belirteç her zaman aynı dosyaya çözülür.
Modül sistemlerinin ilk dersinde sayılan üçüncü güvence budur.

#### Dışa Aktarım Haritası

Paket dizini bulunduğunda, dışarıdan hangi yolların erişilebilir olduğunu bildirimdeki
harita belirler. Haritada listelenmeyen bir yol, dizinde bulunsa bile içe aktarılamaz.

Aşağıdaki iki dosya, `metin-olcer` paketini kuran ayrı bir tüketici projededir ve o
projenin dizininde çalıştırılır. Tüketicinin kendi bildirimi — `tuketici/package.json`:

```json
{
  "name": "tuketici",
  "version": "1.0.0",
  "type": "module",
  "dependencies": { "metin-olcer": "^0.1.0" }
}
```

```javascript
// dosya: tuketici/kullan.mjs
import { olc } from 'metin-olcer';
import { sozcuklereBol } from 'metin-olcer/bolumleme';

console.log(olc('Bir cumle. Iki cumle.').sozcukSayisi);
console.log(sozcuklereBol('bir iki uc').length);
```

```
$ node kullan.mjs
4
3
```

```javascript
// dosya: tuketici/derin.mjs — haritada olmayan bir iç dosyaya erişmeye çalışır
import { ortalamaUzunluk } from 'metin-olcer/src/istatistik.mjs';

console.log(ortalamaUzunluk(['bir', 'iki']));
```

```
$ node derin.mjs 2>&1 | grep -E '^Error' | sed "s|$(pwd -P)/||g"
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './src/istatistik.mjs' is not defined by "exports" in node_modules/metin-olcer/package.json imported from derin.mjs
```

Bu, paket düzeyinde kapsüllemedir. Haritası olmayan bir pakette her iç dosya dışarıya
açıktır; tüketiciler iç dosyalara bağlanır ve dosyaların yeniden düzenlenmesi kırıcı
değişikliğe dönüşür. Harita, kütüphanenin hangi yollarının sözleşme olduğunu ve hangi
dosyalarının serbestçe değiştirilebileceğini ayırır.

#### Bağımlılık Türleri

Bildirimde bağımlılıklar tek bir liste değildir; her liste farklı bir soruya yanıt verir.

**Çalışma bağımlılıkları.** Paket çalışırken gereken paketler. Paketi kuran herkes bunları
da kurar.

**Geliştirme bağımlılıkları.** Yalnızca paketi geliştirirken gerekenler: test koşucusu,
çözümleyici, derleme aracı. Paketi kullanan taraf bunları kurmaz. Ayrımın anlamı,
tüketicinin indirdiği ağacın küçük kalmasıdır.

**Eş bağımlılıklar.** Paketin çalışması için gereken, ancak paketin kendisinin kurmaması
gereken bağımlılıklar. Bir eklenti, genişlettiği kütüphanenin **aynı örneğini** kullanmak
zorundaysa bu listeye yazılır; böylece kütüphaneyi kuran taraf uygulamanın kendisi olur ve
ağaçta tek kopya bulunur. Yinelenen kopyanın neden sorun olduğu, bağımlılık ağacı dersinde
gösterilecektir.

**İsteğe bağlı bağımlılıklar.** Kurulumu başarısız olsa da işlemi durdurmayan bağımlılıklar.
Paket, eksikliğe karşı yedek bir yol taşımak zorundadır; taşımıyorsa bu liste yanlış
kullanılmıştır.

#### Paket Kaydı ve Kilit Dosyası

**Paket kaydı (package registry)**, ad ve sürüm çiftini bir arşive eşleyen bir dizindir.
Yayımlanmış bir ad–sürüm çiftinin içeriği değiştirilemez kabul edilir: aynı çift her zaman
aynı baytları vermelidir. Bu değişmezlik, sonraki tüm güvencelerin dayanağıdır.

Bildirimdeki `^1.4.0` gibi bir aralık, tek bir sürümü değil bir kümeyi gösterir. Kurulum
zamanı geldiğinde kümeden bir seçim yapılır. İki farklı zamanda yapılan iki kurulum, küme
büyüdüğü için farklı seçimler yapabilir; sonuç, aynı kaynaktan farklı çalışan iki ağaçtır.

**Kilit dosyası (lock file)** bu belirsizliği ortadan kaldırır. Kaydettiği üç bilgi
vardır:

1. **Seçilen kesin sürüm.** Aralık değil, tek bir sürüm numarası.
2. **Kaynak adresi.** Arşivin nereden alındığı.
3. **Bütünlük özeti (integrity digest).** Arşivin içeriğinin sağlaması.

Ayrıca ağacın biçimi — hangi paketin hangi düzeyde durduğu — kaydedilir. Kilit dosyası
bulunduğunda kurulum bir arama değil, bir yeniden üretimdir: yazılı ağaç aynen kurulur.

Bu ayrım, Linux'a Giriş kursundaki bağımlılık çözümlemesinin kısıt problemi olarak
tanımlanmasıyla aynı yerden gelir. Çözümleyicinin ürettiği çözüm, o çözümü yeniden
hesaplamadan saklanabilir olmalıdır; kilit dosyası bunun kaydıdır. Bu nedenle sürüm
denetimine eklenir: bir depoyu klonlayan ikinci geliştiricinin ve yapı sunucusunun aynı
ağacı kurması buna bağlıdır.

#### Özet

- Paket, bildirim dosyası taşıyan bir dizindir; bildirim kimliği, giriş noktalarını,
  yayımlanacak dosyaları ve bağımlılıkları tanımlar.
- Çıplak belirteç, içe aktaran dosyanın dizininden köke doğru her düzeydeki `node_modules`
  dizininde aranır; çözümleme yereldir, konuma bağlıdır ve belirlenimcidir.
- Dışa aktarım haritası paket düzeyinde kapsülleme sağlar; haritada olmayan iç dosyalar
  dışarıdan içe aktarılamaz.
- Bağımlılık listeleri farklı sorulara yanıt verir: çalışma, geliştirme, eş ve isteğe bağlı.
- Sürüm aralığı bir küme belirtir; kilit dosyası kümeden yapılan seçimi, kaynağı ve
  bütünlük özetini kaydederek kurulumu yeniden üretilebilir kılar.

#### Sonraki Adım

Aralıkların bir küme belirttiği söylendi, ama kümenin sınırlarını neyin çizdiği
söylenmedi. `^1.4.0` yazan bir bildirimin `1.9.0` sürümünü kabul edip `2.0.0` sürümünü
reddetmesinin arkasında, sürüm numarasının parçalarına anlam yükleyen bir sözleşme vardır.
Sonraki ders bu sözleşmeyi ve karşılaştırma kurallarını çalışan kodla kurar.

### Anlamlı Sürümleme

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/anlamli-surumleme

Sürüm numarasının üç parçasının taşıdığı söz, karşılaştırma ve öncelik kuralları, ön yayın etiketleri, sürüm aralıklarının sınırları ve kırıcı değişikliğin tanımı.

Önceki ders, bildirimdeki `^1.4.0` gibi bir aralığın tek bir sürüm değil bir küme
belirttiğini söyledi. Kümenin sınırlarını çizen şey, sürüm numarasının parçalarına anlam
yükleyen bir sözleşmedir.

Bu sözleşmeye **anlamlı sürümleme (semantic versioning)** denir. Teknik bir mekanizma
değil, bir söz verme biçimidir: numarayı yükselten taraf, tüketiciye ne yaptığını söyler.
Mekanizma olan kısım — karşılaştırma ve aralık hesabı — sözleşmenin üzerine kurulur.

#### Üç Parçanın Verdiği Söz

Sürüm numarası noktalarla ayrılmış üç sayıdan oluşur: **ana (major)**, **ikincil (minor)**
ve **yama (patch)**. Bir yayın hazırlanırken hangi parçanın artırılacağı, yapılan
değişikliğin türüne bağlıdır:

| Değişiklik | Artan parça | Tüketiciye verilen söz |
|---|---|---|
| Hata düzeltmesi, iç iyileştirme | Yama | Kodunuz değişmeden çalışır |
| Geriye dönük uyumlu yetenek eklemesi | İkincil | Kodunuz değişmeden çalışır, yeni yetenek de vardır |
| Var olan davranışı değiştiren düzenleme | Ana | Kodunuz değişebilir; geçiş notlarını okuyun |

Sözün konusu, paketin **açık arayüzüdür**: dışa aktarım haritasında listelenen yollar ve
o yollardan erişilen adlar. Haritada olmayan bir iç dosyanın yeniden adlandırılması ana
sürümü artırmaz — kapsüllemenin karşılığı budur. Bu, Programlama Temelleri kursunda
tanıtılan geriye dönük uyumluluk kavramının paket düzeyinde bir sözleşmeye
dönüştürülmesidir.

Sözleşmenin kendisi hiçbir şey zorlamaz. Numarayı doğru artırmak yazarın sorumluluğudur;
tüketici tarafında ise numaraya duyulan güvenin sınırı, bir sonraki bölümdeki aralıkların
neden savunmasız olduğunu açıklar.

#### Karşılaştırma ve Öncelik

Aralık hesabının önkoşulu, iki sürümün karşılaştırılabilmesidir. Karşılaştırma metin
sırasıyla yapılamaz: `1.10.0` sürümü `1.9.0` sürümünden büyüktür ama metin olarak küçük
görünür. Linux'a Giriş kursundaki paket yöneticisi dersinde aynı sorun sistem paketleri
için anlatılmıştı; burada kural daha dardır ve tümüyle yazılıdır.

Kural üç adımdır: üç sayı soldan sağa sayısal olarak karşılaştırılır; eşitlik durumunda
ön yayın etiketi olan sürüm olmayandan küçüktür; iki taraf da ön yayınsa etiket parçaları
tek tek karşılaştırılır.

```javascript
// dosya: surum.mjs
// Sürüm dizesini çekirdek üçlü ve ön yayın parçalarına ayırır; yapı üstverisi atılır.
export function ayristir(dizge) {
  const [surumsuz] = dizge.split('+');
  const [cekirdek, onYayin = ''] = surumsuz.split('-', 2);
  const [buyuk, kucuk, yama] = cekirdek.split('.').map(Number);
  return {
    buyuk,
    kucuk,
    yama,
    onYayin: onYayin === '' ? [] : onYayin.split('.'),
  };
}

function parcaKarsilastir(a, b) {
  const aSayi = /^\d+$/.test(a);
  const bSayi = /^\d+$/.test(b);
  if (aSayi && bSayi) return Number(a) - Number(b);
  if (aSayi) return -1;          // sayısal parça, harfli parçadan küçüktür
  if (bSayi) return 1;
  return a < b ? -1 : a > b ? 1 : 0;
}

export function karsilastir(x, y) {
  const a = ayristir(x);
  const b = ayristir(y);
  for (const alan of ['buyuk', 'kucuk', 'yama']) {
    if (a[alan] !== b[alan]) return a[alan] - b[alan];
  }
  if (a.onYayin.length === 0 && b.onYayin.length > 0) return 1;
  if (a.onYayin.length > 0 && b.onYayin.length === 0) return -1;
  const uzunluk = Math.min(a.onYayin.length, b.onYayin.length);
  for (let i = 0; i < uzunluk; i += 1) {
    const sonuc = parcaKarsilastir(a.onYayin[i], b.onYayin[i]);
    if (sonuc !== 0) return sonuc;
  }
  return a.onYayin.length - b.onYayin.length;
}
```

```javascript
// dosya: sirala.mjs — surum.mjs yukarıdaki dosyadır
import { karsilastir } from './surum.mjs';

const surumler = ['1.10.0', '1.9.0', '2.0.0-rc.2', '2.0.0-rc.10', '2.0.0', '2.0.0-alpha', '1.9.0+derleme.7'];
console.log(surumler.sort(karsilastir).join('  <  '));
```

```
$ node sirala.mjs
1.9.0  <  1.9.0+derleme.7  <  1.10.0  <  2.0.0-alpha  <  2.0.0-rc.2  <  2.0.0-rc.10  <  2.0.0
```

Çıktıdaki dört gözlem kuralın tamamını özetler:

`1.10.0` sürümü `1.9.0` sürümünden sonra geldi — parçalar sayı olarak karşılaştırıldı.

`2.0.0-alpha` sürümü `2.0.0` sürümünden **önce** geldi. Ön yayın etiketi taşıyan bir sürüm,
aynı çekirdek üçlüye sahip etiketsiz sürümden küçüktür. Etiket, "bu sürüm henüz o sürüm
değil" anlamına gelir.

`2.0.0-rc.2` sürümü `2.0.0-rc.10` sürümünden önce geldi. Etiketin sayısal parçaları sayı
olarak karşılaştırılır; metin sırası kullanılsaydı sıra ters çıkardı.

`1.9.0` ile `1.9.0+derleme.7` karşılaştırmada **eşit** çıktı: artı işaretinden sonrası
**yapı üstverisidir (build metadata)** ve önceliğe girmez. Sıralama kararlı olduğu için
ikisi, listedeki asıl sıralarını korudu. Eşitlik, iki farklı arşivin aynı sürüm sayılması
demektir; bu yüzden yapı üstverisi yayımlanan sürümleri ayırmakta kullanılamaz.

#### Sürüm Aralıkları

Bildirimdeki aralık, kabul edilen sürüm kümesini belirtir. En sık kullanılan iki işaret,
sözleşmenin iki farklı düzeyine güvenir: biri ana sürümün, diğeri ikincil sürümün sabit
kalmasını ister.

```javascript
// dosya: aralik.mjs — surum.mjs bir önceki bölümdeki dosyadır
import { ayristir, karsilastir } from './surum.mjs';

// '^' ve '~' aralıklarını [alt, ust) çiftine çevirir.
export function sinirlar(aralik) {
  const isaret = aralik[0] === '^' || aralik[0] === '~' ? aralik[0] : '=';
  const taban = isaret === '=' ? aralik : aralik.slice(1);
  const { buyuk, kucuk, yama } = ayristir(taban);
  if (isaret === '=') return [taban, `${buyuk}.${kucuk}.${yama + 1}`];
  if (isaret === '~') return [taban, `${buyuk}.${kucuk + 1}.0`];
  if (buyuk > 0) return [taban, `${buyuk + 1}.0.0`];
  if (kucuk > 0) return [taban, `0.${kucuk + 1}.0`];
  return [taban, `0.0.${yama + 1}`];
}

export function uyar(surum, aralik) {
  const [alt, ust] = sinirlar(aralik);
  const onYayinli = ayristir(surum).onYayin.length > 0;
  const tabanOnYayinli = ayristir(alt).onYayin.length > 0;
  if (onYayinli && !tabanOnYayinli) return false;
  return karsilastir(surum, alt) >= 0 && karsilastir(surum, ust) < 0;
}
```

```javascript
// dosya: deneme.mjs — aralik.mjs yukarıdaki dosyadır
import { sinirlar, uyar } from './aralik.mjs';

for (const aralik of ['^1.4.0', '~1.4.0', '^0.4.2', '^0.0.3', '1.4.0']) {
  const [alt, ust] = sinirlar(aralik);
  console.log(`${aralik.padEnd(8)} => ${alt} <= s < ${ust}`);
}

console.log('');
const denemeler = [
  ['1.4.9', '^1.4.0'], ['1.9.0', '^1.4.0'], ['2.0.0', '^1.4.0'],
  ['1.5.0', '~1.4.0'], ['0.4.9', '^0.4.2'], ['0.5.0', '^0.4.2'],
  ['2.0.0-rc.1', '^1.4.0'],
];
for (const [surum, aralik] of denemeler) {
  console.log(`${surum.padEnd(11)} ${aralik.padEnd(8)} ${uyar(surum, aralik) ? 'uyar' : 'uymaz'}`);
}
```

```
$ node deneme.mjs
^1.4.0   => 1.4.0 <= s < 2.0.0
~1.4.0   => 1.4.0 <= s < 1.5.0
^0.4.2   => 0.4.2 <= s < 0.5.0
^0.0.3   => 0.0.3 <= s < 0.0.4
1.4.0    => 1.4.0 <= s < 1.4.1

1.4.9       ^1.4.0   uyar
1.9.0       ^1.4.0   uyar
2.0.0       ^1.4.0   uymaz
1.5.0       ~1.4.0   uymaz
0.4.9       ^0.4.2   uyar
0.5.0       ^0.4.2   uymaz
2.0.0-rc.1  ^1.4.0   uymaz
```

Şapka işareti "ana sürüm değişmesin" der ve ikincil yükseltmeleri kabul eder. Tilde işareti
"ikincil sürüm de değişmesin" der ve yalnızca yama yükseltmelerini kabul eder. İşaretsiz
yazım tek bir sürümü sabitler.

Üçüncü ve dördüncü satırlar özel bir kuralı gösterir. Ana sürümü sıfır olan paketler
kararlı sayılmaz; sözleşme henüz yürürlükte değildir. Bu yüzden şapka işareti sıfırıncı ana
sürümde bir basamak sağa kayar ve ikincil sürümü sabit tutar. Sıfırıncı ikincil sürümde bir
basamak daha kayar. Bu kayma, kararsız paketlerin sessiz yükseltmelerle projeye
sızmamasını sağlar.

Son satır ön yayınlarla ilgili kuralı gösterir: `2.0.0-rc.1` sürümü sayısal olarak
`2.0.0` sürümünden küçük olsa da `^1.4.0` aralığına girmez. Ön yayınlar, aralığın tabanı
zaten bir ön yayın değilse hiçbir aralığa dahil edilmez. Aksi hâlde kararlı bir aralık,
yayımlanmamış sayılan sürümleri çekerdi.

#### Sözleşmenin Sınırları

Aralık işaretlerinin verdiği rahatlık — ikincil yükseltmeleri otomatik almak — bir varsayıma
dayanır: yayıncının ikincil sürüm sözünü tutması. Bu varsayım üç yerde zayıflar.

**Kırıcı değişikliğin tanımı belirsizdir.** Belgelenmemiş bir davranışa bağlanan tüketici,
o davranış değişince kırılır; yayıncı ise açık arayüzü değiştirmediğini düşünür. Belgelenmiş
davranışın sınırı yazılı değilse anlaşmazlık kaçınılmazdır.

**Hata düzeltmesi de kırabilir.** Yanlış bir sonucu düzelten yama sürümü, o yanlış sonuca
göre yazılmış kodu bozar. Sözleşme bunu yama sayar.

**İnsan hatası vardır.** Yanlış parça artırılabilir; kırıcı bir değişiklik ikincil sürüm
olarak yayımlanabilir.

Bu üç nedenle aralıklar bir güvence değil, bir kolaylıktır. Yeniden üretilebilirliği veren
şey aralık değil, önceki derste tanıtılan kilit dosyasıdır. Aralık "hangi yükseltmeleri
kabul etmeye hazırım" der; kilit dosyası kurulu ağacın tam hâlini kaydeder.
İkisi birlikte çalışır: yükseltme kararı bilinçli bir işlemle alınır, kilit dosyası
güncellenir, sınamalar çalıştırılır.

Yayıncı tarafında sözleşmeyi işler kılan araç, **kullanımdan kaldırma (deprecation)**
adımıdır: kaldırılacak bir yetenek önce ikincil bir sürümde uyarı verecek biçimde
işaretlenir, yerine geçecek yol belgelenir, ancak bir sonraki ana sürümde kaldırılır. Bu
adım atlanırsa ana sürüm numarası yükseltmeyi bildirir ama geçişi mümkün kılmaz.

#### Özet

- Sürüm numarasının üç parçası bir söz taşır: yama ve ikincil yükseltmeler kodun
  değişmeden çalışacağını, ana yükseltme değişebileceğini bildirir.
- Sözün konusu paketin açık arayüzüdür; kapsüllenmiş iç dosyalardaki değişiklik ana sürümü
  artırmaz.
- Karşılaştırma parçalar üzerinden sayısal yapılır; ön yayın etiketli sürüm etiketsizden
  küçüktür, yapı üstverisi önceliğe girmez.
- Şapka işareti ana sürümü, tilde işareti ikincil sürümü sabit tutar; ana sürümü sıfır olan
  paketlerde bu sınırlar bir basamak sağa kayar.
- Aralıklar kolaylıktır, güvence değil: kırıcı değişikliğin tanımı belirsizdir, yama
  düzeltmeleri de kırabilir. Yeniden üretilebilirliği kilit dosyası verir.

#### Sonraki Adım

Şimdiye kadar tek bir paketin tek bir aralığı incelendi. Gerçek bir projede onlarca paket,
aynı bağımlılığı farklı aralıklarla ister ve bu istekler her zaman kesişmez. Sonraki ders,
kesişmediğinde ne olduğunu gösterir: ağaçta yinelenen sürümler, aynı kütüphanenin iki ayrı
örneği ve bu ikiliğin ürettiği kimlik sorunları.

### Bağımlılık Ağacı ve Çakışmalar

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/bagimlilik-agaci-ve-cakismalar

Kesişmeyen sürüm isteklerinin iç içe kurulumla çözülmesi, yinelenen kopyaların ürettiği kimlik ve durum sorunları, ağacın düzleştirilmesi ve tekillik gerektiren bağımlılıklar.

Önceki ders tek bir aralığı inceledi. Bir projede onlarca paket bulunur ve bunların çoğu
kendi bağımlılıklarını taşır. Aynı pakete iki farklı yerden, iki farklı aralıkla istek
gelmesi olağandır.

İstekler kesişiyorsa sorun yoktur: kesişimden bir sürüm seçilir ve herkes onu kullanır.
Kesişmediğinde ne olur? Bu dersin konusu, o durumun çözümü ve çözümün bedelidir.

#### Kesişmeyen İstekler

Ölçüm projesi `ortak` adlı bir paketin ikinci ana sürümünü ister. Projenin kullandığı
`ceviri` paketi ise aynı paketin birinci ana sürümüne bağlıdır. `^2.0.0` ile `^1.0.0`
kümelerinin kesişimi boştur.

Sistem paket yöneticilerinde bu durum, Linux'a Giriş kursundaki paket yöneticisi
kavramı dersinde anlatıldığı gibi bir çakışma bildirimiyle sonuçlanır: bir kütüphanenin
sistemde tek bir sürümü kurulabilir. Modül çözümlemesi ise konuma bağlı olduğu için başka
bir çıkış yolu sunar — aynı paketin iki sürümü, ağacın iki ayrı düzeyinde durabilir.

İki bildirim, çakışan istekleri yazılı hâlde gösterir:

```json
{
  "name": "proje", "version": "1.0.0", "type": "module",
  "dependencies": { "ceviri": "^3.1.0", "ortak": "^2.0.0" }
}
```

```json
{
  "name": "ceviri", "version": "3.1.0", "type": "module", "main": "index.mjs",
  "dependencies": { "ortak": "^1.0.0" }
}
```

Kurulmuş ağacın biçimi şudur:

```
$ node agac.mjs
ceviri@3.1.0
  ortak@1.0.0
ortak@2.0.0
yinelenen: ortak -> 1.0.0, 2.0.0
```

Bu çıktıyı üreten betik, önceki derste anlatılan arama kuralını tersinden uygular: her
`node_modules` dizinini gezer ve bulduğu bildirimleri yazar.

```javascript
// dosya: agac.mjs
import { readdir, readFile } from 'node:fs/promises';
import { join } from 'node:path';

async function dolas(dizin, derinlik = 0, bulgular = new Map()) {
  const modulDizini = join(dizin, 'node_modules');
  let girdiler;
  try {
    girdiler = await readdir(modulDizini);
  } catch {
    return bulgular;
  }
  for (const ad of girdiler.sort()) {
    const paketDizini = join(modulDizini, ad);
    const bildirim = JSON.parse(await readFile(join(paketDizini, 'package.json'), 'utf8'));
    console.log(`${'  '.repeat(derinlik)}${bildirim.name}@${bildirim.version}`);
    bulgular.set(bildirim.name, (bulgular.get(bildirim.name) ?? new Set()).add(bildirim.version));
    await dolas(paketDizini, derinlik + 1, bulgular);
  }
  return bulgular;
}

const bulgular = await dolas('.');
for (const [ad, surumler] of bulgular) {
  if (surumler.size > 1) console.log(`yinelenen: ${ad} -> ${[...surumler].join(', ')}`);
}
```

Ağaçtaki yerleşim şudur: `ortak` paketinin ikinci sürümü kök dizinde, birinci sürümü
`ceviri` paketinin kendi `node_modules` dizinindedir. Arama kuralı gereği `ceviri` içinden
yapılan bir içe aktarma önce kendi dizinine bakar ve birinci sürümü bulur; kökten yapılan
bir içe aktarma ikinci sürümü bulur. İki istek de karşılanmıştır.

#### İki Kopyanın Bedeli

Çakışma çözüldü, ama sonuç iki ayrı paket örneğidir. Modül sistemleri konusunda kurulan
tek değerlendirme güvencesi **paket başına değil, çözülmüş dosya yolu başına** geçerlidir;
iki farklı dosya, iki farklı modül demektir.

```javascript
// dosya: node_modules/ortak/index.mjs — ikinci sürüm
export const surum = '2.0.0';
export class Kayit {
  constructor(ad) { this.ad = ad; }
}
const kayitlar = [];
export function ekle(kayit) { kayitlar.push(kayit); return kayitlar.length; }
```

```javascript
// dosya: node_modules/ceviri/index.mjs — kendi dizinindeki birinci sürümü kullanır
import { ekle, Kayit, surum } from 'ortak';

export function kullan(ad) {
  const kayit = new Kayit(ad);
  return { gorulenSurum: surum, kayitSayisi: ekle(kayit), kayit };
}
```

```javascript
// dosya: ana.mjs — yukarıdaki iki paketi kullanır
import { ekle, Kayit, surum } from 'ortak';
import { kullan } from 'ceviri';

const bizim = new Kayit('kok');
console.log('kokun gordugu surum:', surum, '| kayit sayisi:', ekle(bizim));

const onunki = kullan('ceviri-ici');
console.log('cevirinin gordugu surum:', onunki.gorulenSurum, '| kayit sayisi:', onunki.kayitSayisi);
console.log('cevirinin urettigi kayit bizim sinifimizdan mi:', onunki.kayit instanceof Kayit);
```

```
$ node ana.mjs
kokun gordugu surum: 2.0.0 | kayit sayisi: 1
cevirinin gordugu surum: 1.0.0 | kayit sayisi: 1
cevirinin urettigi kayit bizim sinifimizdan mi: false
```

Üç satır, üç ayrı sorunu gösterir.

**Sürüm görünürlüğü kullanıcıya bağlıdır.** Aynı programda iki farklı `ortak` sürümü
çalışıyor. Hangisinin çalıştığı, sorunun nereden sorulduğuna göre değişir.

**Durum paylaşılmaz.** İki kayıt listesi ayrıdır; her ikisi de bir eleman içerir. Kayıt,
önbellek veya sayaç türünden paketin tuttuğu her şey bölünmüştür. Bir kütüphane "tüm
kayıtları listele" gibi bir işlev sunuyorsa, yalnızca kendi kopyasının kayıtlarını görür.

**Kimlik denetimi başarısızdır.** `ceviri` paketinin ürettiği nesne, kökteki sınıfın örneği
değildir. Sınıflar aynı kaynaktan gelse bile iki ayrı değerlendirmenin ürünüdür; nesne
kimliği bunu bilmez. Aynı sorun sembollerde, hata sınıflarında ve marka denetimlerinde
ortaya çıkar.

Bu üç sorun, "programım çalışıyor ama bir yerde tuhaf davranıyor" biçimindeki tanısı zor
arızaların sık nedenidir. Bağımlılık ağacında yinelenen sürüm aramak, bu tür bir arızada
ilk adımdır.

#### Düzleştirme

İç içe kurulum her zaman gerekli değildir. İstekler kesişiyorsa tek bir kopya hepsini
karşılar ve bu kopya ağacın köküne konur. Bu yerleştirmeye **düzleştirme (flattening)**
denir.

Düzleştirmenin iki gerekçesi vardır. Birincisi yer ve süre: aynı paketin onlarca kopyası
yerine tek kopya kurulur. İkincisi az önce sayılan üç sorunun önlenmesidir.

Düzleştirmenin bir yan etkisi, önceki derste değinilen sinsi duruma zemin hazırlamasıdır.
Kök dizine çıkarılan bir paket, onu bildiriminde yazmayan modüllerden de görünür hâle
gelir. Kod yerelde çalışır; ağaç başka bir biçimde kurulduğunda — örneğin farklı bir
kurulum aracı iç içe yerleşimi yeğlediğinde — paket kaybolur. Bu yüzden kural açıktır:
kullanılan her paket, kullanan paketin bildiriminde yazılı olmalıdır.

#### Tekillik Gerektiren Bağımlılıklar

Bazı paketler için iki kopya yalnızca israf değil, doğrudan hatadır. Kayıt tutan, genel
bir durum yöneten ya da nesne kimliğine dayanan kütüphaneler tek örnek olmak zorundadır.

Bunu sağlayan mekanizma, ilk derste tanıtılan **eş bağımlılık** listesidir. Bir eklenti,
genişlettiği kütüphaneyi çalışma bağımlılığı olarak yazarsa kendi dizinine kendi kopyasını
kurar ve ikilik oluşur. Eş bağımlılık olarak yazdığında ise kütüphaneyi kurmaz; yalnızca
"şu aralıktaki bir sürüm ortamda bulunmalı" der. Kurma sorumluluğu uygulamaya geçer ve
ağaçta tek kopya kalır.

Eş bağımlılık aralığı bilinçli seçilmelidir. Fazla dar bir aralık, eklentinin kullanılabildiği
sürüm kümesini gereksiz daraltır ve kesişmeyen istekler üretir; fazla geniş bir aralık,
eklentinin çalışmadığı sürümlerde kurulumun sorunsuz görünmesine yol açar.

#### Çakışma Ne Zaman Çözülmez

İç içe kurulum her çakışmayı çözmez. Üç durumda tek kopya zorunludur ve karar verilmesi
gerekir:

**Eş bağımlılık kesişmiyorsa.** İki eklenti aynı kütüphanenin uyumsuz ana sürümlerini
istiyorsa ikisi bir arada kullanılamaz. Çözüm, eklentilerden birini yükseltmek veya
değiştirmektir.

**Değerler paketler arasında geçiyorsa.** Bir paketin ürettiği nesne başka bir pakete
argüman olarak veriliyorsa ve alan taraf kimlik denetimi yapıyorsa, iki kopya çalışmaz.

**Çıktı boyutu belirleyiciyse.** Tarayıcıya gönderilen bir çıktıda aynı kütüphanenin iki
sürümünü taşımak, indirilen baytı iki katına çıkarır.

Bu durumlarda kurulum araçları, geçişli bir bağımlılığın sürümünü dışarıdan sabitlemeye
izin veren bir bildirim alanı sunar. Alan işe yarar ama tehlikelidir: bir paketin
beklediğinden farklı bir sürümle çalışmaya zorlanması, sürüm sözleşmesinin kasıtlı olarak
çiğnenmesidir. Kullanıldığında bir yorumla gerekçelendirilmeli ve geçici sayılmalıdır;
kalıcı çözüm, uyumsuz paketin yükseltilmesidir.

#### Özet

- Kesişmeyen sürüm istekleri, aynı paketin iki kopyasının ağacın farklı düzeylerine
  kurulmasıyla karşılanır; arama kuralı her tüketiciye kendi kopyasını bulduran şeydir.
- Tek değerlendirme güvencesi paket başına değil, çözülmüş dosya yolu başınadır; iki kopya
  ayrı durum tutar ve nesne kimliği denetimlerini başarısız kılar.
- Kesişen istekler tek kopyayla karşılanır ve kopya köke çıkarılır; bu düzleştirme,
  bildirimde yazılmayan paketlerin görünür olmasına da yol açar.
- Tekillik gerektiren kütüphaneler eş bağımlılık olarak bildirilir; kurma sorumluluğu
  uygulamaya geçer ve ağaçta tek kopya kalır.
- Geçişli bir bağımlılığın sürümünü dışarıdan sabitlemek sözleşmeyi çiğner; geçici çözüm
  sayılmalıdır.

#### Sonraki Adım

Buraya kadar tüm paketler dışarıdan geldi. Bir ekibin kendi yazdığı paketler de aynı
ağaçta yer alır ve aralarındaki bağımlılıklar her değişiklikte yayımlanmayı bekleyemez.
Sonraki ders, birden çok paketin tek bir depoda tutulduğu düzeni, bu düzenin çözümlemeyi
nasıl değiştirdiğini ve paketler arası derleme sırasının nasıl belirlendiğini ele alır.

### Çalışma Alanları

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/calisma-alanlari

Birden çok paketin tek depoda tutulması, çalışma alanı bildirimi, yerel paketlerin bağla çözümlenmesi, topolojik derleme sırası ve sürümleme stratejileri.

Önceki derslerde tüm paketler dışarıdan geldi: bildirimde bir aralıkla istendiler, bir
dizine kuruldular, oradan çözümlendiler. Bir ekibin kendi yazdığı paketler için bu döngü
ağırdır — kütüphanede yapılan her düzeltmenin, onu kullanan uygulamada denenebilmesi için
yayımlanması gerekir.

Ölçüm kütüphanesi bu noktaya geldi. Biçimlendirme yardımcısı ayrı bir pakete çıkarıldı,
ölçüm sonucunu sunan bir rapor aracı eklendi. Üçü de aynı ekibin ürünü ve birlikte
değişiyorlar. Bu ders, üçünün tek bir depoda nasıl tutulacağını ele alır.

#### Çok Paketli Depo

Birden çok paketi tek bir sürüm denetimi deposunda tutan düzene **çok paketli depo
(monorepo)** denir. Çözdüğü sorunlar somuttur:

**Bölünmüş değişiklik.** Kütüphaneye bir parametre eklemek ve onu kullanan araçları
güncellemek, ayrı depolarda birkaç adımlı bir işlemdir: yayımla, bekle, aralığı güncelle,
kur. Tek depoda bu, tek bir işlemedir (commit).

**Sürüm gecikmesi.** Ayrı depolarda kütüphanenin son hâli ile uygulamanın kullandığı hâl
arasında her zaman bir fark bulunur. Tek depoda bu fark sıfırdır.

**Yinelenen yapılandırma.** Çözümleyici kuralları, biçimlendirme ayarları ve sınama düzeni
tek yerde tutulur.

Bedeli de vardır. Depo büyür; her değişiklikte hangi paketlerin etkilendiğini hesaplamak
gerekir; sınama süresi tüm paketlerin toplamı olmaya eğilimlidir; sürüm ve yayımlama
kararları karmaşıklaşır. Bu maliyetler, paketler birlikte değişmiyorsa ödenmeye değmez.
Ölçüt basittir: paketler aynı değişiklikte birlikte güncelleniyorsa tek depo, bağımsız
gelişiyorsa ayrı depolar uygundur.

#### Çalışma Alanı Bildirimi

Kök dizindeki bildirim, hangi alt dizinlerin paket olduğunu söyler ve kendisinin
yayımlanmayacağını işaretler:

```json
{
  "name": "olcum-deposu",
  "private": true,
  "workspaces": ["paketler/*"]
}
```

`private` alanı bir güvenliktir: kök dizin bir kütüphane değildir ve kazayla yayımlanması
istenmez. `workspaces` alanı ise kurulum aracına, listelenen kalıplara uyan her dizini bir
**çalışma alanı (workspace)** olarak ele almasını söyler.

Depodaki üç paket şunlardır:

```json
{ "name": "bicim-yardimcisi", "version": "1.4.0", "type": "module", "main": "index.mjs" }
```

```json
{
  "name": "metin-olcer",
  "version": "0.1.0",
  "type": "module",
  "exports": { ".": "./src/index.mjs" },
  "dependencies": { "bicim-yardimcisi": "^1.4.0" }
}
```

```json
{
  "name": "rapor-araci",
  "version": "0.2.0",
  "type": "module",
  "main": "index.mjs",
  "dependencies": { "metin-olcer": "^0.1.0" }
}
```

Dikkat edilecek nokta, bağımlılıkların yerel olduklarını belirten özel bir yazım
kullanmamasıdır. `metin-olcer` paketi `bicim-yardimcisi` paketini, dışarıdan gelen herhangi
bir paket gibi çıplak adıyla ve bir aralıkla ister. Yerellik, bildirimin değil kurulumun
kararıdır: kurulum aracı, istenen adın depoda bir çalışma alanı olduğunu ve sürümünün
aralığa uyduğunu görürse kayda gitmez.

#### Bağların Kurulması

Yerel paket, ağaca kopyalanarak değil, sembolik bağ ile yerleştirilir:

```
$ for b in node_modules/*; do printf '%s -> %s\n' "$b" "$(readlink "$b")"; done
node_modules/bicim-yardimcisi -> ../paketler/bicim
node_modules/metin-olcer -> ../paketler/olcer
node_modules/rapor-araci -> ../paketler/rapor
```

Sembolik bağ kavramı Linux'a Giriş kursundaki bağlantılar dersinde tanımlanmıştı: hedefe
işaret eden, hedefin kendisi olmayan bir girdi. Buradaki sonucu belirleyicidir. Çözümleme
kuralı `node_modules` dizininde adı arar, bulduğu girdinin bağ olup olmadığına bakmaz;
bulunan dizin kaynak ağacın kendisidir. Kaynakta yapılan bir değişiklik, yeniden kurulum
gerektirmeden tüketiciye görünür.

```javascript
// dosya: paketler/bicim/index.mjs
export function ondalik(sayi, basamak = 2) {
  return sayi.toFixed(basamak);
}
```

```javascript
// dosya: paketler/olcer/src/index.mjs — bolumleme ve istatistik modülleri aynı dizindedir
import { ondalik } from 'bicim-yardimcisi';
import { cumlelereBol, sozcuklereBol } from './bolumleme.mjs';
import { enUzunSozcuk, ortalamaUzunluk } from './istatistik.mjs';

export function olc(metin) {
  const sozcukler = sozcuklereBol(metin);
  return {
    sozcukSayisi: sozcukler.length,
    cumleSayisi: cumlelereBol(metin).length,
    ortalamaUzunluk: ondalik(ortalamaUzunluk(sozcukler)),
    enUzun: enUzunSozcuk(sozcukler),
  };
}
```

```javascript
// dosya: paketler/rapor/index.mjs
import { olc } from 'metin-olcer';

export function rapor(metin) {
  const o = olc(metin);
  return `${o.sozcukSayisi} sozcuk / ${o.cumleSayisi} cumle / ort. ${o.ortalamaUzunluk}`;
}
```

```javascript
// dosya: dene.mjs — depo kökünde
import { rapor } from 'rapor-araci';

console.log(rapor('Modul kendi kapsamini tasir. Betik genel kapsamda calisir.'));
```

```
$ node dene.mjs
8 sozcuk / 2 cumle / ort. 6.13
```

Üç paket, hiçbiri yayımlanmadan birbirini çıplak adıyla buldu. Bu düzenin sessiz bir
tuzağı vardır: yerelde çalışan içe aktarma, yayımlandıktan sonra çalışmayabilir. Bağ
üzerinden erişilen dizin kaynak ağacın tamamıdır; yayımlanan arşiv ise yalnızca bildirimde
listelenen dosyaları taşır. Dışa aktarım haritası veya dosya listesi eksikse, hata yalnızca
yayımdan sonra görünür. Bu yüzden çok paketli depolarda arşivin içeriğini yayımlamadan
önce denetlemek bir alışkanlık olmalıdır.

#### Derleme Sırası

Paketler birbirine bağlı olduğuna göre, üzerlerinde yapılan toplu işlemler — derleme,
sınama, yayımlama — rastgele sırada yürütülemez. `rapor-araci` paketini sınamak,
`metin-olcer` paketinin hazır olmasını gerektirir.

Doğru sıra, bağımlılık çizgesinin topolojik sıralamasıdır. Veri Yapıları kursunda
tanıtılan bu sıralama, burada doğrudan uygulanır:

```javascript
// dosya: sira.mjs — depo kökünde, paketler/ dizinini okur
import { readdir, readFile } from 'node:fs/promises';
import { join } from 'node:path';

const kok = 'paketler';
const paketler = new Map();
for (const dizin of (await readdir(kok)).sort()) {
  const bildirim = JSON.parse(await readFile(join(kok, dizin, 'package.json'), 'utf8'));
  paketler.set(bildirim.name, Object.keys(bildirim.dependencies ?? {}));
}

// Derine arama ile topolojik sıralama: bağımlılıklar önce yayımlanır.
const sonuc = [];
const durum = new Map();
function ziyaret(ad) {
  if (durum.get(ad) === 'bitti') return;
  if (durum.get(ad) === 'sürüyor') throw new Error(`döngü: ${ad}`);
  durum.set(ad, 'sürüyor');
  for (const bagimlilik of paketler.get(ad) ?? []) {
    if (paketler.has(bagimlilik)) ziyaret(bagimlilik);
  }
  durum.set(ad, 'bitti');
  sonuc.push(ad);
}
for (const ad of paketler.keys()) ziyaret(ad);

console.log(sonuc.join(' -> '));
```

```
$ node sira.mjs
bicim-yardimcisi -> metin-olcer -> rapor-araci
```

Betiğin iki ayrıntısı önemlidir. Depoda bulunmayan bağımlılıklar atlanır: dış paketlerin
sırası bizi ilgilendirmez; onlar zaten kuruludur. İkincisi, ziyaret durumu üç değerlidir;
"sürüyor" durumundaki bir düğüme yeniden gelinmesi döngü demektir ve hata verilir. Paketler
arası dairesel bağımlılık, modüller arasındakinin aksine hiçbir biçimde çözülemez.

Aynı çizge ikinci bir soruya da yanıt verir: bir pakette yapılan değişiklikten hangi
paketler etkilenir? Yanıt, çizgenin ters yönde dolaşılmasıyla bulunur ve yalnızca etkilenen
paketlerin sınanmasını sağlar. Büyük depolarda sınama süresini yönetilebilir tutan yöntem
budur.

#### Sürümleme Stratejisi

Depodaki paketlerin sürümleri iki biçimde yönetilir.

**Ortak sürüm.** Tüm paketler aynı numarayı taşır ve birlikte yayımlanır. Yayımlama kararı
tektir, izlenmesi kolaydır; karşılığında hiç değişmemiş bir paket de yeni sürüm alır ve
tüketicileri gereksiz yükseltmeye zorlanır.

**Bağımsız sürüm.** Her paket kendi numarasını taşır ve yalnızca değiştiğinde yayımlanır.
Tüketici açısından dürüst olan yaklaşımdır; karşılığında hangi paketin hangi sürümde
olduğunun izlenmesi ve depo içi aralıkların güncel tutulması gerekir.

Seçim, tüketicinin kim olduğuna bağlıdır. Paketler yalnızca depo içinde kullanılıyorsa
ortak sürüm işi kolaylaştırır. Dışarıya yayımlanıyorsa bağımsız sürüm, önceki derste
kurulan sözleşmeye sadık kalan tek seçenektir: sürüm numarası ancak gerçek bir değişikliği
bildirdiğinde anlam taşır.

#### Özet

- Çok paketli depo, birlikte değişen paketleri tek işlemede güncellemeyi ve tek
  yapılandırmayı paylaşmayı sağlar; paketler bağımsız gelişiyorsa maliyeti karşılıksız kalır.
- Kök bildirimindeki çalışma alanı listesi hangi dizinlerin paket olduğunu belirtir;
  bağımlılıklar yerel olduklarını belirten özel bir yazım kullanmaz.
- Yerel paketler ağaca sembolik bağla yerleştirilir; kaynakta yapılan değişiklik yeniden
  kurulum gerektirmeden görünür.
- Bağ üzerinden tüm kaynak ağacı erişilebilir olduğu için, yayımlanan arşivde eksik kalan
  dosyalar yalnızca yayımdan sonra fark edilir.
- Paketler üzerindeki toplu işlemler bağımlılık çizgesinin topolojik sırasına göre yürütülür;
  ters yönde dolaşma, bir değişiklikten etkilenen paketleri verir.

#### Sonraki Adım

Ağaçtaki paketlerin çoğu doğrudan seçilmedi; başka paketlerin bağımlılığı oldukları için
oradalar. Bu paketler de kod çalıştırır ve kurulum sırasında betik işletebilirler. Sonraki
ders, bu geçişli kütlenin büyüklüğünü ölçer ve bağımlılık seçiminin, kilitlemenin ve
doğrulamanın savunma değeri olan yanlarını ele alır.

### Bağımlılık Riski

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/bagimlilik-riski

Geçişli bağımlılık kütlesinin ölçülmesi, kurulum anında çalışan kodun anlamı, bütünlük doğrulaması, kilitleme disiplini, paket seçim ölçütleri ve terk edilmiş paketlerin yönetimi.

Önceki dersler bağımlılıkları teknik bir çözümleme sorunu olarak ele aldı: hangi sürüm
seçilir, nereye kurulur, kim görür. Bu ders farklı bir soruyu sorar — kurulan kodun
tamamına ne kadar güvenilmektedir ve bu güven nasıl daraltılır?

Soru yönetimseldir ama yanıtı tümüyle tekniktir. Ölçülebilir bir büyüklükle başlamak
gerekir: bir projenin gerçekte kaç paket çalıştırdığı.

#### Geçişli Kütle

Bildirimde yazılı paketler, kurulan paketlerin küçük bir bölümüdür. Her bağımlılık kendi
bağımlılıklarını getirir; bu ilişki kapanana kadar sürer. Kümenin tamamına **bağımlılık
kapanışı (dependency closure)** denir.

Kapanış, bildirimler üzerinde bir enine aramayla hesaplanır:

```javascript
// dosya: kapanis.mjs — kayit/<ad>/package.json dosyalarını okur
import { readFile } from 'node:fs/promises';

async function bildirim(ad) {
  return JSON.parse(await readFile(`kayit/${ad}/package.json`, 'utf8'));
}

const kok = await bildirim('metin-olcer');
const dogrudan = Object.keys(kok.dependencies);
const kapanis = new Map();
const kuyruk = [...dogrudan];

while (kuyruk.length > 0) {
  const ad = kuyruk.shift();
  if (kapanis.has(ad)) continue;
  const paket = await bildirim(ad);
  kapanis.set(ad, paket.version);
  kuyruk.push(...Object.keys(paket.dependencies ?? {}));
}

console.log('doğrudan bağımlılık:', dogrudan.length, '->', dogrudan.join(', '));
console.log('geçişli kapanış:', kapanis.size);
for (const [ad, surum] of kapanis) console.log(`  ${ad}@${surum}`);
```

Küçük bir örnek kayıtta — altı paketlik bir dizinde — sonuç şudur:

```
$ node kapanis.mjs
doğrudan bağımlılık: 2 -> bicim-yardimcisi, dizgi-araci
geçişli kapanış: 5
  bicim-yardimcisi@1.4.0
  dizgi-araci@2.1.3
  sayi-cekirdek@1.0.7
  unicode-tablosu@3.2.0
  veri-paketi@1.2.9
```

İki bilinçli seçim beş paket getirdi. Oran örneğin küçüklüğünden gelmiyor: her düzeyde
çarpan etkisi vardır ve gerçek projelerde iki basamaklı doğrudan bağımlılık dört basamaklı
kapanışlar üretebilir.

Bu sayının anlamı şudur: kapanıştaki her paketin kodu programınızın içinde, programınızın
yetkileriyle çalışır. Doğrudan seçtiğiniz paketlere karar verdiniz; geri kalanına, onları
seçenlerin kararı üzerinden dolaylı olarak güvendiniz. Kapanışın büyüklüğü, bu dolaylı
güvenin ölçüsüdür.

#### Kurulum Anında Çalışan Kod

Güven sınırı, kütüphaneyi çağırdığınız anda başlamaz. Bildirim dosyası, kurulum sırasında
çalıştırılacak betikler tanımlayabilir:

```json
{
  "name": "ornek-paket",
  "version": "1.0.0",
  "scripts": {
    "postinstall": "node kurulum/hazirla.mjs"
  }
}
```

Bu alan meşru işler için vardır: yerel derleme, önbellek hazırlama, uyumluluk denetimi.
Sonucu ise şudur — bir paketi kurmak, o paketin kodunu **çalıştırmakla** eşdeğerdir. Paketi
hiç içe aktarmadan, yalnızca kurulum komutunu çalıştırarak kapanıştaki her paketin betiğine
yürütme hakkı verilmiş olur. Betikler kullanıcının yetkileriyle çalışır; dosya sistemine ve
ağa erişebilirler.

Savunma üç adımlıdır:

**Kurulum betiklerini varsayılan olarak kapatmak.** Kurulum araçları bunu bir seçenekle
sunar. Kapalıyken kurulum başarısız olan paketler tek tek incelenir ve gerçekten gerekliyse
istisna tanınır.

**Yalıtılmış ortamda kurmak.** Kurulumun geliştirici makinesinde değil, sınırlı yetkili ve
sonrasında atılan bir ortamda yapılması, betiğin erişebileceği alanı daraltır. Linux'a
Giriş kursundaki en az yetki ilkesi burada doğrudan uygulanır.

**Kilit dosyasını güncelleyen değişiklikleri incelemek.** Kilit dosyasındaki bir satırın
değişmesi, kurulacak arşivin değişmesi demektir. Bu değişiklikler, kaynak kodundaki
değişiklikler kadar dikkatle gözden geçirilmelidir.

#### Bütünlük Doğrulaması

Kilit dosyasının kaydettiği üçüncü bilgi — bütünlük özeti — belirli bir soruya yanıt verir:
indirilen arşiv, kilitlendiği andaki arşivin aynısı mı?

Özet, arşivin baytları üzerinden hesaplanan bir kriptografik sağlamadır. Tek bir baytın
değişmesi özeti tümüyle değiştirir:

```javascript
// dosya: ozet.mjs
import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';

const icerik = await readFile(process.argv[2]);
const ozet = createHash('sha512').update(icerik).digest('base64');
console.log(`bayt: ${icerik.length}`);
console.log(`sha512-${ozet.slice(0, 44)}...`);
```

```javascript
// dosya: paket.mjs
export function ondalik(sayi, basamak = 2) {
  return sayi.toFixed(basamak);
}
```

```
$ node ozet.mjs paket.mjs
bayt: 99
sha512-evHnpqY+DudhSUf7VDcRTYA5YiDix19+bqCy3i1OZFYh...
```

Dosyadaki tek bir karakter — varsayılan basamak sayısı — `3` yapılıp aynı komut yeniden
çalıştırıldığında:

```
$ node ozet.mjs paket.mjs
bayt: 99
sha512-J1VCPrKRAjSDZKJKnyUTtADWFgGDuC9Wqyj1yLZfZ6ty...
```

Boyut aynı kaldı, özet tümüyle değişti. Betik özeti kısaltarak yazar; gerçek özet daha
uzundur ve kilit dosyasında tam hâliyle saklanır.

Doğrulamanın kapattığı yüzey açıktır: aynı ad ve sürümle farklı içerik sunulması. Kapatmadığı
yüzey de açıktır: paketin **yeni bir sürümü** yayımlandığında özet zaten değişecektir.
Bütünlük özeti, içeriğin değişmediğini kanıtlar; içeriğin güvenli olduğunu değil.

#### Yükseltme Disiplini

Kilit dosyası bulunan bir projede kilide birebir uyan kurulum komutu, kilitte yazılı
ağacı aynen kurar; sıradan kurulum kilidi bildirime göre güncelleyebilir. Bu, yükseltmeyi
bir kaza olmaktan çıkarıp bir işleme dönüştürür. İşleyen bir düzen şu adımları içerir:

1. Yükseltme ayrı bir değişiklik olarak yapılır; başka bir düzenlemeyle aynı işlemede
   birleştirilmez.
2. Değişen kilit satırları incelenir: hangi paketler, hangi sürümden hangi sürüme.
3. Sınamalar çalıştırılır.
4. Yükseltmenin gerekçesi kaydedilir.

Yükseltmeyi geciktirmek de bir risktir: eski sürümde kalmak, düzeltilmiş kusurları taşımaya
devam etmek demektir. Denge, düzenli ve küçük yükseltmelerdedir; birikmiş yükseltmeler tek
seferde yapıldığında hem inceleme olanaksızlaşır hem de bir arıza durumunda hangi paketin
sorumlu olduğu ayırt edilemez.

#### Paket Seçim Ölçütleri

Bir bağımlılık eklemek bir karardır ve zamana bağlı olmayan ölçütlerle değerlendirilebilir:

**Kapanış büyüklüğü.** Paketin kendi bağımlılık kapanışı kaç pakettir? Küçük bir işlev için
büyük bir kapanış getiren paket pahalıdır.

**Arayüz yüzeyi.** Paketin kaç işlevini kullanacaksınız? Tek bir işlev için alınan geniş bir
kütüphane, hem ağaç sarsmanın hem de incelemenin işini zorlaştırır.

**Değiştirilebilirlik.** Paket bırakıldığında yerine ne konur? Arayüzü dar ve standart bir
paket değiştirilebilir; projeye kendi kavramlarını dayatan bir paket değiştirilemez.

**Bakım işaretleri.** Bildirilen kusurlara yanıt veriliyor mu, sınamaları var mı, kaç kişi
katkı veriyor? Tek kişilik bakım, o kişinin ilgisini kaybetmesiyle biten bir bağımlılıktır.

**Lisans.** Kullanım ve dağıtım koşulları projeye uyuyor mu?

**Kendin yazma maliyeti.** İşlev birkaç satırla yazılabiliyorsa, bağımlılığın taşıdığı
kurulum, güncelleme ve güven maliyeti buna değmeyebilir. Karşı yönde de aynı ölçü geçerlidir:
kriptografi, tarih hesabı veya metin kodlaması gibi ayrıntısı yüksek alanlarda kendi
gerçekleştirimini yazmak, olgunlaşmış bir paketi kullanmaktan daha risklidir.

#### Ad Doğrulama ve Terk Edilmiş Paketler

Paket adları kayıt içinde benzersizdir ama benzerlik serbesttir. Yaygın bir paketin adına
bir harf eklenmiş ya da tire konumu değiştirilmiş bir ad, elle yazıldığında fark
edilmeden seçilebilir. Savunma yazım disiplinindedir: paket adı belgeden veya bildirimden kopyalanır,
ezberden yazılmaz; ekleme sonrası bildirimdeki satır ve paketin kaynak deposu bağlantısı
denetlenir.

Terk edilmiş paket ayrı bir sorundur ve tanısı gözlemle konur: kusurlar birikmiş, yayın
yapılmıyor, geçişli bağımlılıkları da bakımsız. Seçenekler şunlardır:

**Değiştirmek.** Aynı işi yapan bakımlı bir paketle değiştirmek. Arayüz yüzeyi darsa
uygulanabilir.

**Depoya almak.** Paketin kaynağını projenin içine kopyalayıp bakımını üstlenmek. Kod
küçükse ve dondurulmuş sayılabiliyorsa uygundur; kapanışı da birlikte taşımak gerekir.

**Bakımını üstlenmek.** Paketi çatallayıp kendi adınızla yayımlamak. Sorumluluk artar,
karşılığında denetim tümüyle sizde olur.

Üçünde de ilk adım aynıdır: paketin gerçekten gerekli olup olmadığını yeniden sormak. En
güvenli bağımlılık, kaldırılmış olandır.

#### Özet

- Bağımlılık kapanışı, doğrudan seçilen paketlerden çok daha büyüktür; kapanıştaki her
  paket programın yetkileriyle çalışır.
- Kurulum betikleri, paketi hiç içe aktarmadan kod çalıştırır; savunma betikleri kapatmak,
  yalıtılmış ortamda kurmak ve kilit değişikliklerini incelemektir.
- Bütünlük özeti, aynı ad ve sürümle farklı içerik sunulmasını yakalar; içeriğin güvenli
  olduğunu kanıtlamaz.
- Yükseltme ayrı ve düzenli bir işlem olmalıdır; geciktirmek de bir risktir.
- Paket seçimi kapanış büyüklüğü, arayüz yüzeyi, değiştirilebilirlik, bakım işaretleri,
  lisans ve kendin yazma maliyeti ölçütleriyle değerlendirilir.
- Terk edilmiş paket için üç yol vardır — değiştirmek, depoya almak, bakımını üstlenmek —
  ve üçünden önce paketin gerekliliği sorgulanır.

#### Sonraki Adım

Bağımlılıklar çözümlendi, kilitlendi ve denetlendi. Geriye, bu paketlerin ve kendi
modüllerinizin hedef ortama nasıl ulaştırılacağı kalıyor. Sonraki konu, modül çizgesini
girdi alıp çalıştırılabilir çıktı üreten araç sınıflarını ele alır; ilk ders, bu üretimin
neden gerekli olduğunu ve nasıl yürüdüğünü çalışan bir örnekle gösterir.

## Derleme Araçları

Modül çizgesinden çıktı üretimi, kaynağın hedef ortama uyarlanması, üretim çıktısından kaynağa geri izleme, kural tabanlı çözümleme ve biçimlendirme ile görev otomasyonu.

### Paketleyici Kavramı

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/paketleyici-kavrami

Modül çizgesinden tek çıktı üretme gerekçesi, çalışan bir paketleyicinin üç adımı, çıktının yapısı ve ağaç sarsmanın yan etkilerle sınırlanması.

Modül sistemleri ve paket yönetimi konuları, kodun kaynak dizininde nasıl düzenlendiğini
ve bağımlılıkların nasıl bulunduğunu kurdu. Kaynak düzeni, hedef ortamın istediği düzenle
aynı olmak zorunda değildir.

**Paketleyici (bundler)**, modül çizgesini girdi alıp hedef ortamda çalışan bir veya
birkaç dosya üreten araç sınıfıdır. Bu ders, aracın ne yaptığını anlatmak yerine küçük bir
gerçekleştirimini kurar; ondan sonra gerçek araçların bu iskelete eklediği şeyler
anlaşılabilir hâle gelir.

#### Neden Tek Çıktı

Kaynak düzeni yüzlerce küçük dosyadan oluşur. Bu düzenin hedef ortamda doğrudan
kullanılması üç sorun çıkarır.

**Dosya başına maliyet.** Ağ üzerinden yüklenen her dosya ayrı bir istektir. İsteklerin
gecikmesi, taşınan baytların süresini aşabilir. İnternet Nasıl Çalışır kursunda tanıtılan
istek başına gecikme, çok sayıda küçük dosyada baskın maliyet hâline gelir.

**Çözümleme farkı.** Çıplak belirteçler dosya sistemindeki bir arama kuralıyla çözülür.
Bu kural her ortamda bulunmaz; tarayıcı, `bicim-yardimcisi` adını kendiliğinden bir dosyaya
çeviremez. Paketleyici bu adları çözülmüş hâlleriyle değiştirir.

**Dil ve biçim farkı.** Kaynak, hedef ortamın anlamadığı bir sözdizim veya iki farklı modül
sistemi karışımı içerebilir. Çıktı tek bir biçimde üretilir.

Bunların hiçbiri her projede geçerli değildir. Yalnızca çalışma zamanında çalışan, ağ
üzerinden dağıtılmayan bir kütüphane paketlenmeden kullanılabilir. Paketleme bir zorunluluk
değil, belirli sorunların çözümüdür.

#### Üç Adım

Bir paketleyicinin çekirdeği üç adımdır: giriş noktasından başlayarak çizgeyi çıkarmak,
her modülü bir fonksiyona sarmak ve modülleri birbirine bağlayan küçük bir çalışma
zamanıyla birlikte yazmak.

Ölçüm kütüphanesinin çağrı tabanlı modüllerle yazılmış hâli girdi olsun:

```javascript
// dosya: kaynak/bolumleme.js
const SOZCUK_AYIRACI = /[^\p{L}\p{N}]+/u;

exports.sozcuklereBol = function (metin) {
  return metin.split(SOZCUK_AYIRACI).filter((s) => s.length > 0);
};
```

```javascript
// dosya: kaynak/istatistik.js
exports.ortalamaUzunluk = function (sozcukler) {
  if (sozcukler.length === 0) return 0;
  return sozcukler.reduce((t, s) => t + s.length, 0) / sozcukler.length;
};

exports.enUzunSozcuk = function (sozcukler) {
  return sozcukler.reduce((en, s) => (s.length > en.length ? s : en), '');
};
```

```javascript
// dosya: kaynak/index.js
const { sozcuklereBol } = require('./bolumleme.js');
const { enUzunSozcuk, ortalamaUzunluk } = require('./istatistik.js');

exports.olc = function (metin) {
  const sozcukler = sozcuklereBol(metin);
  return {
    sozcukSayisi: sozcukler.length,
    ortalamaUzunluk: ortalamaUzunluk(sozcukler),
    enUzun: enUzunSozcuk(sozcukler),
  };
};
```

```javascript
// dosya: kaynak/giris.js
const { olc } = require('./index.js');

console.log(olc('Modul kendi kapsamini tasir. Betik genel kapsamda calisir.'));
```

Paketleyici, bu dört dosyayı tek bir çıktıya çevirir:

```javascript
// dosya: paketle.mjs
import { readFile, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';

const ISTEK = /require\(['"]([^'"]+)['"]\)/g;

async function grafCikar(giris) {
  const moduller = new Map();
  const kuyruk = [giris];
  while (kuyruk.length > 0) {
    const yol = kuyruk.shift();
    if (moduller.has(yol)) continue;
    const kaynak = await readFile(yol, 'utf8');
    const harita = {};
    for (const [, istek] of kaynak.matchAll(ISTEK)) {
      const cozulen = join(dirname(yol), istek);
      harita[istek] = cozulen;
      kuyruk.push(cozulen);
    }
    moduller.set(yol, { kaynak, harita });
  }
  return moduller;
}

function uret(moduller, giris) {
  const kayit = [...moduller]
    .map(([yol, m]) => `${JSON.stringify(yol)}: [function (require, module, exports) {\n${m.kaynak}}, ${JSON.stringify(m.harita)}]`)
    .join(',\n');
  return `(function (kayit, giris) {
  const onbellek = {};
  function calistir(yol) {
    if (onbellek[yol]) return onbellek[yol].exports;
    const [fabrika, harita] = kayit[yol];
    const modul = { exports: {} };
    onbellek[yol] = modul;
    fabrika((istek) => calistir(harita[istek]), modul, modul.exports);
    return modul.exports;
  }
  calistir(giris);
})({\n${kayit}\n}, ${JSON.stringify(giris)});
`;
}

const giris = 'kaynak/giris.js';
const moduller = await grafCikar(giris);
const cikti = uret(moduller, giris);
await writeFile('paket.js', cikti);
console.log('modül sayısı:', moduller.size);
console.log('çizge:', [...moduller.keys()].join(', '));
console.log('çıktı boyutu:', cikti.length, 'bayt');
```

```
$ node paketle.mjs
modül sayısı: 4
çizge: kaynak/giris.js, kaynak/index.js, kaynak/bolumleme.js, kaynak/istatistik.js
çıktı boyutu: 1781 bayt
```

```
$ node paket.js
{ sozcukSayisi: 8, ortalamaUzunluk: 6.125, enUzun: 'kapsamini' }
```

Dört dosya tek dosyaya indi ve çıktı, kaynağın verdiği sonucun aynısını üretti.

#### Çıktının Yapısı

Üretilen dosyanın başı, çalışma zamanının tamamını gösterir:

```javascript
(function (kayit, giris) {
  const onbellek = {};
  function calistir(yol) {
    if (onbellek[yol]) return onbellek[yol].exports;
    const [fabrika, harita] = kayit[yol];
    const modul = { exports: {} };
    onbellek[yol] = modul;
    fabrika((istek) => calistir(harita[istek]), modul, modul.exports);
    return modul.exports;
  }
  calistir(giris);
})({
"kaynak/giris.js": [function (require, module, exports) {
// dosya: kaynak/giris.js
```

Yapı, modül sistemleri konusunda anlatılanların birebir karşılığıdır. Her modül bir
fonksiyona sarılmıştır — modül sarmalayıcısının çıktıya yazılmış hâli. `onbellek` nesnesi
tek değerlendirme güvencesini sağlar. `harita` nesnesi, kaynaktaki belirteçleri çıktıdaki
anahtarlara çevirir; çözümleme derleme anında yapılmış, çalışma anına bırakılmamıştır.

Örneğin sınırları da açıktır ve gerçek araçlarla arasındaki farkı verir. Belirteçleri
düzenli ifadeyle bulmak yeterli değildir: dizgi içinde geçen bir `require` yazımı da
eşleşir. Gerçek araçlar kaynağı ayrıştırıp soyut sözdizim ağacı üzerinden çalışır —
Bilgisayarlar Nasıl Çalışır kursundaki derleme aşamaları dersinde tanıtılan yapı budur.
Ayrıca burada uzantı tamamlama, dizin girişleri ve çıplak ad araması yoktur; hepsi gerçek
bir çözümleyicinin işidir.

#### Kapsam Birleştirme

Her modülün ayrı bir fonksiyona sarılması bir maliyet taşır: her modül için bir fonksiyon
nesnesi, bir kapanış ve bir çağrı. Modül sayısı arttıkça bu maliyet ölçülebilir hâle gelir.

**Kapsam birleştirme (scope hoisting)**, birbirine bağlı modülleri ayrı fonksiyonlara
sarmak yerine tek bir kapsamda birleştiren dönüşümdür. Adlar çakışmaması için yeniden
adlandırılır; sonuç, ara katman olmadan çalışan tek bir gövdedir.

Dönüşüm ancak modüller arası ilişkiler derleme anında biliniyorsa uygulanabilir. Çağrı
tabanlı sistemde `require` çağrısı çalışma anında hesaplanabildiği için bu bilgi eksiktir;
standart modül sözdiziminde ise tamdır. Statik yapının ikinci somut kazancı budur.

#### Ağaç Sarsmanın Sınırı

Kullanılmayan kodun çıktıdan düşürülmesi, örnek paketleyicide hiç yapılmıyor. Etkisi
ölçülebilir: istatistik modülüne, hiçbir yerden çağrılmayan bir işlev eklendiğinde

```javascript
// kaynak/istatistik.js dosyasının sonuna eklenir
// Hiçbir yerden çağrılmayan, ama modülde duran bir dışa aktarım.
exports.harfDagilimi = function (sozcukler) {
  const sayac = new Map();
  for (const sozcuk of sozcukler) {
    for (const harf of sozcuk.toLowerCase()) {
      sayac.set(harf, (sayac.get(harf) ?? 0) + 1);
    }
  }
  return [...sayac.entries()].sort((a, b) => b[1] - a[1]);
};
```

çıktı büyür, davranış değişmez:

```
$ node paketle.mjs
modül sayısı: 4
çizge: kaynak/giris.js, kaynak/index.js, kaynak/bolumleme.js, kaynak/istatistik.js
çıktı boyutu: 2127 bayt
```

```
$ node paket.js
{ sozcukSayisi: 8, ortalamaUzunluk: 6.125, enUzun: 'kapsamini' }
```

Üç yüz kırk altı bayt, hiç çalışmayan kod için ödendi. Gerçek araçlar bu işlevi
düşürebilir. Ön koşul, kapsam birleştirmedeki ile aynıdır: modüller arası ilişkilerin
derleme anında bilinmesi, yani standart modül sözdizimi. Bu sağlandığında da üç koşul
aranır:

**Kullanım görülebilmeli.** Dışa aktarımın hangi adla ve nereden kullanıldığı statik olarak
izlenebilmelidir. Ad hesaplanarak erişiliyorsa izlenemez.

**Yan etki olmamalı.** Modülün en üst düzeyinde, dışa aktarımlardan bağımsız bir iş
yapılıyorsa — bir kayda yazma, bir yerleşik nesneyi değiştirme — modülün tümü düşürülemez.
Araç, yan etkisi olmadığı bildirilmemiş modüllerde temkinli davranır; bu yüzden paketler
bildiriminde yan etki bilgisini işaretler.

**Erişim doğrudan olmalı.** Ad alanı nesnesinin tamamı bir değere bağlanıp dolaştırılıyorsa
hangi adların kullanıldığı bilinemez.

Bu koşullar, kütüphane yazarına somut bir sorumluluk yükler: en üst düzeyde iş yapmamak,
adları doğrudan dışa aktarmak ve yan etki durumunu bildirimde doğru işaretlemek.

#### Dış Bırakılan Bağımlılıklar

Her bağımlılığın çıktıya katılması gerekmez. Çalışma zamanının kendi sağladığı modüller ya
da hedef ortamda zaten bulunan kütüphaneler **dış bağımlılık (external)** olarak
işaretlenir: paketleyici onları çizgeye almaz, çağrıyı olduğu gibi bırakır.

Ayrım kütüphane ve uygulama arasında da geçerlidir. Bir uygulamanın çıktısı, çalışması için
gereken her şeyi taşımalıdır. Bir kütüphanenin çıktısı ise bağımlılıklarını içine
gömmemelidir; gömerse tüketici aynı paketin iki kopyasını taşır ve bağımlılık ağacı
dersinde anlatılan yinelenen sürüm sorunları ortaya çıkar.

#### Özet

- Paketleyici, modül çizgesini girdi alıp hedef ortamda çalışan çıktı üretir; gerekçesi
  dosya başına ağ maliyeti, çözümleme farkı ve biçim farkıdır.
- Çekirdek üç adımdır: çizgeyi çıkarmak, her modülü bir fonksiyona sarmak, modülleri
  bağlayan küçük bir çalışma zamanı yazmak.
- Çıktıdaki önbellek tek değerlendirme güvencesini, belirteç haritası ise derleme anında
  yapılmış çözümlemeyi taşır.
- Kapsam birleştirme, modül başına düşen sarmalayıcı maliyetini kaldırır ve yalnızca
  ilişkiler derleme anında bilindiğinde uygulanabilir.
- Ağaç sarsma önce statik modül sözdizimini, sonra üç koşulu ister: kullanımın
  izlenebilmesi, yan etkisizlik ve doğrudan
  erişim. Yan etki bilgisi bildirimde işaretlenir.

#### Sonraki Adım

Paketleyici modülleri birleştirdi, ama kaynağın yazıldığı sözdizimin hedef ortamda
tanınacağını varsaydı. Bu varsayım her zaman doğru değildir: hedef, kaynağın kullandığı
yazımı ayrıştıramayabilir veya çağırdığı yeteneği sağlamıyor olabilir. Sonraki ders,
kaynağın hedefe uyarlanmasını ve bu uyarlamanın nerede durduğunu ele alır.

### Aktarım ve Hedefleme

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/aktarim-ve-hedefleme

Sözdizim indirgeme ile yetenek dolgusunun ayrımı, hedef kümesinin belirlenmesi, aktarımın çözemediği sorunlar ve kaynak dilin hedef dilden farklı olduğu durumlar.

Önceki ders modülleri tek çıktıda birleştirdi ve kaynağın yazıldığı sözdizimin hedef
ortamda tanınacağını varsaydı. Bu varsayım her zaman doğru değildir.

Kaynağı hedefin anlayacağı biçime çeviren dönüşüme **aktarım (transpilation)** denir.
Aktarım tek bir iş gibi görünür; aslında iki ayrı sorunu çözer ve ikisinin çözümü
birbirinden bağımsızdır.

#### İki Ayrı Sorun

**Sözdizim sorunu** ayrıştırma düzeyindedir. Hedef, kaynağın kullandığı yazımı
tanımıyorsa dosyayı okuyamaz bile; hata, tek bir satır çalışmadan çıkar. Çözümü, aynı
anlamı hedefin tanıdığı yapılarla ifade etmektir.

**Yetenek sorunu** çalışma zamanı düzeyindedir. Hedef dosyayı ayrıştırabilir ama çağrılan
bir işlev veya erişilen bir nesne orada bulunmayabilir. Hata, ilgili satıra gelindiğinde
çıkar. Çözümü, eksik yeteneği çalışma anında tanımlamaktır.

Ayrımın pratik sonucu şudur: ilk sorun derleme anında, ikincisi çalışma anında çözülür.
Bir araç ikisini birlikte yapıyor olabilir, ama ürettikleri farklıdır — biri kaynağı
değiştirir, diğeri çıktıya kod ekler.

#### Sözdizim İndirgeme

**Sözdizim indirgeme (downleveling)**, bir yapıyı daha eski bir yazımla aynı anlamda
yeniden ifade etmektir. Ölçüm kütüphanesinden bir sınıf, üç ayrı özellik kullanır: özel
alan, erişimci ve alan konumunda tanımlanmış ok fonksiyonu.

```javascript
// dosya: kaynak.mjs
export class Olcum {
  #sozcukler;
  constructor(sozcukler) { this.#sozcukler = sozcukler; }
  get sayi() { return this.#sozcukler.length; }
  enUzun = () => this.#sozcukler.reduce((en, s) => (s.length > en.length ? s : en), '');
}

const olcum = new Olcum(['bir', 'ikinci', 'ucuncu-sozcuk']);
console.log(olcum.sayi, olcum.enUzun());
```

Aynı anlamın, bu üç özelliğin hiçbirini kullanmayan karşılığı:

```javascript
// dosya: hedef.js
'use strict';

var _sozcukler = new WeakMap();

function Olcum(sozcukler) {
  var kendisi = this;
  _sozcukler.set(this, sozcukler);
  this.enUzun = function () {
    return _sozcukler.get(kendisi).reduce(function (en, s) {
      return s.length > en.length ? s : en;
    }, '');
  };
}

Object.defineProperty(Olcum.prototype, 'sayi', {
  get: function () { return _sozcukler.get(this).length; },
});

var olcum = new Olcum(['bir', 'ikinci', 'ucuncu-sozcuk']);
console.log(olcum.sayi, olcum.enUzun());
```

```
$ node kaynak.mjs
3 ucuncu-sozcuk
$ node hedef.js
3 ucuncu-sozcuk
$ wc -c kaynak.mjs hedef.js
     356 kaynak.mjs
     521 hedef.js
     877 total
```

Davranış aynı; boyut yarı yarıya büyük. İndirgeme bedava değildir ve bedeli üç kalemdir:

**Boyut.** Her indirgenen yapı daha uzun bir karşılıkla yazılır.

**Başarım.** Özel alanın zayıf eşleme (weak map) ile taklit edilmesi, doğrudan alan
erişiminden pahalıdır. Sınıf sözdizimindeki bazı yapıların indirgenmiş karşılıkları, motorun
eniyilediği yollardan çıkar.

**Anlam yaklaşıklığı.** Taklit tam değildir. Yukarıdaki karşılıkta `#sozcukler` alanı
gerçekten erişilemez değildir; `_sozcukler` adlı eşlemeye ulaşan kod onu okuyabilir.
JavaScript'te Nesneler ve Fonksiyonlar kursunda tanıtılan özel alan güvencesi indirgemede
korunamaz.

#### Yetenek Dolgusu

İkinci sorunun çözümü, eksik yeteneği çalışma anında tanımlamaktır; buna **yetenek dolgusu
(polyfill)** denir. Doğru kalıp, yeteneği önce sınamak ve yalnızca eksikse tanımlamaktır.

Kalıbı gerçekten sınamak için eksik bir ortam gerekir. Çalışma zamanının yalıtılmış
bağlam düzeneği, bir yeteneği kaldırarak böyle bir ortamı üretmeye yarar:

```javascript
// dosya: dolgu.mjs
import vm from 'node:vm';

const eskiOrtam = vm.createContext({ console });

// Yeteneği ortamdan kaldırarak eski bir çalışma zamanını taklit ediyoruz.
vm.runInContext('delete Array.prototype.at;', eskiOrtam);
vm.runInContext('console.log("dolgu öncesi:", typeof [].at);', eskiOrtam);

const dolgu = `
if (typeof Array.prototype.at !== 'function') {
  Object.defineProperty(Array.prototype, 'at', {
    value: function (dizin) {
      const n = Math.trunc(dizin) || 0;
      return this[n < 0 ? this.length + n : n];
    },
    writable: true, configurable: true, enumerable: false,
  });
}
`;
vm.runInContext(dolgu, eskiOrtam);
vm.runInContext('console.log("dolgu sonrası:", typeof [].at, [10, 20, 30].at(-1));', eskiOrtam);

// Ana ortam etkilenmedi.
console.log('ana ortam:', typeof [].at, [10, 20, 30].at(-1));
```

```
$ node dolgu.mjs
dolgu öncesi: undefined
dolgu sonrası: function 30
ana ortam: function 30
```

Üç ayrıntı kalıbın parçasıdır. Sınama, sürüm sorgusuyla değil **yeteneğin kendisiyle**
yapılır: ortamın kimliği değil, aranan işlevin varlığı sorulur. Tanım, sıradan atama yerine
özellik tanımlayıcısıyla yapılır; böylece sayılabilirlik kapatılır ve yerleşik yöntemlerin
davranışı taklit edilir. Son olarak yetenek dolgusu, yerleşik bir nesneyi değiştirdiği için tüm
programı etkiler — bu yüzden dolgular giriş noktasında bir kez yüklenir, kütüphane
modüllerinin içine serpiştirilmez.

Bir kütüphane, tükettiği ortamın yerleşiklerini değiştirmemelidir. Kütüphanenin doğru
davranışı, eksik yeteneği kendi içinde bir yardımcı işlevle karşılamaktır; yerleşiği
değiştirme kararı uygulamanındır.

#### Hedef Kümesi

Hangi yapıların indirgeneceğine ve hangi yetenek dolgularının ekleneceğine karar veren şey **hedef
kümesidir**: çıktının çalışması istenen ortamların listesi. Küme genişledikçe daha fazla
indirgeme yapılır, çıktı büyür ve başarım düşer.

Kümeyi belirlerken iki hata sıktır. Birincisi, kümeyi gereğinden geniş tutmaktır: hiç
kullanılmayan ortamlar için ödenen boyut ve başarım, gerçek kullanıcıların maliyetidir.
İkincisi, kümeyi yazmamaktır — varsayılan bir liste, projenin gerçek kullanıcılarına
uymayabilir.

Doğru yaklaşım kümeyi yazılı ve gerekçeli tutmaktır. Küme, çıktının hangi ortamlarda
çalışacağını bildiren bir sözleşmedir; sözleşmenin dışındaki ortamlarda çıktının çalışması
beklenmez. Kütüphane paketlerinde bu bilgi belgelenmelidir, çünkü tüketicinin hedef kümesi
kütüphanenin kümesini kapsamak zorundadır.

#### Aktarımın Yapamadıkları

Aktarımın sınırları, hedeflemenin sınırlarıdır.

**Motor ilkelleri taklit edilemez.** Yeni bir ilkel tip, çöp toplayıcıya bağlı bir yapı ya
da bellek düzeni gerektiren bir yetenek, kütüphane koduyla yazılamaz. Böyle bir yeteneğin
dolgusu ya yaklaşık olur ya da hiç olmaz.

**Anlam farkları kalır.** Özel alan örneğinde görüldüğü gibi, indirgenmiş karşılık aynı
gözlemlenebilir davranışı vermeyebilir. Farklar genellikle uç durumlarda görünür ve
sınamalarda yakalanmayabilir.

**Başarım geri gelmez.** İndirgenmiş kod, hedefin yerel desteğinden yararlanamaz.

Bu üç sınır, hedeflemenin bir ödünleşim olduğunu gösterir: geniş bir küme daha çok kullanıcı,
daha büyük ve daha yavaş bir çıktı demektir. Karar ölçülerek verilmelidir.

#### Kaynak Dilin Farklı Olması

Aktarımın kapsamı yalnızca sürüm farkı değildir. Kaynak, hedef dilden başka bir dil de
olabilir; araç o dili okur ve hedefin çalıştırabileceği bir çıktı üretir. Bu durumda
dönüşüm iki iş yapar: kaynak dile özgü yapıları karşılamak ve kaynak dilin taşıdığı ek
bilgiyi çıktıdan çıkarmak.

En yaygın örnek tip bilgisidir: kaynakta yazılı tipler derleme anında denetlenir ve
çıktıya yazılmaz. Bu, dönüşümün en ucuz biçimidir — çıktı, kaynağın tip açıklamaları silinmiş
hâlinden ibarettir. Kursun son dersinde bağlanacağı yer burasıdır.

Kaynak ile çıktının ayrışması, bu dersin bıraktığı asıl sorundur. Hata bir yığıt izi
verdiğinde, izdeki satır numaraları çıktıya aittir; kaynağa değil. Aktarım ne kadar
kapsamlıysa bu iki dosya birbirinden o kadar uzaktır.

#### Özet

- Aktarım iki ayrı sorunu çözer: hedefin tanımadığı sözdizim ve hedefte bulunmayan yetenek;
  ilki derleme anında, ikincisi çalışma anında çözülür.
- Sözdizim indirgeme çıktıyı büyütür, başarımı düşürür ve bazı anlam güvencelerini
  yaklaşık hâle getirir.
- Yetenek dolgusu, sürüm sorgusuyla değil yeteneğin varlığıyla koşullanır; yerleşikleri
  değiştirdiği için uygulamanın giriş noktasına aittir, kütüphanelere değil.
- Hedef kümesi, çıktının hangi ortamlarda çalışacağını bildiren yazılı bir sözleşmedir;
  gereğinden geniş tutulması gerçek kullanıcılara maliyet çıkarır.
- Motor ilkelleri taklit edilemez, anlam farkları kalır ve indirgenmiş kod yerel desteğin
  başarımına ulaşamaz.

#### Sonraki Adım

Çıktı artık kaynaktan farklı bir dosyadır: satırları kaymış, yapıları değişmiş, adları
yeniden yazılmış olabilir. Bir hata oluştuğunda geliştiricinin gördüğü satır numarası
üretilmiş dosyaya aittir. Sonraki ders, üretilmiş konumdan kaynak konuma geri dönmeyi
sağlayan eşleme dosyasını ve bu dosyanın nasıl üretildiğini ele alır.

### Kaynak Haritaları

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/kaynak-haritalari

Üretilmiş konumdan kaynak konuma eşleme, harita dosyasının alanları, eşleme dizgisinin kodlanması, haritanın elle üretilmesi ve üretim ortamında yayımlama kararı.

Önceki ders, aktarımın kaynak ile çıktıyı birbirinden ayırdığı noktada durdu. Çıktının
satırları kaymış, yapıları değişmiş olabilir. Bir hata oluştuğunda yığıt izindeki satır
numarası üretilmiş dosyaya aittir ve geliştiricinin elindeki kaynakla eşleşmez.

**Kaynak haritası (source map)**, bu iki dosya arasındaki konum eşlemesini taşıyan bir veri
dosyasıdır. Kod değildir ve çalıştırılmaz; yalnızca "üretilmiş dosyanın şu satır ve sütunu,
kaynak dosyanın şu satır ve sütununa karşılık gelir" bilgisini tutar.

#### Eşlemenin Konusu

Eşleme, dosya değil **konum** düzeyindedir. Bir üretilmiş konum verildiğinde, karşılığı
olan kaynak dosya, satır ve sütun bulunur. Bu ayrıntı düzeyi gereklidir: tek bir üretilmiş
satır, birden çok kaynak satırdan gelmiş olabilir.

Eşlemeyi kullanan üç işlem vardır: yığıt izlerinin kaynağa çevrilmesi, hata ayıklayıcıda
kesme noktalarının kaynak dosyaya konması ve başarım ölçümlerinde sürelerin kaynak
işlevlere yazılması. Üçü de Eşzamansız JavaScript kursundaki hata ayıklama ve profilleme
derslerinde tanıtılan araçların üretilmiş kodla çalışabilmesini sağlar.

#### Harita Dosyasının Alanları

Harita, JSON biçiminde bir nesnedir:

| Alan | İçerik |
|---|---|
| `version` | Biçimin sürüm numarası |
| `file` | Haritanın ait olduğu üretilmiş dosyanın adı |
| `sources` | Kaynak dosyaların yolları |
| `sourcesContent` | Kaynak dosyaların içeriği; isteğe bağlı |
| `names` | Yeniden adlandırılmış tanımlayıcıların özgün adları |
| `mappings` | Konum eşlemelerinin kodlanmış hâli |

`sourcesContent` alanı, kaynağın harita dosyasının içine gömülmesini sağlar. Gömülüyse
araç kaynağı ayrıca aramaz; gömülü değilse `sources` yollarından okumaya çalışır ve
bulamazsa kaynağı gösteremez.

Üretilmiş dosya, haritasına son satırındaki bir yorumla bağlanır. Bu yorum, çalışma zamanına
ve hata ayıklama araçlarına haritayı nerede arayacaklarını söyler.

#### Eşleme Dizgisinin Yapısı

`mappings` alanı, tüm eşlemeleri tek bir dizgide taşır. Yapı üç düzeylidir:

- Noktalı virgül, **üretilmiş satırları** ayırır. Ardışık iki noktalı virgül, o satırda
  hiç eşleme olmadığını gösterir.
- Virgül, aynı satırdaki **bölümleri (segment)** ayırır.
- Eşleme taşıyan her bölüm dört veya beş sayıdan oluşur: üretilmiş sütun, kaynak dosya
  dizini, kaynak satır, kaynak sütun ve varsa ad dizini.

Sayılar doğrudan yazılmaz. İki sıkıştırma uygulanır: her sayı bir öncekine göre **fark**
olarak tutulur ve farklar **değişken uzunluklu nicelik (variable-length quantity)**
kodlamasıyla yazılır. Kodlama, sayıyı beş bitlik gruplara böler, en düşük bite işaret
bitini koyar ve devam eden grupları altıncı bitle işaretler; her grup 64 karakterlik bir
abeceden bir harfle yazılır.

Sonuç, insan tarafından okunamayan ama küçük bir dizgidir. Bin satırlık bir dosyanın
eşlemesi, ayrıntısız yazıldığında yüz binlerce karakter tutar; bu kodlamayla onda birine iner.

#### Haritayı Üretmek

Kodlamayı anlamanın en kısa yolu, çalışan bir üretici yazmaktır. Aşağıdaki dönüşüm bilinçli
olarak yalındır: kaynağın başına üç satırlık bir başlık ekler. Dönüşüm sonunda her kaynak
satırı üç satır aşağı kaymıştır ve bu kayma harita olmadan izlenemez.

```javascript
// dosya: kaynak/olcer.mjs
export function olc(metin) {
  if (typeof metin !== 'string') {
    throw new TypeError('metin bekleniyordu');
  }
  return metin.split(/\s+/).length;
}

console.log(olc('bir iki uc'));
console.log(olc(42));
```

```javascript
// dosya: uret.mjs
import { mkdir, readFile, writeFile } from 'node:fs/promises';

const ABECE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

// Değişken uzunluklu nicelik kodlaması: işaret biti en düşük bitte, 5 bitlik gruplar.
function vlq(sayi) {
  let deger = sayi < 0 ? (-sayi << 1) | 1 : sayi << 1;
  let cikti = '';
  do {
    let parca = deger & 0b11111;
    deger >>>= 5;
    if (deger > 0) parca |= 0b100000;
    cikti += ABECE[parca];
  } while (deger > 0);
  return cikti;
}

await mkdir('cikti', { recursive: true });

const kaynakAdi = 'kaynak/olcer.mjs';
const kaynak = await readFile(kaynakAdi, 'utf8');
const satirlar = kaynak.split('\n');

const banner = ['/* üretilmiş dosya */', '/* elle düzenlemeyin */', ''];
const cikti = [...banner, ...satirlar];

// Banner satırlarının eşlemesi yok; her üretilmiş satır, kaynaktaki karşılığına bakar.
const bolumler = banner.map(() => '');
let oncekiKaynakSatiri = 0;
for (let i = 0; i < satirlar.length; i += 1) {
  const girinti = satirlar[i].length - satirlar[i].trimStart().length;
  bolumler.push([vlq(girinti), vlq(0), vlq(i - oncekiKaynakSatiri), vlq(girinti)].join(''));
  oncekiKaynakSatiri = i;
}

const harita = {
  version: 3,
  file: 'olcer.mjs',
  sources: ['../kaynak/olcer.mjs'],
  sourcesContent: [kaynak],
  names: [],
  mappings: bolumler.join(';'),
};

await writeFile('cikti/olcer.mjs', cikti.join('\n') + '\n//# sourceMappingURL=olcer.mjs.map\n');
await writeFile('cikti/olcer.mjs.map', JSON.stringify(harita));
console.log('mappings:', harita.mappings.slice(0, 60));
```

```
$ node uret.mjs
mappings: ;;;AAAA;AACA;EACE;IACI;EACE;EACE;AACA;AACA;AACA;AACA;AACA
```

Dizginin başındaki üç noktalı virgül, başlık satırlarının eşlemesiz olduğunu söyler.
Dördüncü satırın bölümü `AAAA` — dört sıfır: üretilmiş sütun 0, kaynak dosya 0, kaynak
satır farkı 0, kaynak sütun 0. Beşinci satırın bölümü `AACA` biçimindedir: üçüncü alan
artık sıfır değil, kaynak satırın bir arttığını gösteren bir fark. Girintili satırlarda
ilk ve son alanlar da sıfırdan çıkar; `EACE` bölümü, üretilmiş sütun 2 ve kaynak sütun 2
demektir.

Üretilen dosyanın ilk ve son satırları, bağlantının nasıl kurulduğunu gösterir:

```
$ head -5 cikti/olcer.mjs
/* üretilmiş dosya */
/* elle düzenlemeyin */

// dosya: kaynak/olcer.mjs
export function olc(metin) {
$ tail -2 cikti/olcer.mjs

//# sourceMappingURL=olcer.mjs.map
```

#### Geri İzleme

Üretilmiş dosya çalıştırıldığında hata, üretilmiş konumu bildirir:

```
$ node cikti/olcer.mjs 2>&1 | grep 'at olc' | sed "s|file://||; s|$(pwd -P)/||"
    at olc (cikti/olcer.mjs:7:11)
```

Aynı dosya, çalışma zamanının kaynak haritası desteği açılarak çalıştırıldığında:

```
$ node --enable-source-maps cikti/olcer.mjs 2>&1 | grep 'at olc' | sed "s|$(pwd -P)/||"
    at olc (kaynak/olcer.mjs:4:7)
```

Yığıt izi kaynak dosyayı ve kaynaktaki satırı gösterdi. Satır numarası kesindir; sütun
numarası yaklaşıktır, çünkü bu üretici satır başına tek bir bölüm yazdı. Gerçek araçlar
her sözcük birimi için bölüm yazar ve sütun da kesinleşir. `sed` çağrıları yalnızca
çıktıdaki mutlak dizin yolunu kısaltır.

#### Zincirleme ve Bütünlük

Bir kaynak birden çok dönüşümden geçebilir: önce aktarılır, sonra paketlenir, sonra
küçültülür. Her adım kendi haritasını üretir ve sonuncusu bir öncekine bakar. Araçların bu
zinciri birleştirip tek bir harita üretmesi gerekir; birleştirmeyen bir adım, zincirin
tümünü kullanılamaz hâle getirir.

Bu, aşağıdaki gözlemi açıklar: yığıt izi bazen kaynağa değil, ara bir çıktıya götürür.
Neden genellikle zincirin bir halkasında haritanın taşınmamış olmasıdır.

#### Üretim Ortamında Harita

Harita yayımlanırsa, kaynak kodun tamamı da yayımlanmış olur — özellikle `sourcesContent`
alanı doluysa. Karar üç seçenek arasındadır:

**Yayımlamamak.** Harita üretilir, ama çıktının yanında yayımlanmaz. Kaynak korunur; buna
karşılık üretimden gelen yığıt izleri elle çözülmelidir.

**Yayımlamak.** Harita çıktının yanında durur. Hata ayıklama en kolay hâlindedir; kaynak
kod herkese açıktır.

**Ayrı bir yere yayımlamak.** Harita, hata toplama sistemine yüklenir; çıktının yanında
bulunmaz. Yığıt izleri o sistemde çözülür; kaynak dışarıya açılmaz. Ek altyapı gerektirir.

Seçim kaynağın gizliliğine bağlıdır. Kaynak zaten açıksa harita yayımlamamak için bir
neden yoktur. Kapalıysa üçüncü seçenek, ikisinin arasındaki dengeyi kurar. Hangi seçenek
kullanılırsa kullanılsın, haritanın üretilmesi vazgeçilmezdir: üretilmeyen bir harita
sonradan elde edilemez.

#### Özet

- Kaynak haritası, üretilmiş dosyadaki konumları kaynak dosyadaki konumlara eşleyen bir
  veri dosyasıdır; kod içermez ve çalıştırılmaz.
- Eşlemeler tek bir dizgide, satır ve bölüm ayraçlarıyla ve fark alınıp değişken uzunluklu
  nicelik kodlamasıyla sıkıştırılarak tutulur.
- Üretilmiş dosya haritasına son satırındaki bir yorumla bağlanır; kaynak metin haritaya
  gömülebilir.
- Çalışma zamanının kaynak haritası desteği açıldığında yığıt izleri kaynak konumları
  gösterir; sütun kesinliği yazılan bölüm sıklığına bağlıdır.
- Çok adımlı dönüşümlerde her adım haritasını bir öncekiyle birleştirmelidir; birleştirmeyen
  bir adım zinciri kırar.
- Haritayı yayımlamak kaynağı da yayımlamaktır; ayrı bir hata toplama sistemine yükleme
  seçeneği bu ödünleşimi dengeler.

#### Sonraki Adım

Buraya kadarki araçlar kodu dönüştürdü. Bir başka araç sınıfı kodu dönüştürmez, yalnızca
okur ve kural kümesine göre yargılar: hangi yazımlar kusur belirtisidir, hangi biçim
tutarsızdır. Sonraki ders, çalıştırmadan yapılan bu denetimi ve biçimlendirmenin ondan
neden ayrı tutulduğunu ele alır.

### Statik Çözümleme ve Biçimlendirme

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/statik-cozumleme-ve-bicimlendirme

Ayrıştırma düzeyindeki denetimin sınırı, kural kümesi ve şiddet düzeyleri, uyarı bastırmanın disiplini, biçimlendirmenin çözümlemeden ayrılması ve yinelemeye dayanıklılık.

Buraya kadarki araçlar kodu dönüştürdü: modülleri birleştirdi, sözdizimi indirgedi, konum
eşlemesi üretti. Bu ders, kodu dönüştürmeyen bir araç sınıfını ele alır — kaynağı okuyup
kural kümesine göre yargılayan **statik çözümleme (static analysis)** araçlarını.

Kabuk Programlama kursunda aynı araç sınıfı kabuk betikleri için tanıtılmıştı; ölçütler ve
disiplin aynıdır. Burada dilin kendi özellikleri ve paket düzeyindeki yerleşim eklenir.

#### Ayrıştırma Düzeyinde Denetim

En temel denetim, kaynağın ayrıştırılabilir olup olmadığıdır. Çalışma zamanları bunu kod
çalıştırmadan yapan bir seçenek sunar:

```javascript
// dosya: bozuk.js
function olc(metin) {
  return metin.split(/\s+/.length;
}
```

```
$ node --check bozuk.js 2>&1 | sed "s|$(pwd -P)/||" | head -6
bozuk.js:3
  return metin.split(/\s+/.length;
                           ^^^^^^

SyntaxError: missing ) after argument list
    at wrapSafe (node:internal/modules/cjs/loader:1804:18)
```

Çıkış kodu sıfırdan farklıdır; bu, denetimin bir betiğe konabileceği anlamına gelir.
İletinin son satırındaki iç çerçeveler çalışma zamanının sürümüne göre değişir; ilk dört
satır kararlıdır.

Bu denetimin sınırı, ayrıştırmanın kendisidir. Ayrıştırılabilen her kaynak geçer — anlamı
ne olursa olsun:

```javascript
// dosya: supheli.js
function esikAsti(sayi, esik) {
  if (sayi = esik) {
    return true;
  }
  return false;
}
console.log(esikAsti(3, 5));
```

```
$ node --check supheli.js && echo "sozdizim denetimi: gecti"
sozdizim denetimi: gecti
$ node supheli.js
true
```

Koşulda karşılaştırma değil atama yapıldı. Program ayrıştırıldı, çalıştı ve yanlış yanıt
verdi: 3, 5'i aşmıyor. Dil bu yazımı geçerli sayar çünkü atama bir ifadedir ve değer
üretir. Yakalanması için ayrıştırmanın ötesinde bir yargı gerekir — kural kümesi burada
devreye girer.

#### Kural Kümesi ve Şiddet Düzeyleri

Bir çözümleyici, kaynağı ayrıştırıp elde ettiği yapı üzerinde kurallar çalıştırır. Her
kural bir örüntü arar, bulduğunda bir bulgu üretir ve bulgunun bir **şiddet düzeyi
(severity level)** olur.

Kuralların nasıl işlediğini görmek için küçük bir çözümleyici yeterlidir. Aşağıdaki
gerçekleştirim satır tabanlıdır; gerçek araçlar soyut sözdizim ağacı üzerinde çalışır, ama
kural, şiddet ve bastırma yapısı aynıdır.

```javascript
// dosya: cozumle.mjs
import { readFile } from 'node:fs/promises';

const KURALLAR = [
  { ad: 'gevsek-esitlik', siddet: 'hata', sina: (s) => /[^=!<>]==[^=]|[^!]!=[^=]/.test(s),
    ileti: 'gevşek karşılaştırma yerine katı karşılaştırma kullanın' },
  { ad: 'bos-yakalama', siddet: 'hata', sina: (s) => /catch\s*(\([^)]*\))?\s*\{\s*\}/.test(s),
    ileti: 'boş yakalama bloğu hatayı gizler' },
  { ad: 'konsol', siddet: 'uyari', sina: (s) => /console\.(log|debug)\(/.test(s),
    ileti: 'kütüphane kodunda doğrudan konsol yazımı' },
];

const YOKSAY = /\/\/\s*cozumleme-yoksay:\s*([\w-]+)/;

export async function cozumle(dosya) {
  const satirlar = (await readFile(dosya, 'utf8')).split('\n');
  const bulgular = [];
  satirlar.forEach((satir, i) => {
    const yoksayilan = satir.match(YOKSAY)?.[1];
    for (const kural of KURALLAR) {
      if (kural.ad === yoksayilan) continue;
      if (kural.sina(satir)) {
        bulgular.push({ dosya, satir: i + 1, ...kural });
      }
    }
  });
  return bulgular;
}

const bulgular = await cozumle(process.argv[2]);
for (const b of bulgular) {
  console.log(`${b.dosya}:${b.satir}  ${b.siddet.padEnd(6)} ${b.ad.padEnd(16)} ${b.ileti}`);
}
const hataVar = bulgular.some((b) => b.siddet === 'hata');
console.log(`${bulgular.length} bulgu`);
process.exit(hataVar ? 1 : 0);
```

Ölçüm kütüphanesinin istatistik modülü, üç kuralı da tetikleyecek biçimde yazılmış olsun:

```javascript
// dosya: src/istatistik.mjs
export function ortalamaUzunluk(sozcukler) {
  if (sozcukler.length == 0) return 0;
  return sozcukler.reduce((t, s) => t + s.length, 0) / sozcukler.length;
}

export function enUzunSozcuk(sozcukler) {
  console.log('enUzunSozcuk çağrıldı');   // cozumleme-yoksay: konsol
  try {
    return sozcukler.reduce((en, s) => (s.length > en.length ? s : en), '');
  } catch {}
}
```

```
$ node cozumle.mjs src/istatistik.mjs; echo "cikis kodu: $?"
src/istatistik.mjs:3  hata   gevsek-esitlik   gevşek karşılaştırma yerine katı karşılaştırma kullanın
src/istatistik.mjs:11  hata   bos-yakalama     boş yakalama bloğu hatayı gizler
2 bulgu
cikis kodu: 1
```

Sekizinci satırdaki konsol yazımı bulgu üretmedi: satır sonundaki yorum o kuralı bastırdı.
Çıkış kodu, Kabuk Programlama kursundaki çıkış kodu sözleşmesinin aynısını izler — sıfırdan
farklı kod, çağıran betiğin işlemi durdurmasını sağlar.

#### Kuralların Sınıflanması

Her kural aynı ağırlıkta değildir. Üç sınıf ayırt edilir:

**Doğruluk kuralları.** Kod büyük olasılıkla yanlış davranır: kullanılmayan atama, koşulda
atama, erişilemez kod, sözü beklemeden bırakma. Bunlar hata düzeyinde işaretlenmeye
değerdir.

**Şüphe kuralları.** Kod yanlış olmayabilir ama örüntü kusur belirtisidir: boş yakalama,
karşılaştırma yerine dönüştürme, geniş kapsamlı değişken. Uyarı düzeyi uygundur; bilinçli
kullanımlar için bastırma gerekir.

**Biçim kuralları.** Davranışa etkisi yoktur: girinti, tırnak türü, noktalı virgül. Bunlar
çözümleyicinin işi değildir; bir sonraki bölümün konusudur.

Ayrımın pratik önemi, hata düzeyinin bir maliyet taşımasıdır: hata düzeyindeki her kural,
işlem akışını durdurma yetkisi alır. Bu yetki biçim tercihlerine verildiğinde, ekip
çözümleyiciyi bir engel olarak görmeye başlar ve bastırma yorumları çoğalır.

#### Bastırma Disiplini

Bastırma yorumu gerekli bir düzenektir: hiçbir kural kümesi tüm meşru kullanımları
öngöremez. Kötüye kullanımı da aynı ölçüde kolaydır.

İşleyen üç kural vardır. Bastırma **dar** olmalıdır: tüm dosya değil tek satır, tüm kurallar
değil tek kural. Bastırma **gerekçeli** olmalıdır: yanındaki yorum neden bastırıldığını
söylemelidir. Bastırma **sayılabilir** olmalıdır: kaç bastırma olduğu ölçülüyorsa, artışı
bir sinyaldir.

Bir kural sürekli bastırılıyorsa sorun kuralın kendisindedir. O kural ya projeye uygun
değildir ve kapatılmalıdır, ya da yanlış tanımlanmıştır ve daraltılmalıdır.

#### Biçimlendirme Ayrı Bir İştir

**Biçimlendirici (formatter)**, kodun anlamını değiştirmeden yazımını tek bir düzene sokan
araçtır. Çözümleyiciden iki noktada ayrılır: bulgu bildirmez, doğrudan yazar; ve doğru
olanı değil, **tek olanı** seçer.

Doğru biçimlendiricinin taşıması gereken özellik **yinelemeye dayanıklılıktır (idempotent)**:
bir kez uygulanmış çıktıya yeniden uygulandığında hiçbir şey değişmemelidir. Bu özellik
olmadan araç, her çalıştırmada değişiklik üretir ve sürüm denetimini gürültüye boğar.

```javascript
// dosya: bicimle.mjs
import { readFile, writeFile } from 'node:fs/promises';

export function bicimle(kaynak) {
  const satirlar = kaynak
    .replace(/\t/g, '  ')
    .split('\n')
    .map((s) => s.replace(/\s+$/, ''));
  while (satirlar.length > 0 && satirlar.at(-1) === '') satirlar.pop();
  return satirlar.join('\n') + '\n';
}

const dosya = process.argv[2];
const once = await readFile(dosya, 'utf8');
const birKez = bicimle(once);
const ikiKez = bicimle(birKez);
await writeFile(dosya, birKez);
console.log('değişti mi:', once !== birKez);
console.log('idempotent mi:', birKez === ikiKez);
```

Girdi, sekme karakteri ve satır sonu boşluğu içeren bir dosya olsun. `cat -et` çağrısı
sekmeleri `^I`, satır sonlarını `$` ile gösterir:

```
$ printf 'export function olc(metin)   {\n\treturn metin.split(/\\s+/).length;   \n}\n' > bicimsiz.mjs
$ cat -et bicimsiz.mjs
export function olc(metin)   {$
^Ireturn metin.split(/\s+/).length;   $
}$
$ node bicimle.mjs bicimsiz.mjs
değişti mi: true
idempotent mi: true
$ cat -et bicimsiz.mjs
export function olc(metin)   {$
  return metin.split(/\s+/).length;$
}$
$ node bicimle.mjs bicimsiz.mjs
değişti mi: false
idempotent mi: true
```

İkinci çalıştırma hiçbir şey değiştirmedi. Örnek biçimlendirici yalnızca iki dönüşüm yapar;
gerçek araçlar kaynağı ayrıştırıp ağaçtan yeniden yazar ve satır uzunluğu, parantez
yerleşimi, alan hizalaması gibi kararları da verir. Yeniden yazma yaklaşımının sonucu,
çıktının girdinin özgün yazımından **tümüyle bağımsız** olmasıdır.

Ayrımın gerekçesi bir maliyet hesabıdır. Biçim tartışmaları inceleme süresinin ölçülebilir
bir bölümünü tüketir ve teknik bir sonuca bağlanmaz. Kararın araca devredilmesi bu süreyi
sıfırlar. Bunun karşılığında ekip, tek tek biçim tercihlerinden vazgeçer.

#### Otomasyon ve Sınırlar

İki aracın da işe yaraması, çalıştırılmalarına bağlıdır. Yerleşme sırası genellikle şudur:
düzenleyicide kayıtta biçimlendirme, işleme öncesi denetim, yapı hattında son denetim. Son
halka olmadan diğerleri isteğe bağlı kalır; yalnızca son halka varsa geri bildirim gecikir.

Statik çözümlemenin sınırı ilkesel bir sınırdır: kural kümesi, kodun ne yaptığını değil
nasıl yazıldığını bilir. Yanlış bulgular kaçınılmazdır ve kaçırılan kusurlar da öyle.
Çözümleyici sınamaların yerine geçmez; ikisi farklı kusur sınıflarını yakalar. Bir
çözümleyicinin temiz raporu, kodun doğru olduğunu değil, bilinen kusur örüntülerinden
kaçındığını söyler.

#### Özet

- Sözdizim denetimi ayrıştırılabilirliği sınar ve kod çalıştırmadan sonuç verir; anlamsal
  kusurları — koşulda atama gibi — yakalamaz.
- Statik çözümleyici, ayrıştırılmış kaynak üzerinde kural kümesi çalıştırır; her bulgu bir
  şiddet düzeyi taşır ve çıkış kodu işlem akışını durdurabilir.
- Kurallar doğruluk, şüphe ve biçim olarak ayrılır; hata düzeyi yalnızca doğruluk
  kurallarına verilir.
- Bastırma dar, gerekçeli ve sayılabilir olmalıdır; sürekli bastırılan bir kural yanlış
  tanımlanmıştır.
- Biçimlendirici anlamı değiştirmeden tek bir yazım düzeni uygular ve yinelemeye dayanıklı
  olmalıdır; kararın araca devredilmesi inceleme süresini biçim tartışmalarından kurtarır.
- Statik çözümleme sınamaların yerine geçmez; temiz rapor doğruluğu değil, bilinen kusur
  örüntülerinin yokluğunu bildirir.

#### Sonraki Adım

Bu kursta kurulan araçların hepsi komut satırından çağrılan programlardır: paketleyici,
çözümleyici, biçimlendirici, sınama koşucusu. Her geliştiricinin bunları aynı seçeneklerle
çağırması bir bellek işi olmamalıdır. Kursun son dersi, bu çağrıların projede nasıl
adlandırılıp saklandığını ve yeniden üretilebilir bir komut kümesinin nasıl kurulduğunu
ele alır.

### Görev Otomasyonu

Source: https://academia.sh/tr/kurslar/javascript-ekosistemi/gorev-otomasyonu

Betik tanımlarının bildirim dosyasında saklanması, betik koşucusunun yaptığı işler, yaşam döngüsü kancaları, çıkış kodu sözleşmesi ve yeniden üretilebilir komut kümesi.

Kurs boyunca kurulan araçların hepsi komut satırından çağrılan programlardır: paketleyici,
çözümleyici, biçimlendirici, sınama koşucusu. Her birinin kendi seçenekleri, kendi girdi
yolları vardır.

Bu komutların geliştiricinin belleğinde durması iki sorun çıkarır. Komutlar kişiden kişiye
farklılaşır ve yapı sunucusunda çalışan komut, geliştiricinin çalıştırdığından başka olur.
Bu ders, komutları projenin kendisine yazmayı ve çalıştırılmalarını tek bir sözleşmeye
bağlamayı ele alır.

#### Betik Tanımları

Bildirim dosyasındaki betik alanı, projeye ait komutları adlarıyla saklar:

```json
{
  "name": "metin-olcer",
  "version": "0.1.0",
  "type": "module",
  "exports": { ".": "./src/index.mjs" },
  "scripts": {
    "presinama": "node --check src/index.mjs",
    "sinama": "node --test",
    "olcum": "node araclar/olc.mjs",
    "bozuk": "node --check src/bozuk.mjs"
  }
}
```

Tanımların bildirimde durmasının üç sonucu vardır. Komutlar sürüm denetimine girer ve
değişiklikleri incelenebilir. Depoyu klonlayan bir geliştirici, hangi komutların
bulunduğunu okuyarak öğrenir. Aynı adlar hem yerelde hem yapı hattında çağrılır; iki ortam
arasındaki fark ortadan kalkar.

Adlandırma sözleşmesi de önemlidir. Ad, aracın adını değil **yapılan işi** anlatmalıdır.
Sınama komutu `sinama` adını taşır; hangi sınama koşucusunun kullanıldığı tanımın sağ
tarafındadır. Araç değiştiğinde çağıranlar değişmez — komut adları projenin arayüzüdür,
araç seçimi ise gerçekleştirimi.

#### Betik Koşucusunun Yaptığı

Betikleri çalıştıran araca **betik koşucusu** denir ve yaptığı iş dört maddeye sığar.
Kendi gerçekleştirimi, dördünü de görünür kılar:

```javascript
// dosya: kos.mjs
import { spawnSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { delimiter, resolve } from 'node:path';

const bildirim = JSON.parse(readFileSync('package.json', 'utf8'));
const betikler = bildirim.scripts ?? {};
const [istenen, ...ekArgumanlar] = process.argv.slice(2);

function calistir(ad, argumanlar = []) {
  const komut = [betikler[ad], ...argumanlar].join(' ');
  console.log(`> ${ad}: ${komut}`);
  const sonuc = spawnSync(komut, {
    shell: true,
    stdio: 'inherit',
    env: { ...process.env, PATH: resolve('node_modules/.bin') + delimiter + process.env.PATH },
  });
  return sonuc.status ?? 1;
}

if (betikler[istenen] === undefined) {
  console.error(`betik yok: ${istenen}`);
  process.exit(1);
}

for (const ad of [`pre${istenen}`, istenen, `post${istenen}`]) {
  if (betikler[ad] === undefined) continue;
  const kod = calistir(ad, ad === istenen ? ekArgumanlar : []);
  if (kod !== 0) {
    console.error(`durdu: ${ad} betiği ${kod} kodu ile çıktı`);
    process.exit(kod);
  }
}
```

**Komutu bulur.** Ad bildirimdeki tablodan okunur; yoksa hata verilir.

**Ortamı hazırlar.** Kurulu paketlerin çalıştırılabilir dosyaları `node_modules/.bin`
dizinine yerleştirilir ve bu dizin arama yolunun başına eklenir. Sonucu şudur: bir aracı
komut adıyla çağırmak için sistem geneline kurmak gerekmez; projenin kendi kopyası
bulunur. Linux'a Giriş kursundaki komut yolu dersinde tanıtılan arama sırası burada
bilinçli bir araç hâline gelir.

**Kancaları çağırır.** İstenen betikten önce `pre` önekli, sonra `post` önekli aynı adlı
betik varsa çalıştırılır.

**Çıkış kodunu taşır.** Alt sürecin kodu koşucunun kodu olur.

Sınama betiği çalıştırıldığında dördünün ikisi tek çıktıda görünür:

```
$ node ../kos.mjs sinama 2>&1 | grep -vE 'duration_ms|ms\)|^$'
> presinama: node --check src/index.mjs
> sinama: node --test
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
```

Kanca kendiliğinden çalıştı, ardından asıl betik çalıştı. Süzgeç, çıktıdaki süre
ölçümlerini eler; süreler her çalıştırmada değişir. Sınama dosyası, çalışma zamanının
yerleşik sınama arayüzünü kullanır:

```javascript
// dosya: test/olcum.test.mjs
import assert from 'node:assert/strict';
import test from 'node:test';
import { olc } from '../src/index.mjs';

test('sözcük ve cümle sayısı', () => {
  const olcum = olc('Bir cumle. Iki cumle.');
  assert.equal(olcum.sozcukSayisi, 4);
  assert.equal(olcum.cumleSayisi, 2);
});

test('boş metin sıfır döndürür', () => {
  assert.equal(olc('').sozcukSayisi, 0);
});
```

#### Argüman Aktarımı

Bazı betikler değişken girdi alır. Koşucu, betik adından sonra verilen argümanları komutun
sonuna ekler:

```javascript
// dosya: araclar/olc.mjs
import { olc } from '../src/index.mjs';

const metin = process.argv.slice(2).join(' ');
if (metin === '') {
  console.error('kullanım: node araclar/olc.mjs <metin>');
  process.exit(2);
}
const olcum = olc(metin);
console.log(`${olcum.sozcukSayisi} sözcük, ${olcum.cumleSayisi} cümle, en uzun: ${olcum.enUzun}`);
```

```
$ node ../kos.mjs olcum "Modul kendi kapsamini tasir. Betik genel kapsamda calisir."
> olcum: node araclar/olc.mjs Modul kendi kapsamini tasir. Betik genel kapsamda calisir.
8 sözcük, 2 cümle, en uzun: kapsamini
```

Argümanların kabuk tarafından yeniden yorumlandığına dikkat edin: komut bir kabuk içinde
çalıştığı için boşluk ve özel karakterler kabuk kurallarına tabidir. Kabuk Programlama
kursundaki alıntılama kuralları burada geçerlidir; değişken içerik taşıyan argümanlar
alıntılanmalıdır.

#### Başarısızlık Sözleşmesi

Otomasyonun tümü tek bir sözleşmeye dayanır: bir komut başarısız olduğunda sıfırdan farklı
bir çıkış kodu döndürür. Sözleşme tutulduğunda zincir kendiliğinden durur:

```javascript
// dosya: src/bozuk.mjs
export function eksik(metin) {
  return metin.split(/\s+/.length;
}
```

```
$ node ../kos.mjs bozuk 2>&1 | grep -E '^>|^durdu'
> bozuk: node --check src/bozuk.mjs
durdu: bozuk betiği 1 kodu ile çıktı
$ node ../kos.mjs bozuk > /dev/null 2>&1; echo "cikis kodu: $?"
cikis kodu: 1
```

Sözdizim denetimi başarısız oldu, koşucu durdu ve kendi çıkış kodunu 1 yaptı. Bu kod, onu
çağıran bir üst katmanın — yapı hattının — durmasını sağlar.

Sözleşmeyi bozan iki yaygın hata vardır. Birincisi, hata durumunda sıfır döndüren
betiklerdir; boru hattının son komutu başarılıysa kabuk sıfır döndürür ve gerçek hata
gizlenir. İkincisi, yalnızca ekrana hata yazıp çıkış kodunu ayarlamayan betiklerdir;
otomasyon ekranı okumaz, yalnızca kodu okur.

#### Yeniden Üretilebilirlik

Aynı komutun aynı sonucu vermesi dört koşula bağlıdır.

**Bağımlılıklar kilitten kurulur.** Kilit dosyası varsa kurulum bir arama değil yeniden
üretimdir. Yapı hattında, kilidi güncelleyen değil kilide uyan kurulum biçimi
kullanılmalıdır; kilitle bildirim çelişirse işlem durmalıdır.

**Araçlar projeye kuruludur.** Çözümleyici, biçimlendirici ve derleme araçları geliştirme
bağımlılığı olarak bildirilir. Sistem geneline kurulmuş bir araca güvenmek, sürümünü
denetimsiz bırakmaktır.

**Komutlar tek yerde tanımlıdır.** Yapı hattı yapılandırması komutları yeniden yazmaz;
betik adlarını çağırır. Böylece komutun içeriği değiştiğinde iki yer güncellenmez.

**Çıktılar girdilerden belirlenir.** Derleme çıktısı, kaynak ve yapılandırmadan başka bir
şeye — zamana, makine adına, ağ erişimine — bağlıysa yeniden üretilemez.

Bu dört koşul sağlandığında, yapı hattı yalnızca geliştiricinin yerelde çalıştırdığı
komutları çalıştıran bir ortama dönüşür. Bir hata yapı hattında çıkıp yerelde çıkmıyorsa,
koşullardan biri sağlanmıyor demektir; hatanın kendisini aramadan önce hangi koşulun
bozulduğu aranmalıdır.

#### Özet

- Betik tanımları bildirimde saklanır; sürüm denetimine girer, belgeleme işlevi görür ve
  yerel ile yapı hattı arasındaki komut farkını ortadan kaldırır.
- Betik adları yapılan işi anlatır, aracı değil; araç değiştiğinde çağıranlar değişmez.
- Koşucu komutu bulur, kurulu araçların dizinini arama yolunun başına ekler, `pre` ve
  `post` kancalarını çağırır ve çıkış kodunu taşır.
- Otomasyon çıkış kodu sözleşmesine dayanır: başarısızlık sıfırdan farklı bir kodla
  bildirilmezse zincir yanlış biçimde sürer.
- Yeniden üretilebilirlik dört koşula bağlıdır: kilitten kurulum, projeye kurulu araçlar,
  tek yerde tanımlı komutlar ve girdilerden belirlenen çıktılar.

#### Kurs Kapanışı

Kurs, tek dosyalık bir ölçüm betiğiyle başladı ve o betiği bir pakete dönüştürerek ilerledi.
Yol boyunca dört soru yanıtlandı.

**Kod nasıl bölünür?** Modül kapsamı, adların dışarı sızmasını ayrıştırma düzeyinde
engeller. Dışa aktarım yüzeyi bir tasarım kararıdır: statik sözdizim bağlamayı
değerlendirmeden önceye alır, çağrı tabanlı sistem esneklik karşılığında bu güvenceden
vazgeçer, dinamik içe aktarım ikisinin arasında bir yol açar.

**Bağımlılıklar nasıl yönetilir?** Çıplak ad, dizin ağacında belirlenimci bir kuralla
aranır. Sürüm aralıkları bir kolaylıktır, kilit dosyası bir güvencedir. Ağaçtaki yinelenen
kopyalar sessiz arızalar üretir; geçişli kapanış, güvenilen kod kütlesinin ölçüsüdür.

**Kaynak hedefe nasıl ulaşır?** Paketleyici modül çizgesini tek çıktıya indirger, aktarım
sözdizimi ve yetenekleri hedefe uyarlar, kaynak haritası üretilmiş konumdan kaynağa geri
dönüş sağlar. Her adımın ölçülebilir bir bedeli vardır.

**Kalite nasıl korunur?** Statik çözümleme kusur örüntülerini kod çalıştırmadan yakalar,
biçimlendirme tartışmayı ortadan kaldırır, görev otomasyonu bu araçların herkeste aynı
biçimde çalışmasını sağlar.

Kursun boydan boya taşıdığı ilke, dördünde de aynıdır: **bilgiyi kodun içine yazmak.**
Bağımlılık bildirimde, sürüm sözleşmesi numarada, kurulu ağaç kilitte, kaynak eşlemesi
haritada, komutlar betik tablosunda durur. Belleğe ya da alışkanlığa bırakılan her bilgi,
bir başkasının makinesinde kaybolur.

Bir bilgi türü hâlâ kodun dışındadır: bir işlevin hangi tipte değer beklediği ve ne
döndürdüğü. Ölçüm kütüphanesinin işlevleri metin bekler, ama bu beklenti yalnızca çalışma
anında sınanır — sayıyla çağrılan bir işlev hatayı ancak kendisine gelindiğinde verir.
Sonraki kurs, bu bilgiyi kaynağa yazmayı ve derleme anında denetlemeyi ele alıyor: tip
çıkarımı, daraltma, genellikler ve derleyicinin katılık düzeyinin bilinçli
seçimi.

