Menu

CommonJS vs ES Modules: require ve import Farkı

Node.js projelerinde karşımıza çıkan iki modül sistemi: require ve import arasındaki fark, .cjs ile .mjs uzantıları ve hangisini ne zaman kullanmalı.

Tek Dil, İki Modül Sistemi

JavaScript'in başlangıçta bir modül sistemi yoktu. Node bu boşluğu 2009'da CommonJS (require, module.exports) ile doldurdu ve uzun yıllar boyunca Node kodu böyle yazıldı. Derken 2015'te dilin kendisine standart bir modül sistemi geldi: ES Modules (import, export). Bugün hem tarayıcılar hem de Node bunu destekliyor.

Dolayısıyla vahşi doğada ikisine de rastlarsınız. İşte aynı minik modülün her iki yazımı:

index.js
Output
Click Run to see the output here.
index.js
Output
Click Run to see the output here.

Aynı işlev, iki farklı paketleme. Sayfanın geri kalanı da zaten hangi paketlemenin ne zaman önemli olduğunu ve hangisini seçmen gerektiğini anlatıyor.

Sözdizimi Farkları

require vs import arasındaki günlük farklar bir kartpostala sığacak kadar basit:

// CommonJS
const fs = require("fs");
const { readFile } = require("fs/promises");

module.exports = something;
module.exports.name = value;
exports.name = value;
// ES Modüller
import fs from "fs";
import { readFile } from "fs/promises";

export default something;
export const name = value;
export { name };

require sıradan bir fonksiyon çağrısıdır. import ise bir ifadedir (statement) — yalnızca modülün en üst seviyesinde yer alabilir ve yol mutlaka string literal olmak zorundadır. Bu kısıtlama keyfi değil; ESM'in CommonJS'te yapılamayan şeyleri yapmasını sağlayan şey tam olarak bu.

Asıl Fark: Statik mi, Dinamik mi?

CommonJS, require() çağrısını satır çalıştığı anda değerlendirir. Bunu bir if bloğunun içine koyabilir, yolu çalışma zamanında hesaplayabilir, modülü koşullu olarak yükleyebilirsin:

index.js
Output
Click Run to see the output here.

ES Modules statik çalışır. Motor, herhangi bir kod çalıştırmadan önce tüm import ifadelerini parse eder, bir bağımlılık grafı oluşturur ve her şeyi baştan çözer. Bu yüzden yol (path) düz bir string olmak zorunda ve import yalnızca dosyanın en üstünde yer alabiliyor.

