---
title: 'Ağaç Sarsma'
source: 'https://academia.sh/tr/kurslar/olusturma-stratejileri/agac-sarsma'
course: 'Oluşturma Stratejileri ve Altyapı'
language: tr
updated: '2026-08-17T18:09:30+00:00'
license: 'CC BY-SA 4.0'
---

# Ağaç Sarsma

Çizgenin modül düzeyinden ad düzeyine indirilmesi; canlı dışa aktarımların yayılımı, yan etkili modülün elenememesi, paket bildirimindeki yan etki bildiriminin üç ayarı ve elemeyi engelleyen kaynak yazımları.

Bir önceki ders parçaların hangi modülleri taşıyacağını belirledi. Belirlemediği şey, o
modüllerin *içinde* nelerin taşınacağıdır. Bağımlılık çizgesi bir modülün gerekli olduğunu
söyler; içindeki hangi adın kullanıldığını söylemez. Kuzey Yamaç Ölçüm İstasyonu
uygulamasının birim modülü altı ad dışa aktarıyorsa ve bunların ikisi hiçbir yerden
çağrılmıyorsa, çizge bunu göremez.

**Ağaç sarsma (tree shaking)**, çizgeyi modül düzeyinden ad düzeyine indirip erişilemeyen
dışa aktarımları çıktıdan düşürme işidir. Modüller, Araçlar ve Ekosistem kursundaki
paketleyici dersi bu dönüşümün üç koşulunu saymıştı. Bu ders koşulları çalıştırarak
sınar: birinin ihlal edildiği bir kaynak ağaç kurar, elemenin ne zaman durduğunu ölçer ve
paketin bunu bildirimle nasıl mümkün kıldığını gösterir.

## Ad Düzeyinde Canlılık

Çizgede düğümler modüller değil, `modül#ad` çiftleridir. Bir çift **canlı** ise çıktıda
kalır; değilse düşer.

Yayılım giriş noktasından başlar. Giriş noktasının dışa aktarımları koşulsuz canlıdır;
onları isteyen şey derlemenin dışındadır. Canlı bir dışa aktarımın gövdesinde geçen her ad
da canlıdır: gövde bir içe aktarım adına dokunuyorsa, o adın kaynak modüldeki karşılığı
canlanır. Yeniden dışa aktarımlar zinciri uzatır — toplayıcı bir dosyadaki
`export { a } from "./x.js"` satırı, `a` istendiğinde `x.js` içindeki `a` adını canlandırır,
ötekilere dokunmaz.

Dinamik içe aktarım bu zincirin bir yerinde belirsizlik yaratır. `import()` çağrısının
sonucu bir ad alanı nesnesidir ve hangi adların kullanılacağı çağrı yerinden her zaman
okunamaz. Temkinli davranış, dinamik hedefin bütün dışa aktarımlarını canlı saymaktır.

## Kaynak Ağacın Gerçek Yüzeyi

Şimdiye kadarki kaynak ağaç yalnız kullanılan adları taşıyordu. Gerçek bir yardımcı modül
kümesi böyle olmaz: tüketicilerinin hepsinin ihtiyaç duymadığı bir yüzey sunar, adlar tek
bir toplayıcı dosyadan dağıtılır ve içlerinden biri en üst düzeyde iş yapar.

Aşağıdaki blok, Modül Paketleme ve Kod Bölme derslerinde kurulan `kaynak/` ağacını sürdürür; giriş noktası oradan gelir ve o derslerin kurulum blokları önce çalıştırılmalıdır.

