ES modüllerinde (ESM) __dirname ve __filename tanımlı değildir; kullanırsanız ReferenceError alırsınız. Node.js'te bunların doğrudan karşılığı import.meta.dirname ve import.meta.filename özellikleridir. Belgelerdeki YAML kaydına göre ikisi v21.2.0 ve v20.11.0 ile geldi, v24.0.0 ve v22.16.0 ile deneysel olmaktan çıktı. Daha eski sürümleri de desteklemeniz gerekiyorsa aynı sonucu fileURLToPath(import.meta.url) ve path.dirname() ile üretebilirsiniz.
Bu yazıda import.meta nesnesinin Node.js'teki bütün özelliklerini (url, filename, dirname, resolve(), main) çalışan örneklerle ele alıyoruz. Ayrıca dosyaya göre göreli yol üretmenin iki doğru yolunu, URL.pathname kullanmanın neden hatalı olduğunu ve ESM içinde require gerektiğinde createRequire() kullanımını gösteriyoruz. Örnekler adı boşluk ve Türkçe karakter içeren bir klasörde (proje klasörü) çalıştırıldı, çünkü farklar en çok burada ortaya çıkıyor. Örnekler Windows üzerinde Node.js v26.13.0 ile çalıştırıldı; çıktılardaki uzun yerel klasör yolu okunabilirlik için C:\proje olarak kısaltıldı. Linux ve macOS'ta aynı yollar /home/kullanici/proje/... biçiminde görünür.
Neden ESM dosyasında __dirname yok?

