Node.js çalışma zamanı package.json dosyasındaki yalnızca beş alanı kullanır: "name", "main", "type", "exports" ve "imports". "type" alanı .js dosyalarının CommonJS mi ESM mi sayılacağını, "main" ve "exports" paketin dışarıya açılan giriş noktalarını, "imports" ise yalnızca paketin kendi içinden kullanılabilen ve # ile başlayan özel kısa yolları tanımlar. "exports" tanımlandığında orada listelenmeyen hiçbir dosya paket adı üzerinden içe aktarılamaz.
Bu yazıda bu alanları çalışan bir örnek paketle anlatıyoruz: koşullu dışa aktarımlar (import, require, node, default), alt yollar ve * desenleri, null ile gizleme, --conditions bayrağı, # takma adları ve paketin kendi adıyla kendini içe aktarması. npm'in kullandığı dependencies, scripts gibi diğer alanlar Node.js tarafından yok sayılır; onlar npm belgelerinin konusudur. Örnekler Node.js v26.13.0 ile çalıştırıldı.
Node.js hangi package.json alanlarını okur?

| Alan | Ne işe yarar | Not |
|---|---|---|
| "name" | Paketin adı; exports ile birlikte kendini adıyla içe aktarmayı sağlar | npm kayıt kuralları npm belgelerinde |
| "main" | Paketin tek giriş noktası (node_modules araması ve klasör require) | exports varsa ondan sonra gelir |
| "type" | .js dosyalarının modül türü: "module" veya "commonjs" | Yoksa .js dosyaları CommonJS (ya da sözdizimi tespiti) |
| "exports" | Birden fazla giriş noktası, koşullar, kapsülleme | v12.7.0 ile geldi |
| "imports" | Paket içi özel # takma adları | v14.6.0 ve v12.19.0 ile geldi |
"type" alanının kuralları ve sözdizimi tespiti CommonJS ve ESM farkı yazısında ayrıntılı anlatılıyor. Burada odak, bir paketin dışarıya ne sunduğu ve içeride neye nasıl eriştiği.
"main" ile "exports" arasındaki fark
"main" Node'un bütün sürümlerinde desteklenir ama yetisi sınırlıdır: yalnızca ana giriş noktasını tanımlar ve paketin geri kalan dosyalarını korumaz. Aşağıdaki örnekte yalnızca "main" alanı olan bir pakette hem ana giriş hem de paketin içindeki rastgele bir dosya sorunsuz yükleniyor. Aynı örnek, Node'un "engines" alanını çalışma zamanında denetlemediğini de gösteriyor; kendi package.json dosyamızda "node": ">=99" yazmasına rağmen kod v26.13.0 üzerinde çalıştı.
{ "name": "eski-lib", "main": "./lib/ana.js" }// pkg2/package.json -> { "type": "commonjs", "engines": { "node": ">=99" } }
// pkg2/ex.js
console.log(require('eski-lib'));
console.log(require('eski-lib/lib/ic'));
console.log(process.version);
// çıktı: main ile yüklendi
// çıktı: derin yol da açık
// çıktı: v26.13.0"exports" ise belgelerin deyişiyle "main" alanının modern alternatifidir: birden fazla giriş noktası tanımlamayı, ortama göre farklı dosya seçmeyi ve "exports" içinde tanımlanmayan her şeyi dışarıya kapatmayı sağlar. İki alan birlikte varsa destekleyen sürümlerde "exports" önceliklidir. Yeni paketler için belgelerin önerisi "exports" kullanmak; eski araçlarla uyum gerekiyorsa aynı dosyayı gösteren bir "main" alanını yanına eklemek.
Örnek paket: koşullu dışa aktarımlar ve alt yollar

