---
title: CommonJS
source: 'https://academia.sh/tr/kurslar/javascript-ekosistemi/commonjs'
course: 'Modüller, Araçlar ve Ekosistem'
language: tr
updated: '2026-08-17T18:09:45+00:00'
license: 'CC BY-SA 4.0'
---

# 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ı, önceki derste 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. Önceki derste 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. Atamalar bir döngü içinde veya hesaplanmış anahtarlarla yapılıyorsa bu tahmin
tutmaz ve 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.