CommonJS dosyaları çalışmadan önce Node tarafından bir fonksiyonun içine sarılır ve __dirname ile __filename bu fonksiyonun parametreleri olarak gelir; ayrıntısı require ve module.exports yazısında anlatılıyor. ESM dosyaları bu sarmalayıcıyla çalışmaz. Belgelerin "Differences between ES modules and CommonJS" bölümü bunu açıkça listeler: ESM'de require, exports, module.exports, __filename ve __dirname yoktur. Onların yerine modüle özgü bilgiler import.meta nesnesinde toplanır ve bu nesne yalnızca ES modüllerinde vardır.
// ex3.js (package.json: { "type": "module" })
try {
console.log(__dirname);
} catch (err) {
console.log(err.name + ': ' + err.message);
}
console.log(typeof __filename, typeof require);
// çıktı: ReferenceError: __dirname is not defined
// çıktı: undefined undefinedtypeof operatörü tanımsız bir değişkende hata vermediği için ikinci satır sorunsuz çalışıyor. Bu, aynı kodun hem CommonJS hem ESM'de çalışması gereken nadir durumlarda kullanılabilecek bir kontrol; ama yeni kodda doğrudan import.meta kullanmak çok daha açık. Bir dosyanın neden ESM olarak çalıştığını merak ediyorsanız CommonJS ve ESM farkı yazısındaki kurallara bakın.
import.meta.url, filename ve dirname
import.meta.url modül dosyasının mutlak file: URL'sidir ve belgelere göre tarayıcılardaki tanımla aynıdır. URL olduğu için boşluk %20, Türkçe karakterler de UTF-8 baytlarının yüzde kodlarıyla yazılır. import.meta.filename aynı bilginin işletim sistemine uygun dosya yolu biçimidir (sembolik bağlantılar çözülmüş olarak) ve belgelere göre url.fileURLToPath(import.meta.url) ile aynı sonucu verir. import.meta.dirname ise path.dirname(import.meta.filename) değerine eşittir:
// proje klasörü/ex1.js
import { fileURLToPath } from 'node:url';
import path from 'node:path';
console.log(import.meta.url);
console.log(import.meta.filename);
console.log(import.meta.dirname);
console.log(fileURLToPath(import.meta.url) === import.meta.filename);
console.log(path.dirname(import.meta.filename) === import.meta.dirname);
// çıktı: file:///C:/proje/meta/proje%20klas%C3%B6r%C3%BC/ex1.js
// çıktı: C:\proje\meta\proje klasörü\ex1.js
// çıktı: C:\proje\meta\proje klasörü
// çıktı: true
// çıktı: trueWindows'ta yol ters eğik çizgiyle ve sürücü harfiyle, Linux'ta /home/... biçiminde gelir; URL ise her iki sistemde de ileri eğik çizgi kullanır. Bu yüzden URL'yi elle dosya yoluna çevirmeye çalışmayın, işi fileURLToPath() fonksiyonuna bırakın. URL ile dosya yolu arasındaki dönüşümlerin ayrıntısı URL ve URLSearchParams yazısında var.
// ex6.js
const kod = 'export default [typeof import.meta.filename, typeof import.meta.dirname, import.meta.url.slice(0, 5)]';
const m = await import('data:text/javascript,' + encodeURIComponent(kod));
console.log(m.default);
// çıktı: [ 'undefined', 'undefined', 'data:' ]Dosyaya göre göreli yol üretmenin iki doğru yolu
__dirname en çok, kodun bulunduğu klasördeki bir veri dosyasını, şablonu ya da yapılandırmayı okumak için kullanılır. Bu gereklidir, çünkü './veri.txt' gibi göreli bir yol fs fonksiyonlarına verildiğinde dosyanın değil, process.cwd() ile dönen çalışma klasörünün göreli yolu olarak yorumlanır. Uygulamayı başka bir klasörden başlattığınızda dosya bulunamaz. ESM'de iki doğru yöntem var: path.join(import.meta.dirname, ...) ile bir dosya yolu üretmek ya da new URL('./veri.txt', import.meta.url) ile bir URL nesnesi üretip onu doğrudan fs fonksiyonuna vermek. Belgeler ikinci kalıbı import.meta.url bölümünde örnek olarak veriyor; fs fonksiyonları file: URL nesnelerini kabul eder.
// ex2.js
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
const a = readFileSync(join(import.meta.dirname, 'veri.txt'), 'utf8');
const b = readFileSync(new URL('./veri.txt', import.meta.url), 'utf8');
console.log(a === b, JSON.stringify(a));
const yanlis = new URL('./veri.txt', import.meta.url).pathname;
console.log(yanlis.includes('%20'));
// çıktı: true "merhaba dünya\n"
// çıktı: trueSon iki satır sık yapılan bir hatayı gösteriyor. URL'nin pathname alanı bir dosya yolu değildir: yüzde kodlarını çözmez (boşluk %20 olarak kalır) ve Windows'ta /C:/... gibi başında eğik çizgi olan bir değer döndürür. Belgelerdeki fileURLToPath() örnekleri bu farkı tek tek gösterir. Bu değeri fs fonksiyonuna verdiğinizde dosya bulunamaz:
// ex8.js
import { readFileSync } from 'node:fs';
try {
readFileSync(new URL('./veri.txt', import.meta.url).pathname, 'utf8');
} catch (err) {
console.log(err.code);
}
// çıktı: ENOENTKlasör adınızda boşluk ya da Türkçe karakter yoksa bu hata geliştirme makinenizde hiç görünmeyebilir ve kod ilk kez farklı bir klasöre kurulduğunda ortaya çıkar. Kuralı basit tutun: dosya yolu gerekiyorsa import.meta.dirname ya da fileURLToPath(), URL yeterliyse URL nesnesinin kendisi. path.join() ve path.resolve() farkları için path modülü yazısına bakabilirsiniz.
import.meta.resolve(): require.resolve karşılığı