```bash
mkdir -p kaynak

cat > kaynak/birim.js <<'EOF'
export const SICAKLIK_ALT = -60;
export const SICAKLIK_UST = 60;
export const sinirdaMi = (c) => c >= SICAKLIK_ALT && c <= SICAKLIK_UST;
export const fahrenhayt = (c) => c * 9 / 5 + 32;
export const milimetreCiva = (hpa) => hpa * 0.75006157584566;
export const ruzgarSogumasi = (c, hiz) =>
  13.12 + 0.6215 * c - 11.37 * hiz ** 0.16 + 0.3965 * c * hiz ** 0.16;
EOF

cat > kaynak/tarih.js <<'EOF'
const iki = (s) => String(s).padStart(2, "0");
export const saatDakika = (d) => iki(new Date(d).getUTCHours()) + ":" + iki(new Date(d).getUTCMinutes());
export const gunAnahtari = (d) => new Date(d).toISOString().slice(0, 10);
export const haftaAnahtari = (d) => {
  const t = new Date(d);
  const gun = Math.floor((t - Date.UTC(t.getUTCFullYear(), 0, 1)) / 86400000);
  return t.getUTCFullYear() + "-H" + iki(Math.ceil((gun + 1) / 7));
};
EOF

cat > kaynak/tahmin.js <<'EOF'
import { SICAKLIK_ALT, SICAKLIK_UST } from "./birim.js";

const gecerli = (o) => o.sicaklik > SICAKLIK_ALT && o.sicaklik < SICAKLIK_UST;

export const egilim = (olcumler) => {
  const dizi = olcumler.filter(gecerli);
  if (dizi.length < 2) return 0;
  return (dizi.at(-1).sicaklik - dizi[0].sicaklik) / (dizi.length - 1);
};
EOF

cat > kaynak/kayit.js <<'EOF'
export const bicimciler = new Map();
export const kaydet = (ad, islev) => bicimciler.set(ad, islev);
EOF

cat > kaynak/olcek-kaydi.js <<'EOF'
import { kaydet } from "./kayit.js";
import { fahrenhayt } from "./birim.js";

kaydet("fahrenhayt", fahrenhayt);
EOF

cat > kaynak/arac.js <<'EOF'
import "./olcek-kaydi.js";
export { sinirdaMi, fahrenhayt, milimetreCiva, ruzgarSogumasi } from "./birim.js";
export { saatDakika, gunAnahtari, haftaAnahtari } from "./tarih.js";
export { egilim } from "./tahmin.js";
export { bicimciler } from "./kayit.js";
EOF

cat > kaynak/bicim.js <<'EOF'
import { saatDakika, sinirdaMi, fahrenhayt, bicimciler } from "./arac.js";

export function olcumSatiri(o) {
  const uyari = sinirdaMi(o.sicaklik) ? "" : " (sinir disi)";
  const ikinci = bicimciler.has("fahrenhayt")
    ? " / " + bicimciler.get("fahrenhayt")(o.sicaklik).toFixed(1) + " F" : "";
  return saatDakika(o.damga) + "  " + o.sicaklik.toFixed(1) + " C" + ikinci + "  %" + o.nem + uyari;
}
export const ikiliBirim = (o) => o.sicaklik.toFixed(1) + " C / " + fahrenhayt(o.sicaklik).toFixed(1) + " F";
EOF

cat > paket.json <<'EOF'
{
  "name": "kuzey-yamac",
  "type": "module",
  "sideEffects": ["./kaynak/olcek-kaydi.js"]
}
EOF
```

Üç yapı eklendi. Birim ve tarih modülleri kullanılmayan adlar taşıyor. Toplayıcı dosya
adları tek yerden dağıtıyor ve içine bir de çıplak içe aktarım koyuyor. Ölçek kaydı modülü
hiçbir şey dışa aktarmıyor; yalnız en üst düzeyde bir kayda yazıyor ve biçimlendirme
modülü çıktısını o kayda bakarak değiştiriyor.

## Çözümleyici

Aşağıdaki betik modülleri satır satır ayrıştırır, canlılığı yayar ve üç ayrı yan etki
politikası altında çıktı boyutunu hesaplar.