Aşağıdaki selam-lib paketi node_modules içinde duruyor ve dışa aktarımlarının hepsi tek bir package.json dosyasında tanımlı. Ana giriş noktası (".") üç koşula ayrılmış; ayrıca bir sabit alt yol, bir desen, desenin içinden gizlenmiş bir klasör ve package.json dosyasının kendisi dışa aktarılıyor:
{
"name": "selam-lib",
"type": "module",
"main": "./eski-giris.cjs",
"exports": {
".": {
"development": "./src/gelistirme.js",
"import": "./src/index.js",
"require": "./src/index.cjs"
},
"./ozellik": "./src/ozellik.js",
"./araclar/*": "./src/araclar/*.js",
"./araclar/gizli/*": null,
"./package.json": "./package.json"
}
}import ve require koşulları
Koşullu dışa aktarımlar, aynı belirtecin nasıl istendiğine göre farklı dosyaya çözümlenmesini sağlar. Node.js çekirdeğinin tanıdığı koşullar, belgelerdeki sırayla, en özelden en genele şunlardır: "node-addons", "node", "import", "require", "module-sync" ve "default". "import" paket import veya import() ile yüklendiğinde, "require" ise require() ile yüklendiğinde eşleşir ve ikisi her zaman birbirini dışlar. "default" her zaman eşleşen genel yedektir ve en sona yazılmalıdır.
// pkg/ex1.js (pkg/package.json içinde "type": "module")
import { kaynak } from 'selam-lib';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
console.log(kaynak);
console.log(require('selam-lib').kaynak);
// çıktı: import koşulu: src/index.js
// çıktı: require koşulu: src/index.cjsPaketin "main" alanı ./eski-giris.cjs dosyasını gösterdiği halde iki yükleme de ona hiç uğramadı; "exports" varken "main" yalnızca "exports" alanını tanımayan eski sürümler ve araçlar için anlam taşır.
Koşulların sırası önemlidir
Belgelere göre "exports" nesnesinde anahtar sırası anlamlıdır: eşleştirme sırasında önce yazılan koşul önceliklidir. Örnek paketimizde "development" koşulu en üstte. Bu, Node'un tanıdığı bir koşul değil; topluluk tanımları arasında yer alıyor ve yalnızca --conditions (kısa biçimi -C) bayrağıyla etkinleşiyor. Bayrakla çalıştırınca hem import hem de require tarafı geliştirme dosyasını alıyor, çünkü "development" sırada önce geliyor:
# ex4.js: import { kaynak } from 'selam-lib'; console.log(kaynak);
node ex4.js
# çıktı: import koşulu: src/index.js
node --conditions=development ex4.js
# çıktı: development koşulu: src/gelistirme.js
node -C development ex1.js
# çıktı: development koşulu: src/gelistirme.js
# çıktı: development koşulu: src/gelistirme.jsBelgeler özel koşul adlarına birkaç kısıtlama getiriyor: en az bir karakter içermeli, nokta ile başlamamalı, virgül içermemeli ve "10" gibi tam sayı bir anahtar olmamalı. Uyumluluk için yalnızca harf ve rakam, gerekiyorsa ayırıcı olarak :, - veya = kullanılması öneriliyor. Topluluk tanımları listesinde "types" (tip sistemleri için, her zaman ilk sırada), "browser", "development" ve "production" bulunuyor; son ikisi birbirini dışlamalı. Ortam dallarında her zaman bir "default" koşulu eklemek, bilinmeyen çalışma ortamlarının da paketi kullanabilmesini sağlar.
Alt yollar, desenler ve null ile gizleme
"." dışındaki anahtarlar alt yol dışa aktarımlarıdır (subpath exports). "./ozellik" gibi sabit bir anahtar tek bir dosyayı açar. "./araclar/*" gibi bir desen ise * yerine gelen metni hedefteki her * yerine koyar. Belgeler bunun yalnızca bir metin değiştirme olduğunu, * değerinin / içerebileceğini ve dosya uzantısına özel bir işlem yapılmadığını vurguluyor. Bir desenin altındaki klasörü dışarıya kapatmak için hedef olarak null verilir:
// pkg/ex2.js
import ozellik from 'selam-lib/ozellik';
import tarih from 'selam-lib/araclar/tarih';
import pkg from 'selam-lib/package.json' with { type: 'json' };
console.log(ozellik, '|', tarih, '|', pkg.name);
for (const yol of ['selam-lib/src/ic.js', 'selam-lib/araclar/gizli/anahtar']) {
try {
await import(yol);
} catch (err) {
console.log(err.code);
}
}
// çıktı: ozellik alt yolu | araclar/tarih | selam-lib
// çıktı: ERR_PACKAGE_PATH_NOT_EXPORTED
// çıktı: ERR_PACKAGE_PATH_NOT_EXPORTEDPaketin içinde gerçekten var olan src/ic.js dosyasına erişim engellendi, çünkü "exports" içinde yok. Hatanın mesajı ne olduğunu açıkça söylüyor: Package subpath './src/ic.js' is not defined by "exports" in ...selam-lib\package.json. Belgeler bu kapsüllemenin güçlü bir koruma olmadığını da not ediyor: node_modules/paket/src/ic.js dosyasını mutlak yoluyla isteyen kod onu yine yükleyebilir. Amaç güvenlik değil, paketin herkese açık arayüzünü netleştirmek.
"./package.json": "./package.json" satırı da bilinçli. Learn rehberi, "exports" kullanırken bu satırı eklemenin genellikle iyi bir fikir olduğunu, böylece tüketicilerin paket bilgilerini içe aktarabileceğini söylüyor. Bu satır olmasaydı yukarıdaki JSON import'u da ERR_PACKAGE_PATH_NOT_EXPORTED verirdi.
Hedef yollarının kuralları
- Tüm hedefler
./ile başlayan göreli URL olmalı;/dist/main.js,../ortak/util.jsveyafile:///ile başlayan hedefler geçersizdir. - Hedef, paketin kök klasörünün dışına çıkamaz; ilk
./sonrasında.,..veyanode_modulesparçaları (ve bunların URL kodlu karşılıkları) genel olarak yasaktır. - Belgeler alt yolları ya uzantılı (
paket/alt.js) ya da uzantısız (paket/alt) olarak, ama tek bir biçimde sunmayı öneriyor; böylece her modül için tek bir belirteç olur. - Yalnızca
"."dışa aktarılıyorsa kısa biçim kullanılabilir:"exports": "./index.js".
"imports" alanı ve # takma adları
"imports" alanı, yalnızca paketin kendi dosyalarından kullanılabilen özel eşlemeler tanımlar. Anahtarlar her zaman # ile başlamak zorundadır; bu, onları dış paket adlarından ayırır. Belgelerdeki YAML kaydına göre v25.4.0 ve v24.14.0 sürümlerinden beri #/ ile başlayan anahtarlara da izin veriliyor. Desen ve koşul kuralları "exports" ile aynıdır; tek fark, "imports" hedeflerinin dış bir paketi de gösterebilmesidir. Uzun ../../../ zincirlerinden kurtulmanın Node'un kendi mekanizması budur:
{
"name": "benim-uygulamam",
"type": "module",
"engines": { "node": ">=22" },
"exports": {
".": "./index.js"
},
"imports": {
"#ayar": "./src/ayar.js",
"#yardimci/*": "./src/yardimci/*.js"
}
}// pkg/ex3.js
import ayar from '#ayar';
import { buyut } from '#yardimci/metin';
import { ad } from 'benim-uygulamam';
console.log(ayar.port, buyut('istanbul'), '|', ad);
try {
await import('#olmayan');
} catch (err) {
console.log(err.code);
}
// çıktı: 8080 İSTANBUL | benim-uygulamam kök modülü
// çıktı: ERR_PACKAGE_IMPORT_NOT_DEFINEDÜçüncü import satırı paketin kendi adıyla kendini içe aktarıyor (self-referencing). Belgelere göre bu yalnızca package.json dosyasında "exports" alanı varken çalışır ve yalnızca "exports" içinde izin verilen yollara erişir; hem import hem de require ile ve kapsamlı (scoped, @kurum/paket) paket adlarıyla da kullanılabilir. Tanımlanmamış bir # adını istemek ise ERR_PACKAGE_IMPORT_NOT_DEFINED verir.
"engines" alanı ve Node sürüm uyumu
"engines" alanı Node'un okuduğu alanlar arasında yok; yukarıdaki örnekte gördüğünüz gibi çalışma zamanında hiçbir denetim yapılmıyor. Node.js Learn rehberi bu alanı yine de öneriyor: paketin hangi Node sürümleriyle uyumlu olduğunu hem insanlara hem araçlara bildiriyor ve kullanılan paket yöneticisine bağlı olarak uyumsuz bir sürümde kurulumun hata vermesini sağlayabiliyor. Bu davranış paket yöneticisine ait olduğu için ayrıntısını npm belgelerinde bulursunuz. Kurulu Node sürümünüzü öğrenmek ve yönetmek için Node.js kurulumu ve sürüm yöneticileri yazısına bakabilirsiniz.
Hangi package.json yapısını seçmeli?

