ESM VE COMMONJS MODÜL SİSTEMLERİNİ KARŞILAŞTIRMAK
Bir projede “import çalışmıyor”, “require ESM dosyasını açmıyor” ya da “package.json değiştirdim her şey dağıldı” gibi cümleler duyduysanız, mesele çoğu zaman modül sistemidir. JavaScript ekosisteminde aynı işi yapan iki farklı yaklaşım uzun süre yan yana yaşadı: CommonJS ve ESM.
CommonJS, Node.js dünyasında yıllarca varsayılan standarttı. ESM (ECMAScript Modules) ise dilin kendi standardı olarak geldi ve tarayıcı tarafında doğal çalışmaya başladı. Günümüzde birçok ekip, projelerinde bu iki yaklaşımı birlikte yönetmek zorunda kalıyor; bu da doğru kararlar almayı kritik hale getiriyor.
Bu yazıda ESM ve CommonJS farklarını; çalışma zamanı davranışlarını, uyumluluk sorunlarını ve pratik geçiş adımlarını ele alacağız. Konuyu uygulamalı öğrenmek için javascript eğitimi içeriğinde örnek repo ve alıştırmalar da bulunuyor.

Modül sistemini seçme nedenlerini netleştirmek
Modül sistemi seçimi sadece sözdizimi tercihi değildir; paket dağıtımı, araç zinciri ve runtime davranışı gibi alanları etkiler. Bu yüzden “hangisi daha yeni?” yerine “hangi ihtiyacı daha iyi çözüyor?” diye düşünmek gerekir.
Tarayıcı ve Node hedefini birlikte değerlendirmek
Tarayıcıda ESM doğal çalışır; script etiketiyle modül yükleyebilirsiniz. Node tarafında ise ESM desteği yerleşmiş olsa da proje ayarları ve dosya uzantıları gibi detaylar önem kazanır. Hedef ortamlar karışık ise seçimi buna göre yapmak gerekir.
Bağımlılık ekosistemini uyumlu tutmak
Kullandığınız paketlerin bir kısmı sadece CommonJS, bir kısmı sadece ESM olabilir. Bu durumda “tek sistem” ideali yerine uyumluluk yönetimi daha gerçekçi olur. Özellikle build araçları ve test koşucuları bu kararı etkiler.
ESM ve CommonJS sözdizimini karşılaştırmak
İlk görünen fark import/export ile require/module.exports biçimidir. Ancak asıl fark, modüllerin yüklenme şekli ve bağlanma davranışında ortaya çıkar.
Import/export ile isimli ve varsayılan dışa aktarmayı kurgulamak
ESM’de isimli export’lar ve default export’lar açık bir sözleşme oluşturur. Bu sözleşme, IDE otomatik tamamlama ve statik analiz açısından avantaj sağlar.
// ESM: named export + default export
export const sum = (a, b) => a + b;
export default function formatPrice(value) {
return new Intl.NumberFormat("tr-TR", { style: "currency", currency: "TRY" }).format(value);
}
// ESM import
import formatPrice, { sum } from "./utils.js";
console.log(sum(2, 3), formatPrice(149.9));Require ve module.exports ile esnek paketlemeyi sürdürmek
CommonJS’te require çalışma zamanında çağrılır ve exports nesnesi üzerinden paylaşım yapılır. Bu yaklaşım daha “dinamik” hissettirir; ama statik analiz gücü ESM kadar yüksek değildir.
// CommonJS: module.exports
const sum = (a, b) => a + b;
function formatPrice(value) {
return new Intl.NumberFormat("tr-TR", { style: "currency", currency: "TRY" }).format(value);
}
module.exports = { sum, formatPrice };
// CommonJS require
const { sum, formatPrice } = require("./utils.cjs");
console.log(sum(2, 3), formatPrice(149.9));Yükleme davranışını ve çalışma zamanını anlamlandırmak
Modül sistemi seçiminde en kritik farklardan biri, yüklemenin ne zaman ve nasıl gerçekleştiğidir. Bu fark; performans, hata tipi ve “niye undefined geldi?” gibi durumları doğrudan etkiler.
ESM’de statik çözümleme ile güveni artırmak
ESM import ifadeleri üst seviyede çalışır ve modül grafiği statik olarak çıkarılabilir. Bu sayede bundler’lar tree-shaking yapmayı daha kolaylaştırır. Ayrıca import’lar blok içinde rastgele çağrılamadığı için yapı daha öngörülebilir olur.
CommonJS’de dinamik require ile kontrol alanı açmak
CommonJS’te require koşula bağlı çalıştırılabilir. Bu bazen performans optimizasyonu gibi görünür; bazen de kontrol edilmesi zor bağımlılıklara dönüşür. Dinamik kullanım “kolay” olsa da bakım maliyeti yaratabilir.
Node.js tarafında uyumluluğu planlamak ve hata azaltmak
Node projelerinde en sık sorun, dosya uzantıları ve paket türü ayarlarından çıkar. Burada küçük bir ayar değişikliği, tüm import zincirini etkileyebilir.
package.json type ayarıyla modül türünü belirlemek
"type": "module" ayarı, .js dosyalarını ESM gibi yorumlatır. Bu ayar yoksa .js çoğunlukla CommonJS gibi davranır. Alternatif olarak .mjs (ESM) ve .cjs (CommonJS) uzantılarıyla dosya bazında ayrım yapabilirsiniz.
Karışık projelerde uzantı stratejisi belirlemek
Tek repo içinde her şeyi bir anda dönüştürmek yerine, modülleri bölerek ilerlemek daha az risk taşır. Örneğin yeni yazdığınız modülleri .mjs ile, eski modülleri .cjs ile yönetebilirsiniz. Böylece geçiş sürecini kontrollü yürütmek mümkün olur.
- Yeni modüller için ESM standardını tercih etmek
- Eski bağımlılıklar CommonJS ise .cjs ile izolasyon sağlamak
- Build aracı ayarlarını modül türüne göre güncellemek
- Test koşucusu uyumluluğunu erken doğrulamak
Geçiş sürecini adım adım yürütmek ve riskleri azaltmak
Geçişte hedef, “her şeyi import’a çevirmek” değil; modül sınırlarını netleştirip paket yapısını sürdürülebilir kılmaktır. Bu, ekipler arası geliştirmede sürprizleri azaltır.
Karşılaşılan tipik hataları sınıflandırmak
En sık görülenler: ESM dosyasını require ile çağırmak, CommonJS çıktısını import ile yanlış okumak ve dosya uzantısını unutmak. Bu hataları “kural seti” haline getirip review sürecine dahil etmek fayda sağlar.
İki dünyayı köprüleyerek ilerlemek
Geçişte bazen köprü çözümler gerekir: CommonJS tarafında dinamik import kullanmak ya da ESM tarafında createRequire ile require çağırmak gibi. Bunlar kalıcı mimari olmaktan çok, geçişi güvenle tamamlamaya yarayan araçlar olarak görülmelidir.

Özet: ESM; standart, statik ve analiz edilebilir bir modül yapısı sunar. CommonJS; Node ekosisteminde köklü ve dinamik bir yaklaşım taşır. Proje hedefinize, bağımlılıklarınıza ve araç zincirinize göre doğru seçimi yaparak geçişi planlamak, modül kaynaklı hataları ciddi biçimde azaltır. Uygulamalı örnekler için javascript eğitimi içeriğine göz atabilirsiniz.