```js
// sarsma.mjs — ad duzeyinde canlilik cozumlemesi ve uc yan etki politikasi.
import { readFileSync } from "node:fs";
import { dirname, join, relative } from "node:path";

const GIRIS = "kaynak/giris-tarayici.js";
const BASLANGIC = /^(import\b|export\b|const\b|let\b|function\b|class\b)/;

function ayristir(yol) {
  const satirlar = readFileSync(yol, "utf8").split("\n");
  const coz = (b) => relative(".", join(dirname(yol), b));
  const iceAktarim = new Map();          // yerel ad -> { modul, dis ad }
  const ciplak = [];                     // yalniz yan etki icin ice aktarilan moduller
  const parca = [];                      // { ad, modul, dis, govde, disaAktarim }
  for (let i = 0; i < satirlar.length; i++) {
    const s = satirlar[i];
    let e;
    if ((e = s.match(/^import\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/))) {
      for (const p of e[1].split(",").map((x) => x.trim()).filter(Boolean)) {
        const [dis, yerel = dis] = p.split(/\s+as\s+/);
        iceAktarim.set(yerel, { modul: coz(e[2]), dis });
      }
    } else if ((e = s.match(/^import\s*["']([^"']+)["']/))) {
      ciplak.push(coz(e[1]));
    } else if ((e = s.match(/^export\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/))) {
      for (const p of e[1].split(",").map((x) => x.trim()).filter(Boolean)) {
        parca.push({ ad: p, modul: coz(e[2]), dis: p, govde: "", disaAktarim: true,
          boyut: Buffer.byteLength(p) + 2 });
      }
    } else if ((e = s.match(/^(?:export\s+)?(?:const|let|function|async function|class)\s+(\w+)/))) {
      const govde = [s];
      while (i + 1 < satirlar.length && !BASLANGIC.test(satirlar[i + 1])) govde.push(satirlar[++i]);
      const metin = govde.join("\n").replace(/\s+$/, "");
      parca.push({ ad: e[1], govde: metin, disaAktarim: s.startsWith("export"),
        boyut: Buffer.byteLength(metin) + 1 });
    } else if (s.trim() && !s.trim().startsWith("//")) {
      parca.push({ ad: null, govde: s, disaAktarim: false, boyut: Buffer.byteLength(s) + 1 });
    }
  }
  return { iceAktarim, ciplak, parca, boyut: Buffer.byteLength(readFileSync(yol)) };
}

const modul = new Map();
const kuyruk = [GIRIS];
while (kuyruk.length > 0) {
  const yol = kuyruk.shift();
  if (modul.has(yol)) continue;
  const m = ayristir(yol);
  modul.set(yol, m);
  const dinamik = [...readFileSync(yol, "utf8").matchAll(/\bimport\(\s*["']([^"']+)["']\s*\)/g)]
    .map(([, b]) => relative(".", join(dirname(yol), b)));
  kuyruk.push(...[...m.iceAktarim.values()].map((i) => i.modul), ...m.ciplak, ...dinamik,
    ...m.parca.filter((p) => p.modul).map((p) => p.modul));
  m.dinamik = dinamik;
}

function coz(politika) {                 // politika: "yok" | "yansiz" | "liste"
  const LISTE = JSON.parse(readFileSync("paket.json", "utf8")).sideEffects;
  const canli = new Set();               // "modul#ad"
  const tutulan = new Set();
  const is = [];

  function govdeyiTara(yol, govde) {
    const m = modul.get(yol);
    for (const [yerel, kaynak] of m.iceAktarim)
      if (new RegExp("\\b" + yerel + "\\b").test(govde)) is.push([kaynak.modul, kaynak.dis]);
    for (const p of m.parca)
      if (p.ad && new RegExp("\\b" + p.ad + "\\b").test(govde) && !govde.startsWith("export"))
        is.push([yol, p.ad]);
  }

  function tut(yol) {
    if (tutulan.has(yol)) return;
    tutulan.add(yol);
    const m = modul.get(yol);
    for (const p of m.parca) if (!p.disaAktarim) govdeyiTara(yol, p.govde);
    for (const c of m.ciplak) {
      const korunur = politika === "yok" || (politika === "liste" && LISTE.includes("./" + c));
      if (korunur) tut(c);
    }
    for (const d of m.dinamik) {
      tut(d);
      for (const p of modul.get(d).parca) if (p.disaAktarim) is.push([d, p.ad]);
    }
    if (politika === "yok") {
      for (const i of m.iceAktarim.values()) tut(i.modul);
      for (const p of m.parca) if (p.modul) tut(p.modul);
    }
  }

  function canlandir(yol, ad) {
    const anahtar = yol + "#" + ad;
    if (canli.has(anahtar)) return;
    canli.add(anahtar);
    tut(yol);
    const p = modul.get(yol).parca.find((x) => x.ad === ad && x.disaAktarim);
    if (!p) return;
    if (p.modul) is.push([p.modul, p.dis]);
    else govdeyiTara(yol, p.govde);
  }

  tut(GIRIS);
  for (const p of modul.get(GIRIS).parca) if (p.disaAktarim) is.push([GIRIS, p.ad]);
  while (is.length > 0) canlandir(...is.pop());

  let toplam = 0;
  const dusen = [], oluAd = [];
  for (const [yol, m] of modul) {
    if (!tutulan.has(yol)) { dusen.push(yol); continue; }
    let bayt = m.boyut;
    for (const p of m.parca)
      if (p.disaAktarim && !canli.has(yol + "#" + p.ad)) {
        bayt -= p.boyut;
        oluAd.push(yol.replace("kaynak/", "") + "#" + p.ad);
      }
    toplam += bayt;
  }
  return { toplam, dusen, oluAd };
}

const ham = [...modul.values()].reduce((t, m) => t + m.boyut, 0);
console.log("cizgedeki modul:", modul.size, " ham boyut:", ham, "B");
for (const politika of ["yok", "yansiz", "liste"]) {
  const r = coz(politika);
  console.log("-- sideEffects: " + politika + " --");
  console.log("  cikti boyutu : " + r.toplam + " B  (kazanc " + (ham - r.toplam) + " B)");
  console.log("  dusen modul  : " + (r.dusen.map((y) => y.replace("kaynak/", "")).join(", ") || "-"));
  console.log("  olu ad       : " + (r.oluAd.sort().join(", ") || "-"));
}
```