import.meta.resolve(belirtec), bir belirtecin geçerli modüle göre hangi mutlak URL'ye çözümleneceğini dize olarak döndürür. Belgelere göre ESM'deki tam require.resolve karşılığıdır ve Node'un modül çözümleme kurallarının tamamını, paketlerin "exports" kısıtlamaları dahil, uygular. Kararlılık etiketi Stability: 1.2 - Release candidate. YAML kaydına göre v20.6.0 ve v18.19.0 ile bayraksız hale geldi ve v20.0.0'dan beri Promise yerine senkron olarak dize döndürüyor:
// ex4.js
console.log(import.meta.resolve('./veri.txt').endsWith('/veri.txt'));
console.log(import.meta.resolve('node:fs'));
console.log(import.meta.resolve('fs'));
console.log(import.meta.resolve('./olmayan.js').endsWith('/olmayan.js'));
try {
import.meta.resolve('olmayan-paket');
} catch (err) {
console.log(err.code);
}
// çıktı: true
// çıktı: node:fs
// çıktı: node:fs
// çıktı: true
// çıktı: ERR_MODULE_NOT_FOUNDDavranışın iki yüzüne dikkat edin. Göreli bir file: belirteci için fonksiyon dosyanın gerçekten var olup olmadığına bakmadan URL üretir; belgelere göre v20.6.0'dan beri yerel diskte karşılığı olmayan file: URL'leri için hata fırlatmıyor. Bir paket adı (bare specifier) ise node_modules içinde aranır ve bulunamazsa ERR_MODULE_NOT_FOUND fırlatılır. Belgeler ayrıca bu fonksiyonun senkron dosya sistemi işlemleri yapabileceği için require.resolve gibi performansı etkileyebileceğini ve özel yükleyicilerin (custom loaders) içinde kullanılamayacağını not ediyor. İkinci bir parent argümanı alan standart dışı biçim yalnızca --experimental-import-meta-resolve bayrağıyla açılıyor.
import.meta.main: dosya doğrudan mı çalıştırıldı?
CommonJS'te bir dosyanın node dosya.js ile doğrudan çalıştırılıp çalıştırılmadığını require.main === module ile anlarsınız. ESM'deki karşılığı import.meta.main özelliğidir: modül işlemin giriş noktasıysa true, başka bir modül tarafından import edildiyse false olur. Belgelere göre v24.2.0 ve v22.18.0 ile geldi ve Stability: 1.0 - Early development olarak işaretli; yani erken aşamada ve değişebilir. Tipik kullanımı, bir dosyayı hem kütüphane hem de küçük bir komut satırı aracı olarak kullanmaktır:
// arac.js
export function selamla() {
return 'selam';
}
console.log('arac.js import.meta.main:', import.meta.main);
if (import.meta.main) {
console.log(selamla());
}
// ex5.js
import { selamla } from './arac.js';
console.log('ex5.js import.meta.main:', import.meta.main, selamla());
// node arac.js
// çıktı: arac.js import.meta.main: true
// çıktı: selam
// node ex5.js
// çıktı: arac.js import.meta.main: false
// çıktı: ex5.js import.meta.main: true selamESM içinde require gerektiğinde: createRequire
Bazı durumlarda ESM dosyasından CommonJS'e özgü bir mekanizmaya ihtiyaç duyarsınız: belgelere göre yerel eklentiler (addon) ESM import'u ile yüklenemez, ayrıca require.resolve ya da senkron JSON okuma gerekebilir. node:module modülündeki createRequire(dosyaAdi) fonksiyonu, verdiğiniz dosyanın konumuna göre çözümleme yapan bir require fonksiyonu üretir. Argüman olarak bir file: URL'si, URL nesnesi veya mutlak yol kabul eder; en yaygın kullanım import.meta.url vermektir:
// eski.cjs
console.log(typeof __dirname, require('node:path').basename(__filename));
// ex7.js
import { createRequire } from 'node:module';
import { join } from 'node:path';
const require = createRequire(import.meta.url);
require('./eski.cjs');
console.log(require.resolve('./veri.txt') === join(import.meta.dirname, 'veri.txt'));
// çıktı: string eski.cjs
// çıktı: true.cjs dosyasının kendi içinde __dirname ve __filename normal şekilde çalışıyor; çünkü o dosya CommonJS sarmalayıcısıyla yükleniyor. Belgeler, birden fazla ortamda çalışması gereken ESM kodları için process.getBuiltinModule() fonksiyonunu da gösteriyor: globalThis.process?.getBuiltinModule varsa yerleşik modülü senkron alıp oradan createRequire üretebilirsiniz.
Mini örnek: kendi klasöründeki şablonları okuyan modül
Öğrendiklerimizi tek bir dosyada birleştirelim. Aşağıdaki modül, yanındaki sablonlar klasöründe duran metin şablonlarını listeliyor ve {ad} gibi yer tutucuları dolduruyor. Klasör yolu import.meta.dirname ile üretildiği için modülü hangi çalışma klasöründen başlatırsanız başlatın doğru dosyalar bulunuyor. Dosya başka bir modülden import edildiğinde yalnızca iki fonksiyonu sunuyor; doğrudan çalıştırıldığında ise import.meta.main sayesinde küçük bir deneme çıktısı üretiyor. Bunu kanıtlamak için betiği bir üst klasörden çalıştırdık; ilk satırdaki false, çalışma klasörünün kod klasöründen farklı olduğunu gösteriyor:
// proje klasörü/ex9.js
import { readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
const SABLON = join(import.meta.dirname, 'sablonlar');
export function sablonlar() {
return readdirSync(SABLON).filter((f) => f.endsWith('.txt')).sort();
}
export function doldur(ad, veri) {
const metin = readFileSync(join(SABLON, ad), 'utf8');
return metin.replace(/\{(\w+)\}/g, (_, anahtar) => veri[anahtar] ?? '');
}
if (import.meta.main) {
console.log(process.cwd() === import.meta.dirname);
console.log(sablonlar());
console.log(doldur('hosgeldin.txt', { ad: 'Zeynep' }));
}cd ..
node "proje klasörü/ex9.js"
# çıktı: false
# çıktı: [ 'hosgeldin.txt', 'sifre.txt' ]
# çıktı: Merhaba Zeynep, hesabınız hazır.Aynı modülde join(import.meta.dirname, ...) yerine readdirSync('./sablonlar') yazsaydık, üst klasörden çalıştırınca ENOENT alırdık; çünkü göreli yol çalışma klasörüne göre çözülürdü. Gerçek bir uygulamada bu fark, betiğin bir servis yöneticisi ya da zamanlanmış görev tarafından farklı bir çalışma klasöründen başlatıldığı anda ortaya çıkar. Şablon adı kullanıcıdan geliyorsa ../ ile klasör dışına çıkılmasını engellemek için ayrıca bir yol denetimi eklemeniz gerekir; bunun bir örneği path modülü yazısındaki güvenli yol fonksiyonunda var. Dosya okumanın diğer biçimleri için de dosya okuma rehberine bakabilirsiniz.
CommonJS ve ESM karşılıkları tablosu

| CommonJS | ESM | Not |
|---|---|---|
| __filename | import.meta.filename | Yalnızca file: modüllerde; v21.2.0 / v20.11.0 |
| __dirname | import.meta.dirname | Yalnızca file: modüllerde; v21.2.0 / v20.11.0 |
| pathToFileURL(__filename) | import.meta.url | Her ESM modülünde var |
| require.resolve(x) | import.meta.resolve(x) | Stability 1.2, dize döndürür |
| require.main === module | import.meta.main | Stability 1.0, v24.2.0 / v22.18.0 |
| require(x) | import / createRequire(import.meta.url) | createRequire v12.2.0 |
Sık yapılan hatalar
- İnternette sık paylaşılan
const __dirname = ...satırını.jsdosyasında belirsiz modda kullanmak: Belgelere göre CommonJS sarmalayıcı değişkenleriniconstile yeniden tanımlamak ESM sözdizimi sayılır ve dosya sözdizimi tespitiyle ESM'e çevrilir."type"alanını açıkça yazın, güncel sürümlerde ise doğrudanimport.meta.dirnamekullanın. new URL(...).pathnamedeğerini dosya yolu sanmak: Yüzde kodları çözülmez, Windows'ta sürücü harfinin önünde/kalır veENOENTalırsınız.- Göreli yolu
fsfonksiyonuna doğrudan vermek:'./veri.txt'çalışma klasörüne göre çözülür, dosyaya göre değil. import.meta.resolve()sonucunu dosyanın varlık kanıtı saymak: Görelifile:belirteçleri için dosya yoksa da URL döner; varlığı ayrıca kontrol edin.- URL'yi elle yola çevirmek:
import.meta.url.replace('file://', '')gibi işlemler Windows'ta ve özel karakterli yollarda bozulur;fileURLToPath()kullanın.
Sık Sorulan Sorular
import.meta.dirname hangi Node sürümünde kullanılabilir?
Belgelerdeki YAML kaydına göre v21.2.0 ve v20.11.0 ile eklendi; v24.0.0 ve v22.16.0 ile deneysel etiketi kaldırıldı. Daha eski bir sürümü desteklemeniz gerekiyorsa path.dirname(fileURLToPath(import.meta.url)) aynı değeri verir.
TypeScript ile import.meta.dirname kullanabilir miyim?
Node çalışma zamanı açısından fark yoktur; kod ESM olarak çalışıyorsa özellik vardır. Tip tanımları ve modül ayarları için TypeScript ile Node.js yazısına bakın.
process.cwd() ile import.meta.dirname arasındaki fark nedir?
process.cwd() işlemin başlatıldığı çalışma klasörünü verir ve process.chdir() ile değişebilir; import.meta.dirname ise kod dosyasının bulunduğu klasördür ve sabittir. Kullanıcının verdiği göreli yollar için process.cwd(), uygulamanızla gelen dosyalar için import.meta.dirname doğru tercihtir. process.argv ve çıkış kodları yazısı cwd konusunu da ele alıyor.
Bir eklentiyi dosyaya göre göreli yoldan dinamik olarak nasıl import ederim?
Dinamik import() göreli bir dizeyi zaten içinde bulunduğu modüle göre çözer; ama adı çalışma anında oluşturulan yollar için new URL() ile mutlak bir URL üretmek niyeti daha açık gösterir ve aynı URL'yi başka API'lere de verebilmenizi sağlar. Aşağıdaki örneği diskin kök klasöründen çalıştırdık; eklenti yine modülün yanındaki klasörden bulundu:
// ex10.js
const ad = 'pdf';
const m = await import(new URL(`./eklentiler/${ad}.js`, import.meta.url));
console.log(m.default);
// çıktı: pdf eklentisi yüklendiEklenti adı dışarıdan geliyorsa yalnızca izin verdiğiniz adları kabul eden bir liste tutun; aksi halde kullanıcı ../ içeren bir adla beklemediğiniz bir modülü yükletebilir. Belgelerin hatırlattığı bir ayrıntı daha: ESM modülleri URL olarak önbelleğe alındığı için aynı dosyayı farklı sorgu dizesiyle (?v=2) import etmek onu yeniden yükler.
import.meta.url Windows ve Linux arasında farklı mı?
Biçim aynıdır: her iki sistemde de file:/// ile başlar ve ileri eğik çizgi kullanır. Windows'ta sürücü harfi URL'nin içinde yer alır (file:///C:/...). Fark dosya yoluna çevirdiğinizde ortaya çıkar; bu yüzden dönüşümü her zaman fileURLToPath() ile yapın.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Node.js öğrenme rehberi · CommonJS ve ESM farkı · path modülü · URL ve URLSearchParams · Node.js dosya okuma · JavaScript ES modülleri · Node.js nedir?