Typescript Tsconfig Module Resolution Path Aliases Declaration Files Build Targets
Definisi singkat
tsconfig.json adalah file konfigurasi utama TypeScript. Di sini kamu mengatur bagaimana compiler membaca source, mencari modul, memeriksa tipe, dan mengeluarkan hasil build.
Lima area yang paling sering berubah di project nyata adalah:
compilerOptions: pusat semua perilaku compiler.- Module resolution: cara TypeScript mencari file saat kamu menulis
import. - Path aliases: shortcut impor agar path panjang lebih mudah dirawat.
- Declaration files: file
.d.tsyang menyimpan bentuk tipe tanpa JavaScript runtime. - Build targets: versi JavaScript dan library target yang ingin dihasilkan.
Kalau konfigurasi TypeScript mulai membesar, masalah biasanya bukan karena TypeScript-nya, tapi karena hubungan antarbagian ini tidak lagi selaras.
Peran compilerOptions
compilerOptions adalah tempat kamu mengatur perilaku inti compiler. Hampir semua hal penting di project TypeScript masuk ke sini.
Contoh opsi yang paling sering dipakai:
target: output JavaScript yang ingin dihasilkan, misalnyaES2020atauESNext.module: format modul output, misalnyaCommonJS,ESNext, atauNodeNext.moduleResolution: strategi pencarian modul.baseUrldanpaths: dasar untuk alias path.outDirdanrootDir: arah input dan output build.declaration: apakah TypeScript menghasilkan.d.ts.strict: kumpulan pengecekan tipe yang lebih ketat.esModuleInteropdanallowSyntheticDefaultImports: membantu interop dengan modul CommonJS.
Intinya begini: compilerOptions bukan hanya daftar preferensi. Ia menentukan bagaimana TypeScript memahami source, menafsirkan import, dan menghasilkan artefak build.
Contoh konfigurasi dasar
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"declaration": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"]
}Kapan path aliases membantu
Path aliases membantu saat import mulai terlalu dalam atau terlalu rapuh.
Berguna kalau
- kamu sering menulis import seperti
../../../../components/Button - struktur folder sering berubah, tapi import tetap harus stabil
- kamu punya domain/folder yang jelas, misalnya
@components,@lib,@features - kamu ingin import lebih mudah dibaca saat review kode
Contoh
Tanpa alias:
import { formatDate } from '../../../../lib/date/format-date';Dengan alias:
import { formatDate } from '@/lib/date/format-date';Kenapa alias membantu
- Mengurangi path relatif yang panjang.
- Membuat refactor folder lebih aman.
- Memperjelas batas layer:
@/domain,@/ui,@/server, dan seterusnya.
Kapan alias tidak perlu
- Project kecil dengan struktur datar.
- Library publik yang ingin tetap mudah dipahami pengguna luar.
- Saat alias cuma dipakai untuk gaya, bukan untuk mengurangi kompleksitas.
Jebakan umum path aliases
Alias di tsconfig.json tidak otomatis dipahami runtime. Ini jebakan yang paling sering.
Artinya:
- TypeScript bisa lolos saat compile.
- Tapi Node, test runner, bundler, atau tooling lain bisa gagal resolve jika belum dikonfigurasi juga.
Jadi kalau kamu pakai alias, pastikan semua lapisan sinkron:
- TypeScript compiler
- bundler seperti Vite, Webpack, Turbopack, atau tsup
- test runner seperti Vitest atau Jest
- runtime Node kalau menjalankan file langsung
Module resolution
Module resolution adalah cara TypeScript mencari file saat membaca import.
Misalnya saat kamu menulis:
import { Button } from '@/components/button';TypeScript harus menentukan:
- apakah itu file
.ts,.tsx,.d.ts,.js, atau folder - apakah path itu alias atau path relatif
- apakah yang dicari file source, declaration, atau output runtime
Strategi yang umum
Classic
Model lama. Jarang dipakai untuk project modern.
Node
Meniru perilaku resolusi modul Node.js tradisional.
NodeNext
Dipakai kalau project mengikuti semantics ESM Node modern dan ingin resolusi yang selaras dengan package type, extension, dan export map.
Bundler
Cocok untuk project yang dibundle oleh tool modern. TypeScript lebih fokus pada type-checking dan mengikuti pola bundler, bukan sepenuhnya meniru runtime Node.
Pilih yang sesuai lingkungan
- Pakai
NodeNextkalau output dan runtime benar-benar mengikuti Node ESM. - Pakai
Bundlerkalau source akan diproses bundler modern dan kamu tidak ingin TypeScript terlalu memaksa aturan Node lama. - Jangan asal pakai default lama kalau project sudah pindah ke stack modern.
Jebakan umum module resolution
moduledanmoduleResolutiontidak cocok.- Import path terlihat valid di editor, tapi gagal saat build atau test.
- Package external punya
exportsmap, tapi konfigurasi compiler belum mengikuti. - File extension ESM dilupakan saat runtime Node menuntutnya.
Declaration files
Declaration file adalah file .d.ts yang berisi bentuk tipe, bukan implementasi JavaScript.
Isi declaration file biasanya mencakup:
- type alias
- interface
- signature function
- shape module
- augmentasi global
Peran declaration file
Declaration file dipakai untuk memberi TypeScript pengetahuan tipe tentang sesuatu yang tidak memiliki source TypeScript yang langsung terlihat oleh compiler.
Contoh penggunaan:
- package JavaScript murni yang belum punya tipe bawaan
- library internal yang ingin dibagikan ke project lain
- file global declaration seperti
window.__APP_CONFIG__ - module augmentation untuk menambah tipe pada library yang sudah ada
Contoh .d.ts
declare module 'legacy-date-lib' {
export function parseDate(value: string): Date;
export function formatDate(date: Date): string;
}Contoh global declaration:
export {};
declare global {
interface Window {
__APP_VERSION__: string;
}
}Cara pakai declaration file
Ada dua pola umum:
-
Dibaca otomatis oleh TypeScript
- Letakkan di lokasi yang termasuk dalam
includeatautypeRoots. - Cocok untuk declaration internal project.
- Letakkan di lokasi yang termasuk dalam
-
Dipasang bersama package
- Package publish menyertakan
.d.ts. - Bisa melalui field
typesatautypingsdipackage.json.
- Package publish menyertakan
Jebakan umum declaration file
- Menulis implementasi di
.d.ts— ini tidak boleh. - Lupa
export {}di file yang hanya berisi augmentasi global, sehingga scope jadi kacau. - Menaruh declaration file di folder yang tidak ikut dipindai compiler.
- Mengandalkan declaration file untuk menutupi masalah runtime, padahal tipe bukan perbaikan perilaku.
Build targets
Build target menentukan versi JavaScript dan library yang dianggap tersedia saat compile.
Dua hal yang perlu dibedakan
target: versi output JavaScript yang dihasilkan TypeScript.lib: kumpulan API bawaan yang dianggap ada saat mengetik code.
Contoh:
target: ES2017bisa tetap memakaiPromise, tapi tidak otomatis berarti semua fitur DOM atau Web API tersedia.libmenentukan apakah TypeScript mengenali API sepertifetch,Map,Set, atauDOM.
Contoh pola umum
Aplikasi web modern
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"]
}
}Library yang ingin fleksibel
{
"compilerOptions": {
"target": "ES2019",
"declaration": true,
"emitDeclarationOnly": false,
"module": "ESNext"
}
}Node service modern
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}Kenapa target penting
Target yang lebih rendah berarti TypeScript bisa menurunkan sintaks modern ke bentuk lama. Ini berguna kalau environment produksi belum mendukung fitur terbaru.
Tapi konsekuensinya:
- output bisa lebih verbose
- beberapa helper tambahan bisa muncul
- kamu perlu memastikan polyfill atau runtime support sesuai
Jebakan umum build target
targetterlalu tinggi untuk runtime produksi.libtidak sesuai environment, lalu muncul error tipe pada API yang sebenarnya ada atau sebaliknya.- Build library dan build aplikasi memakai target berbeda tanpa disadari.
- Tergoda mengejar
ESNexttanpa memeriksa bundler, runtime, dan test environment.
Alur kerja compile → resolve → emit
Secara sederhana, TypeScript berjalan lewat tiga tahap konsep:
- Compile: baca file, cek tipe, dan gabungkan konfigurasi.
- Resolve: cari file yang dirujuk lewat import, alias, declaration, dan package map.
- Emit: hasilkan output
.js,.d.ts, sourcemap, atau artefak lain.
Diagram ini penting karena banyak bug TypeScript bukan bug tipe, melainkan mismatch di tahap resolve atau emit.
Catatan implementasi
Susun konfigurasi bertahap
Untuk project yang makin besar, biasanya lebih sehat kalau kamu punya beberapa config:
tsconfig.jsondasartsconfig.app.jsontsconfig.node.jsontsconfig.build.json
Pola ini membantu memisahkan kebutuhan editor, build, test, dan tooling.
Pastikan semua tool membaca alias yang sama
Kalau TypeScript tahu @/, tapi Jest atau Node tidak, hasilnya akan membingungkan.
Cek konsistensi di:
tsconfig.json- bundler config
- test config
- package entrypoints
Gunakan strict mode sedini mungkin
Semakin besar project, semakin mahal biaya menyalakan strict mode belakangan.
Mulai dari ketat sejak awal biasanya lebih murah daripada membersihkan hutang tipe di akhir.
Kesalahan umum saat konfigurasi membesar
- Satu
tsconfigdipaksa melayani semua kebutuhan. - Alias dipakai di TypeScript, tapi tidak disinkronkan ke runtime.
moduleResolutiondipilih tanpa memperhatikan runtime target.declarationaktif, tapi publik API belum dirapikan.- File
.d.tsdibuat untuk menutupi masalah integrasi, bukan untuk mendeskripsikan kontrak. - Target build naik terus tanpa cek support lingkungan produksi.
- Konfigurasi editor, test, dan build saling berbeda diam-diam.
Referensi cepat
compilerOptions: pusat perilaku compiler TypeScript.- Path aliases: cocok untuk import yang panjang, domain-based structure, dan refactor yang lebih aman.
- Declaration files: dipakai untuk tipe tanpa runtime implementation.
- Build targets: menentukan output syntax dan API yang dianggap tersedia.
- Module resolution: menentukan bagaimana import dicari dan dipetakan ke file nyata.
Kalau kamu sedang debug TypeScript, biasanya urutan cek yang paling cepat adalah: tsconfig → moduleResolution → paths → runtime resolver → declaration file → target output.