Bunun getirisi şu: araçlar hiçbir kod çalıştırmadan tüm modül grafını görebiliyor. Bundler'lar tree-shaking'i (kullanılmayan export'ları eleme) bu sayede yapıyor, editörler doğru autocomplete'i bu sayede sunuyor, tarayıcı da modülleri paralel olarak bu sayede çekebiliyor.

ESM içinde gerçekten dinamik yükleme gerektiğinde import() devreye girer — fonksiyon gibi çağrılan ve Promise döndüren bir ifadedir:

index.js
Output
Click Run to see the output here.

Node Bir Dosyanın Hangi Sistemi Kullandığına Nasıl Karar Veriyor?

Tek bir Node projesinde her iki modül sistemini de birlikte kullanabilirsiniz. Node, her dosyanın hangi sistemle çalışacağını iki şeye bakarak belirliyor:

  • Dosya uzantısı: .mjs her zaman ESM, .cjs ise her zaman CommonJS olarak ele alınır.
  • En yakın package.json dosyasındaki "type" alanı: "module" ise .js dosyaları ESM kabul edilir; "commonjs" (belirtilmemişse varsayılan değer budur) ise CommonJS olur.
// package.json
{
    "name": "my-app",
    "type": "module"
}

"type": "module" ayarı açıkken, aynı paket içindeki sıradan bir hello.js dosyası import/export kullanır. Yanına bir hello.cjs bıraktığında ise sadece o dosya require kullanır. Bir projenin kademeli olarak ESM'e geçmesi ya da bir kütüphanenin her iki formatı aynı anda dağıtması işte böyle mümkün oluyor.

Yeni başlayanların sık düştüğü tuzak: ESM bir dosyada require ve module.exports diye bir şey yoktur. Kas hafızasıyla uzanıp bunları yazarsan karşına ReferenceError çıkar.

Interop: İki sistemi birlikte kullanmak

Çoğu zaman ESM bir dosyadan CommonJS bir paketi (ya da tam tersini) kullanman gerekecek. Ve kurallar iki yönde de aynı işlemiyor.

ESM dosyadan CommonJS import etmek sorunsuz çalışır. CJS tarafındaki module.exports nesnesi, default export olarak gelir:

index.js
Output
Click Run to see the output here.
// app.mjs
import greet from "./greet.cjs";
console.log(greet("Rosa"));

CommonJS modüllerinden named import kullanımı bazen çalışır — Node, named export'ları statik olarak tespit etmeye çalışır — ancak güvenilir bir sonuç için default export'u alıp destructuring ile parçalamak daha mantıklı:

import pkg from "./utils.cjs";
const { parse, stringify } = pkg;

CommonJS içinden ESM çağırmak ise işin en can sıkıcı yönü. Bir ES modülünü require() ile çağıramazsınız; doğrudan ERR_REQUIRE_ESM hatası fırlatır. Burada kaçış yolu dinamik import(): CJS içinde de çalışır ve size bir Promise döner:

index.js
Output
Click Run to see the output here.

Modern Node (22+), belirli koşullar altında ESM için senkron bir require() desteği ekledi; ama taşınabilir çözüm hâlâ dinamik import().

Bilinmesi Gereken Diğer Davranış Farkları

Söz diziminin ötesinde, iki sistemin zaman zaman canınızı sıkacak birkaç ayrıntıda da anlaşamadığını göreceksiniz:

index.js
Output
Click Run to see the output here.

Birkaç önemli nokta daha:

  • Dosyanın en üst seviyesinde this. CJS tarafında this, doğrudan module.exports'a işaret eder. ESM'de ise undefined'dır. Ayrıca ESM her zaman strict mode'da çalışır.
  • __dirname ve __filename. CJS bunları hazır olarak sunar. ESM'de ise bu değerleri import.meta.url üzerinden kendin türetmen gerekir:
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
  • Import'larda dosya uzantıları. ESM, göreli yollarda uzantıyı zorunlu tutar ("./utils" değil, "./utils.js" yazmalısınız). CJS bu konuda daha esnektir.
  • Canlı bağlantılar (live bindings) vs anlık görüntü. ESM import'ları, dışa aktaran modüldeki değişkenlere canlı referanslardır. CJS ise module.exports'a yükleme anında atanmış olanın bir kopyasını verir. Çoğu kodda farkı hissetmezsiniz ama dairesel bağımlılıklarda (circular dependencies) bu detay kritik olur.

Hangisini Kullanmalı?

Yeni bir proje başlatıyorsanız: ES Modules. package.json dosyasına "type": "module" ekleyin ve geriye dönüp bakmayın. ESM artık dilin standardı; hem tarayıcıda hem Node'da aynı şekilde çalışıyor, top-level await destekliyor ve modern araçlar ESM düşünülerek tasarlanıyor.

CommonJS'te kalmanın mantıklı olduğu durumlar:

  • Mevcut bir CJS kod tabanını sürdürüyorsunuz ve geçişin maliyeti henüz değmez durumda.
  • Çok eski Node sürümlerini veya ESM kullanamayan tüketicileri desteklemesi gereken bir kütüphane yayınlıyorsunuz.
  • Kritik bir bağımlılık yalnızca CJS olarak dağıtılıyor ve interop tarafı karışık. (Artık nadir, ama oluyor.)

Bu durumlarda bile ESM kodunu sürekli okuyor olacaksınız — son birkaç yılda npm'de yayınlanan her şey o yöne gidiyor. İkisini de anlamak artık tercih değil zorunluluk; ama fiilen yazdığınız sistemin deyimlerine hâkim olmak asıl mesele.

Kısa Bir Zihinsel Kontrol Listesi

Yeni bir dosya açtığınızda kendinize şunları sorun:

  • Bu dosya import/export mu kullanıyor, require/module.exports mi? İkisini karıştırmayın.
  • En yakın package.json dosyasındaki "type" alanı ne diyor?
  • Bir paketi import ediyorsanız, o paketin package.json dosyasına bakın — ESM mi, CJS mi, yoksa ikisini birden mi sunuyor?
  • ERR_REQUIRE_ESM hatası aldıysanız, CJS içinden ESM yüklemeye çalışıyorsunuz demektir. Ya dinamik import() kullanın ya da çağıran tarafı ESM'e taşıyın.

Node'da modüllerle ilgili yaşanan kafa karışıklığının yüzde doksanı bu dört maddenin biridir.

Sırada: npm Temelleri

Modüller, kendi kodunuzu dosyalara bölmenin yoludur. Sıradaki adım ise başkalarının yazdığı kodu projenize dahil etmek — npm tam olarak bu iş için var. Paket kurulumu, semver aralıkları ve npm iş akışının günlük hayatta gerçekten kullandığınız kısımlarını ele alacağız.

Sıkça Sorulan Sorular

JavaScript'te require ile import arasındaki fark nedir?

require CommonJS'in modül yükleme yöntemidir: senkron çalışır, çağrıldığı satırda devreye girer ve modülün module.exports'a atadığı ne varsa onu döner. import ise ES Modules sözdizimidir: statiktir, dosyanın en üstüne hoist edilir ve kod çalışmadan önce analiz edilir. Ayrıca this değerinin ne olduğu, dairesel bağımlılıkların nasıl çözüldüğü ve top-level await desteği gibi konularda da ikisi birbirinden ayrışır.

Yeni bir Node projesinde CommonJS mi ES Modules mi kullanmalıyım?

ES Modules tercih edin. package.json dosyasına "type": "module" ekleyip import/export yazın. ESM artık resmi standart; hem tarayıcıda hem Node'da çalışıyor ve top-level await destekliyor. CommonJS ise eski paketlerde ve bazı araçlarda hâlâ karşınıza çıkacak, yani siz yazmasanız bile okumanız gerekecek.

Aynı projede require ve import'u birlikte kullanabilir miyim?

Evet, ama bazı kurallarla. .mjs uzantılı dosyalar veya "type": "module" ayarlı paketler ESM kullanır; .cjs veya "type": "commonjs" ise CJS'tir. ESM tarafında bir CommonJS modülünü import edebilirsiniz — module.exports size default export olarak gelir. Ama CommonJS içinden bir ESM modülünü doğrudan require() edemezsiniz; bunun için Promise dönen dinamik import()'u kullanmanız gerekir.

Coddy ile kodlamayı öğren

BAŞLA