```
$ node sarsma.mjs
cizgedeki modul: 13  ham boyut: 4188 B
-- sideEffects: yok --
  cikti boyutu : 3546 B  (kazanc 642 B)
  dusen modul  : -
  olu ad       : arac.js#egilim, arac.js#gunAnahtari, arac.js#haftaAnahtari, arac.js#milimetreCiva, arac.js#ruzgarSogumasi, birim.js#milimetreCiva, birim.js#ruzgarSogumasi, tahmin.js#egilim, tarih.js#haftaAnahtari
-- sideEffects: yansiz --
  cikti boyutu : 3231 B  (kazanc 957 B)
  dusen modul  : olcek-kaydi.js, tahmin.js
  olu ad       : arac.js#egilim, arac.js#gunAnahtari, arac.js#haftaAnahtari, arac.js#milimetreCiva, arac.js#ruzgarSogumasi, birim.js#milimetreCiva, birim.js#ruzgarSogumasi, kayit.js#kaydet, tarih.js#haftaAnahtari
-- sideEffects: liste --
  cikti boyutu : 3408 B  (kazanc 780 B)
  dusen modul  : tahmin.js
  olu ad       : arac.js#egilim, arac.js#gunAnahtari, arac.js#haftaAnahtari, arac.js#milimetreCiva, arac.js#ruzgarSogumasi, birim.js#milimetreCiva, birim.js#ruzgarSogumasi, tarih.js#haftaAnahtari
```

Üç politika, aynı kaynak ağaçtan üç ayrı çıktı üretiyor. Ölü ad listesi hepsinde ortak bir
çekirdek taşıyor: milimetre cıva, rüzgâr soğuması, hafta anahtarı ve eğilim hesabı hiçbir
canlı gövdede geçmiyor. Toplayıcı dosyadaki yeniden dışa aktarım satırları da onlarla
birlikte düşüyor — adı taşıyan satır, adın kendisi ölünce anlamsızdır.

