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.

Konfigurasi package.json TypeScript dengan exports dan typesVersions untuk isolatedDeclarations

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.

Module resolution error TypeScript saat isolatedDeclarations aktif dan exports package.json salah konfigurasi

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.ts di path yang di-import consumer
  • Rute typesVersions: Kalau nggak ketemu, cek typesVersions di package.json buat mapping ulang path
  • Rute deklarasi sumber: Kalau masih gagal, cari file .ts asli di direktori src/ pakai rootDir sebagai 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.ts di-emit per file tanpa informasi dari file tetangga
  • typesVersions bergantung ke struktur direktori: Mapping wildcard typesVersions butuh tahu direktori src/ kamu, yang sekarang tidak diakses saat emit
  • exports jadi satu-satunya sumber kebenaran: Node.js dan TypeScript sama-sama akan menggunakan field exports untuk resolve path
Editor kode menampilkan package.json dengan exports dan typesVersions untuk TypeScript isolatedDeclarations

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 exports punya field types sendiri. Ini wajib karena TypeScript butuh lokasi .d.ts yang eksplisit per entry point.
  • typesVersions tetap dipertahankan sebagai fallback backward-compatible untuk consumer TypeScript versi lama (di bawah 5.5).
  • Pola typesVersions gunakan 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 apakah exports dan typesVersions kamu 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.ts benar-benar ada di path yang sesuai dengan exports.*.types. Jangan sampai typo nama file.
Terminal error module resolution TypeScript isolatedDeclarations package.json salah konfigurasi

Checklist Migrasi dari typesVersions ke exports + types

Kalau library kamu udah ada dan mau migrasi ke pola baru, ikuti langkah berikut:

  1. Jalankan npx attw di versi package saat ini. Catat semua subpath yang berfungsi.
  2. Buat mapping exports dengan field types per subpath. Mulai dari yang paling sering dipakai consumer.
  3. Pertahankan typesVersions dengan wildcard "*": ["./dist/*"] sebagai fallback.
  4. Naikkan versi TypeScript ke 5.6, nyalakan isolatedDeclarations: true.
  5. Jalankan build, verifikasi .d.ts ada di path yang sesuai.
  6. Jalankan npx publint dan npx attw lagi.
  7. Uji di project consumer kosong.
  8. 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.

About the Author

Dzul Qurnain

Suka nonton Anime, ngoding dan bagi-bagi tips kalau tahu.. Oh iya, suka baca ( tapi yang menarik menurutku aja)... Praktisi WordPress, web development, SEO, dan server administration yang membagikan tutorial teknis dan catatan implementasi nyata.

View All Articles