Belgelerdeki önerileri bir araya getirince çoğu proje için şu tablo çıkıyor. Uygulama yazıyorsanız (yayınlamayacağınız bir proje), "type" ve gerekiyorsa "imports" çoğu zaman yeterli. Kütüphane yayınlıyorsanız "exports" ile açık bir arayüz tanımlayın ve tek bir biçim seçin:
| Senaryo | Önerilen alanlar |
|---|---|
| Yalnızca ESM uygulama | "type": "module", isteğe bağlı "imports" |
| CommonJS uygulama | "type": "commonjs" (açıkça), isteğe bağlı "imports" |
| Tek biçimli kütüphane | "name", "type", "exports" (".", "./package.json") |
| Eski araçlarla uyumlu kütüphane | "exports" ile birlikte aynı dosyayı gösteren "main" |
| Node ve diğer ortamlar için farklı giriş | "exports" içinde "node" ve en sonda "default" |
Learn rehberinin bir notu da şu: "exports" paketin iç dosyalarına dışarıdan erişimi engellediği için önerilir; bu korumaya ihtiyacınız yoksa "main" daha basittir ve sizin için daha iyi bir seçenek olabilir.
Sık yapılan hatalar
"exports"ekleyip"./package.json"satırını unutmak: Paket bilgisini okuyan araçlar ve tüketicilerERR_PACKAGE_PATH_NOT_EXPORTEDalır.- Koşulları yanlış sıralamak:
"default"en üste yazılırsa her zaman o eşleşir ve altındaki koşullara hiç bakılmaz. "require"koşulunu CommonJS dosyası sanmak: Koşul yükleme biçimini anlatır; hedef dosyanın türünü uzantı ve"type"belirler."imports"anahtarını#olmadan yazmak: Belgelere göre anahtarlar#ile başlamak zorunda.- Hedefte
../kullanmak: Hedefler paket kökünün dışına çıkamaz ve./ile başlamalıdır. "engines"alanının Node'u durduracağını sanmak: Node bu alanı okumaz; denetim paket yöneticisine aittir.
Sık Sorulan Sorular
ERR_PACKAGE_PATH_NOT_EXPORTED hatası ne anlama gelir?
İçe aktarmaya çalıştığınız alt yol, paketin "exports" alanında tanımlı değil. Paketin belgelerinde önerilen giriş noktasını kullanın; kendi paketinizse ilgili yolu "exports" içine ekleyin.
"imports" ile tsconfig paths aynı şey mi?
Hayır. "imports" Node.js çalışma zamanının kendi çözümleme mekanizmasıdır ve # ile başlar. TypeScript paths ayarı yalnızca tip denetimi ve derleme sırasında kullanılır.
Bir paketin package.json dosyasını kodda nasıl bulurum?
node:module modülündeki findPackageJSON() fonksiyonu bunu yapar, ama belgelerde Stability: 1.1 - Active Development olarak işaretli. Belgeler bu fonksiyonla modül türünü belirlemeye çalışmamanızı da uyarıyor; çünkü modül türünü "type" alanından önce dosya uzantısı ve yükleyici kancaları belirler.
Hem import hem require destekleyen bir paket yazmam gerekiyor mu?
Çoğu zaman hayır. Güncel Node sürümlerinde require() senkron ESM modüllerini yükleyebildiği için Learn rehberi tek biçimde yayınlamayı öneriyor. İki biçim gerçekten gerekiyorsa rehberdeki "node" ve "default" anahtarlı yapı, Node'un her zaman aynı dosyayı seçmesini sağlayarak çift yükleme riskini ortadan kaldırıyor.
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ı · require ve module.exports · __dirname ve import.meta · Node.js hata kodları · TypeScript modül çözümleme · Node.js nedir?