## Yan Etki Engeli

Politikalar arasındaki farkı yaratan tek dosya ölçek kaydıdır. Bu modülün dışa aktarımı
yoktur; çizgeye yalnız çıplak bir içe aktarımla girer ve en üst düzeyinde bir işlev
çağırır. Ad düzeyi çözümlemesi burada tıkanır: canlanacak bir ad yoktur, ama modülün
çalışması bir başkasının davranışını değiştirir.

Sağlaması bir çalıştırmayla yapılır. Aşağıdaki blok, elemenin uygulandığı bir kopya
üretir ve aynı çağrıyı iki çıktı üzerinde koşturur. `sed -i` seçeneği BSD ve GNU
sürümlerinde farklı davranır; yedek uzantısı vererek her ikisinde de aynı sonuç alınır.

```bash
#!/usr/bin/env bash
# Yan etkili modul elenmis bir kopya uretilir, ayni cagri iki cikti uzerinde kosturulur.
rm -rf elenmis && mkdir elenmis && cp kaynak/*.js elenmis/
sed -i.yedek -e '/^import ".\/olcek-kaydi.js";$/d' -e '/from ".\/tahmin.js"/d' elenmis/arac.js
rm -f elenmis/*.yedek elenmis/olcek-kaydi.js elenmis/tahmin.js

OLCUM='{ damga: "2026-02-11T06:00:00Z", sicaklik: -4.2, nem: 72 }'
node --input-type=module -e "
import { olcumSatiri } from './kaynak/bicim.js';
console.log('korunmus:', olcumSatiri(${OLCUM}));
"
node --input-type=module -e "
import { olcumSatiri } from './elenmis/bicim.js';
console.log('elenmis :', olcumSatiri(${OLCUM}));
"
```

```
korunmus: 06:00  -4.2 C / 24.4 F  %72
elenmis : 06:00  -4.2 C  %72
```

İkinci satırda Fahrenhayt sütunu yoktur. Eleme bir hata üretmedi, bir istisna fırlatmadı;
çıktıyı sessizce değiştirdi. Bu, ağaç sarsmanın en tehlikeli kusur biçimidir: derleme
başarılı görünür, testlerin kapsamadığı bir davranış kaybolur.

Bu yüzden araç, yan etki durumu bildirilmemiş bir modülü kendiliğinden düşürmez.
Bilgiyi paketin bildirimindeki `sideEffects` alanı verir ve üç ayarı vardır.

**Alan yoksa** araç temkinli davranır: hiçbir modülü tümüyle düşürmez, yalnız ölü dışa
aktarım bildirimlerini siler. Çıktı 3546 bayttır ve doğrudur, ama tahmin modülü içi
boşalmış hâlde çıktıda kalır.

**Alan `false` ise** yazar, paketteki hiçbir modülün en üst düzeyde iş yapmadığına söz
vermiş olur. Araç ölçek kaydını ve tahmin modülünü düşürür, kayıt modülündeki `kaydet`
adı da ölür. Çıktı 3231 bayta iner — ve yukarıdaki ikinci satırın davranışını üretir. Bu
paket için `false` bir yalandır.

**Alan bir liste ise** yazar hangi dosyaların yan etkili olduğunu tek tek söyler. Ölçek
kaydı korunur, tahmin modülü düşer. Çıktı 3408 bayttır: doğruluğu bozmadan elde edilebilen
en küçük çıktı.

Alan bu yüzden bir ayar değil, bir taahhüttür. Yanlış doldurulduğunda araç yanlış eleme
yapar ve arıza, elemenin yapıldığı yerde değil, kaybolan davranışın kullanıldığı yerde
görünür.

## Elemeyi Durduran Öteki Yazımlar

