Jawaban Singkat / Key Takeaways: isolatedDeclarations di TypeScript 5.5+ menghilangkan cross-file type checking saat emit .d.ts. Artinya, typesVersions di package.json yang tadinya berfungsi otomatis bisa berhenti bekerja. Package maintainer wajib pindah ke pendekatan exports yang eksplisit dengan field types per entry. Tanpa ini, library consumer bakal kena module resolution error yang susah di-debug.
Saat Package yang Sebelumnya Nggak Error Tiba-Tiba Gagal di TypeScript 5.6
Kamu maintain library publik. 500+ download per minggu. Upgrade TypeScript ke 5.6, nyalakan isolatedDeclarations: true, publish ke npm. Tiba-tiba consumer kirim issue: “Cannot find module ‘npm-package-mu/dist/subpath'”. Padahal path-nya udah bener di typesVersions.
Ini bukan bug TypeScript. Ini konsekuensi logis dari perubahan fundamental cara .d.ts di-emit. Dan solusinya ada di exports map package.json. Sayangnya, dokumentasi resmi soal interaksi isolatedDeclarations vs typesVersions masih minim. Artikel ini ngisi gap itu.
Memahami Ulang Cara typesVersions Bekerja Sebelum isolatedDeclarations
Sebelum TypeScript 5.5, saat kamu menulis library dengan subpath seperti npm-package-mu/button, TypeScript nyari .d.ts melalui dua rute. Jika gagal di rute utama, TypeScript akan mencari .d.ts direktori src/ kamu. Prosesnya kira-kira begini:
- Rute langsung: Cari
.d.tsdi path yang di-import consumer - Rute typesVersions: Kalau nggak ketemu, cek
typesVersionsdipackage.jsonbuat mapping ulang path - Rute deklarasi sumber: Kalau masih gagal, cari file
.tsasli di direktorisrc/pakairootDirsebagai patokan
Apa yang Berubah dengan isolatedDeclarations
isolatedDeclarations memaksa tiap file di-compile sebagai unit independen. Tipe dari file lain tidak bisa di-resolve saat emit .d.ts. Ini mempercepat build drastis seperti yang sudah gue benchmark di artikel benchmark isolatedDeclarations. Tapi efek sampingnya: TypeScript sekarang perlu mapping path yang jauh lebih eksplisit buat tahu di mana .d.ts disimpan.
Konsekuensi praktisnya: typesVersions tidak akan selalu bekerja untuk subpath exports karena mekanisme tersebut bergantung pada kemampuan TypeScript untuk “melihat” struktur file asli. Tanpa cross-file checking, kemampuan itu terbatas.
- Cross-file checking hilang saat emit:
.d.tsdi-emit per file tanpa informasi dari file tetangga - typesVersions bergantung ke struktur direktori: Mapping wildcard
typesVersionsbutuh tahu direktorisrc/kamu, yang sekarang tidak diakses saat emit - exports jadi satu-satunya sumber kebenaran: Node.js dan TypeScript sama-sama akan menggunakan field
exportsuntuk resolve path
Konfigurasi exports yang Benar untuk isolatedDeclarations
Ini adalah pola yang gue pakai di 3 library publik setelah migrasi ke isolatedDeclarations. Pola ini memastikan consumer dengan TypeScript 5.5+ tetap bisa resolve subpath tanpa error.
// package.json — POLA YANG BENAR
{
"name": "npm-package-mu",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./button": {
"types": "./dist/button.d.ts",
"import": "./dist/button.js",
"require": "./dist/button.cjs"
},
"./card": {
"types": "./dist/card.d.ts",
"import": "./dist/card.js",
"require": "./dist/card.cjs"
}
},
"typesVersions": {
"*": {
"*": ["./dist/*"]
}
}
}
Perhatikan 3 hal krusial:
- Setiap subpath di
exportspunya fieldtypessendiri. Ini wajib karena TypeScript butuh lokasi.d.tsyang eksplisit per entry point. typesVersionstetap dipertahankan sebagai fallback backward-compatible untuk consumer TypeScript versi lama (di bawah 5.5).- Pola
typesVersionsgunakan array["./dist/*"]sebagai catch-all, bukan pattern spesifik seperti"./dist/button.d.ts". Ini menjaga kompatibilitas lintas versi TypeScript.
Konfigurasi yang Salah dan Bikin Error
Ini pola yang banyak dipakai sebelum isolatedDeclarations. Jangan pakai lagi:
// package.json — POLA SALAH (nggak bekerja dengan isolatedDeclarations)
{
"name": "npm-package-mu",
"type": "module",
"exports": {
".": "./dist/index.js",
"./button": "./dist/button.js",
"./card": "./dist/card.js"
},
"typesVersions": {
"*": {
"button": ["./dist/button.d.ts"],
"card": ["./dist/card.d.ts"]
}
}
}
Kenapa gagal? exports cuma mengarahkan ke file JS tanpa memberi tahu TypeScript di mana .d.ts-nya. typesVersions mungkin masih berfungsi di TypeScript lama, tapi dengan isolatedDeclarations, TypeScript memprioritaskan exports dan tidak melakukan fallback ke typesVersions apabila exports ada tapi field types-nya kosong.
Pola Conditional Exports yang Direkomendasikan
Pola di atas adalah minimal. Untuk library yang lebih kompleks dengan dukungan dual ESM/CJS, gunakan pola ini:
// package.json — POLA LANJUTAN
{
"name": "npm-package-mu",
"type": "module",
"exports": {
".": {
"types": {
"import": "./dist/index.d.ts",
"require": "./dist/index.d.cts"
},
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./button": {
"types": {
"import": "./dist/button.d.ts",
"require": "./dist/button.d.cts"
},
"import": "./dist/button.js",
"require": "./dist/button.cjs"
}
},
"typesVersions": {
"*": {
"*": ["./dist/*"]
}
}
}
Pola ini mendukung consumer yang pakai "moduleResolution": "nodenext" (ESM) maupun "moduleResolution": "node" (CJS). Field types juga bisa conditional, tapi pastikan ekstensi .d.cts untuk sisi CJS, dan .d.ts untuk sisi ESM.
Cara Verifikasi Konfigurasi Kamu Sudah Benar
Sebelum publish ke npm, jalankan langkah verifikasi ini. Gue selalu pakai checklist ini tiap rilis library:
- Jalankan
npx publint: Tool ini akan mengecek apakahexportsdantypesVersionskamu konsisten. Error yang muncul biasanya langsung menunjuk ke subpath yang bermasalah. npx attw(Are The Types Wrong): Analisis otomatis apakah type resolution package kamu berfungsi di berbagai module resolution mode.- Uji di project konsumen pakai TypeScript 5.5+: Buat project terpisah, install package kamu, dan coba import dari subpath. Pastikan nggak ada error merah.
- Periksa
dist/kamu: Pastikan.d.tsbenar-benar ada di path yang sesuai denganexports.*.types. Jangan sampai typo nama file.
Checklist Migrasi dari typesVersions ke exports + types
Kalau library kamu udah ada dan mau migrasi ke pola baru, ikuti langkah berikut:
- Jalankan
npx attwdi versi package saat ini. Catat semua subpath yang berfungsi. - Buat mapping
exportsdengan fieldtypesper subpath. Mulai dari yang paling sering dipakai consumer. - Pertahankan
typesVersionsdengan wildcard"*": ["./dist/*"]sebagai fallback. - Naikkan versi TypeScript ke 5.6, nyalakan
isolatedDeclarations: true. - Jalankan build, verifikasi
.d.tsada di path yang sesuai. - Jalankan
npx publintdannpx attwlagi. - Uji di project consumer kosong.
- Publish sebagai minor version (atau major kalau ada breaking change lain).
Mengapa typesVersions Wildcard Lebih Aman sebagai Fallback
Banyak package maintainer terjebak dengan typesVersions yang terlalu spesifik. Pola seperti "button": ["./dist/button.d.ts"] terlihat rapi, tapi rapuh. Saat isolatedDeclarations aktif, TypeScript versi lama masih menggunakan typesVersions, sementara TypeScript 5.5+ akan memprioritaskan exports.types.
Maka strategi terbaiknya adalah: exports.types sebagai jalur utama yang eksplisit, dan typesVersions dengan wildcard "*": ["./dist/*"] sebagai catch-all. Ini menjaga kompatibilitas ganda tanpa perlu memelihara dua mapping yang bisa tidak sinkron.
FAQ
Apakah typesVersions akan dihapus dari TypeScript?
Tidak secara resmi. Tapi dengan isolatedDeclarations jadi default behavior di masa depan, fungsi typesVersions akan makin terbatas. TypeScript team menganjurkan migrasi ke exports.types sebagai best practice jangka panjang. Referensi: TypeScript 5.5 Release Blog.
Apakah library yang cuma punya entry point tunggal perlu setup exports + types?
Kalau package kamu cuma punya satu entry point ("main" atau "exports": "." saja), perubahan ini nggak terlalu berdampak. Cukup pastikan field types: "./dist/index.d.ts" di root package.json sudah benar. Konfigurasi exports dengan types jadi krusial hanya saat kamu punya subpath exports seperti npm-package-mu/button.
Apakah pnpm/yarn workspaces terpengaruh oleh perubahan ini?
Ya, terutama di monorepo dengan banyak internal package. Resolusi internal package di pnpm/yarn workspaces juga mengikuti aturan yang sama. Kalau package internal tidak mengkonfigurasi exports.types dengan benar, package consumer di monorepo yang sama akan kena error resolusi. Solusinya sama: pastikan setiap package internal punya exports.types yang eksplisit.
Apa tool terbaik untuk debugging module resolution error?
Gunakan tsc --traceResolution. Flag ini akan mencetak setiap langkah yang TypeScript ambil saat mencari module. Output-nya panjang, tapi kamu bisa grep keyword package-mu. Tambahan: panduan troubleshooting module resolution TypeScript dan Are The Types Wrong sangat membantu.
Kesimpulan
isolatedDeclarations bukan cuma fitur build performance. Ini adalah perubahan mendasar yang memaksa package maintainer untuk lebih disiplin dalam mendefinisikan type resolution. typesVersions masih berfungsi, tapi tidak bisa lagi diandalkan sebagai mekanisme utama. Masa depan ada di exports dengan field types yang eksplisit per subpath.
Langkah yang harus kamu ambil sekarang: cek package.json library-mu, jalankan npx attw, dan pastikan setiap subpath di exports punya field types. Jangan tunggu consumer kirim issue “Cannot find module”. Fix ini kecil, tapi dampaknya besar buat reputasi package-mu.