Yan etki tek engel değildir. Aşağıdaki yazımların hepsi ad düzeyi izlemeyi kırar ve
aracı modülün tamamını tutmaya zorlar.

**Ad alanı nesnesinin bütün olarak dolaştırılması.** `import * as arac from "./arac.js"`
yazımından sonra nesne bir işleve verilirse, hangi adların okunacağı statik olarak
bilinemez. Adlar tek tek içe aktarıldığında bu belirsizlik doğmaz.

**Hesaplanmış erişim.** `arac[secilenAd]` biçiminde bir okuma, çalışma zamanında
belirlenen bir ada karşılık gelir. Böyle bir erişimi gören araç, o modüldeki bütün adları
canlı saymak zorundadır.

**Çağrı tabanlı modül biçimi.** CommonJS'te dışa aktarım nesnesi çalışma zamanında
değiştirilebilir; hangi adın var olduğu değerlendirme sonuna kadar kesin değildir. Statik
ayrıştırmayla elde edilen liste eksik olabileceği için eleme uygulanmaz.

**Değerlendirme anında iş yapan bildirimler.** En üst düzeydeki bir çağrının sonucu bir
sabite bağlanıyorsa, o çağrının yan etkisi olup olmadığı bilinmez. Araçlar bu belirsizliği
kaynağa konan saflık açıklamalarıyla aşar; açıklama da bildirim alanı gibi bir taahhüttür.

**Toplayıcı dosyalar.** Toplayıcının kendisi zararsızdır — örnekteki toplayıcının ölü
yeniden dışa aktarımları düştü. Zararlı olan, toplayıcının yan etkili bir modülü çıplak
içe aktarımla çizgeye sokmasıdır: o tek satır, toplayıcıyı içe aktaran her tüketiciye o
modülü taşır.

Bu liste, kütüphane yazarına dört maddelik somut bir sorumluluk bırakır: en üst düzeyde iş
yapmamak, adları doğrudan dışa aktarmak, yan etkili dosyaları bildirimde listelemek ve
toplayıcıya çıplak içe aktarım koymamak.

## Özet

- Ağaç sarsma çizgeyi `modül#ad` düzeyine indirir; canlılık giriş noktasının dışa
  aktarımlarından başlayıp gövdelerde geçen adlar üzerinden yayılır.
- Yeniden dışa aktarımlar canlılığı ada göre iletir; dinamik içe aktarım hedefinin bütün
  dışa aktarımları temkinli olarak canlı sayılır.
- Dışa aktarımı olmayıp en üst düzeyde iş yapan bir modül ad düzeyi çözümlemesiyle
  elenemez; elenirse arıza sessizdir — örnekte Fahrenhayt sütunu hatasız biçimde kaybolur.
- Paket bildirimindeki yan etki alanı üç sonuç üretir: alan yoksa 3546 bayt ve modül
  düşmez, `false` ise 3231 bayt ve davranış bozulur, doğru liste ise 3408 bayt ve davranış
  korunur.
- Ad alanı nesnesinin dolaştırılması, hesaplanmış erişim, çağrı tabanlı modül biçimi ve
  değerlendirme anında iş yapan bildirimler elemeyi durdurur.

## Sonraki Adım

Çizge şimdiye kadar yalnız betik dosyalarından kuruldu. Oysa bir arayüzün çıktısı yalnız
koddan oluşmaz: ölçüm listesinin biçem dosyası, istasyon simgesi, arayüzün yazı tipi ve
tarayıcıya olduğu gibi kopyalanacak birkaç statik dosya da çıktıya girer. Bunların bir
kısmı koddan içe aktarılır ve çizgenin düğümü hâline gelir; bir kısmı hiç içe aktarılmaz
ama yine de yayımlanır. Sonraki ders bu ikinci akışı ele alır: varlıkların boru hattından
geçirilmesi, küçük olanların gövdeye gömülmesi, büyük olanların ayrı dosya olarak
yazılması ve kaynaktaki adların çıktıdaki adlara bağlanması.
