TS Config #
tsconfig.json adalah pusat kendali seluruh perilaku TypeScript compiler — ia menentukan file mana yang dikompilasi, ke versi JavaScript apa outputnya, seberapa ketat type checking dilakukan, dan banyak lagi. Memahami tsconfig.json secara mendalam bukan hanya untuk mengonfigurasi proyek dari nol, tapi juga untuk memahami mengapa sebuah proyek yang kamu warisi berperilaku seperti yang ia lakukan. Banyak masalah TypeScript yang tampaknya membingungkan — error yang aneh, path import yang tidak berfungsi, atau output JavaScript yang tidak sesuai ekspektasi — semuanya berakar dari konfigurasi tsconfig.json. Artikel ini membahas semua opsi penting beserta implikasi praktis dan rekomendasinya.
Struktur Dasar tsconfig.json
#
Generate file konfigurasi awal dengan perintah:
npx tsc --init
Ini menghasilkan tsconfig.json dengan semua opsi dikomentari. Berikut struktur lengkap dan hubungan antar bagian utamanya:
flowchart TD
A[tsconfig.json] --> B[compilerOptions]
A --> C[include]
A --> D[exclude]
A --> E[files]
A --> F[extends]
A --> G[references]
B --> B1[Target & Output]
B --> B2[Type Checking]
B --> B3[Module Resolution]
B --> B4[Source Maps & Debug]
B --> B5[Paths & Aliases]
C --> C1[Glob: src/**/*]
D --> D1[node_modules\ndist\n*.spec.ts]
F --> F1[Base config\ndari file lain]
G --> G1[Project references\nuntuk monorepo]
style B fill:#339af0,color:#fff
style B2 fill:#ff6b6b,color:#fff
style F fill:#51cf66,color:#fffcompilerOptions — Opsi Target dan Output
#
Kelompok opsi ini mengontrol ke versi JavaScript apa kode TypeScript dikompilasi dan di mana hasilnya disimpan:
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationDir": "./dist/types",
"sourceMap": true,
"removeComments": false,
"noEmitOnError": true
}
}
target — Versi JavaScript Output
#
target menentukan fitur JavaScript apa yang boleh digunakan di output. Fitur yang lebih baru dari target akan di-downlevel (di-transpile ke ekuivalen yang lebih lama):
Target Gunakan untuk Fitur yang di-transpile
────────────────────────────────────────────────────────────────────
ES5 Browser lama, kompatibilitas luas Arrow fn, class, const, let, dll
ES2015/ES6 Node.js 6+, browser modern async/await, generators
ES2016 Node.js 8+ Async iteration
ES2017 Node.js 8+ Object.values, Object.entries
ES2019 Node.js 12+ Optional catch binding
ES2020 Node.js 14+ Optional chaining, nullish coalescing
ES2022 Node.js 16+ Top-level await, class fields
ESNext Selalu versi terbaru Minimal transpilasi
module — Sistem Modul
#
module menentukan bagaimana pernyataan import/export dikompilasi di output:
CommonJS Node.js tradisional — require/module.exports
ES2020 ES modules modern — import/export (perlu Node.js 12+ dengan "type":"module")
NodeNext Node.js ESM yang aware .cts/.mts — direkomendasikan untuk Node.js modern
Bundler Untuk Vite, Webpack, esbuild — tidak resolve module sendiri
lib — API yang Tersedia
#
lib menentukan type definition built-in apa yang tersedia. Jika tidak diset, TypeScript menentukan otomatis berdasarkan target:
{
"compilerOptions": {
// Untuk Node.js backend (tidak perlu DOM)
"lib": ["ES2022"],
// Untuk browser / frontend
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// Untuk menggunakan fetch di Node.js 18+
"lib": ["ES2022", "DOM"]
}
}
declaration dan declarationDir
#
Penting untuk library yang akan dipublikasikan ke npm — menghasilkan file .d.ts yang memungkinkan pengguna library mendapatkan type safety:
{
"compilerOptions": {
"declaration": true, // Hasilkan file .d.ts
"declarationDir": "./dist/types", // Lokasi file .d.ts
"declarationMap": true // Source map untuk .d.ts (memudahkan "Go to Definition")
}
}
sourceMap dan inlineSourceMap
#
{
"compilerOptions": {
"sourceMap": true, // Hasilkan file .js.map terpisah
// ATAU
"inlineSourceMap": true, // Embed source map langsung di file .js (tidak disarankan untuk produksi)
"inlineSources": true // Sertakan source TypeScript di dalam source map
}
}
compilerOptions — Type Checking
#
Inilah kelompok opsi yang paling mempengaruhi keamanan kode TypeScript kamu:
{
"compilerOptions": {
// === WAJIB AKTIF — mengaktifkan semua strict check berikut sekaligus ===
"strict": true,
// Opsi strict individual (sudah termasuk dalam strict: true)
"noImplicitAny": true, // Larang tipe 'any' yang disimpulkan implisit
"strictNullChecks": true, // null/undefined tidak bisa masuk ke tipe lain
"strictFunctionTypes": true, // Periksa tipe parameter fungsi secara kontravariant
"strictBindCallApply": true, // Periksa tipe untuk bind/call/apply
"strictPropertyInitialization": true, // Properti class harus diinisialisasi di constructor
"noImplicitThis": true, // 'this' dengan tipe implisit 'any' adalah error
"useUnknownInCatchVariables": true, // Error di catch bertipe 'unknown' bukan 'any'
"alwaysStrict": true, // Parse dalam strict mode dan emit "use strict"
// Opsi tambahan di luar strict
"noUnusedLocals": true, // Error untuk variabel lokal yang tidak digunakan
"noUnusedParameters": true, // Error untuk parameter yang tidak digunakan
"noImplicitReturns": true, // Semua jalur return harus mengembalikan nilai
"noFallthroughCasesInSwitch": true, // Larang fall-through di switch tanpa break
"noUncheckedIndexedAccess": true, // arr[i] bertipe T | undefined (lebih aman)
"noPropertyAccessFromIndexSignature": true, // Larang dot notation untuk index signature
"exactOptionalPropertyTypes": true, // Bedakan undefined vs tidak ada properti
"noImplicitOverride": true // Wajib tulis 'override' untuk method override
}
}
Implikasi Praktis strict: true
#
// Dengan strict: true, semua ini menjadi error yang ditangkap compiler:
// noImplicitAny — tidak ada any implisit
function proses(x) { return x; }
// ✗ Error: Parameter 'x' implicitly has an 'any' type
// strictNullChecks — tidak ada null/undefined yang tidak ditangani
const nama: string = null;
// ✗ Error: Type 'null' is not assignable to type 'string'
// useUnknownInCatchVariables — catch error bertipe unknown
try { } catch (e) {
e.message; // ✗ Error: 'e' is of type 'unknown'
}
// noUncheckedIndexedAccess — akses array/object dengan indeks lebih aman
const arr: number[] = [1, 2, 3];
const val = arr[10]; // Tipe: number | undefined — bukan hanya number
Aktifkanstrict: truedari hari pertama proyek. Mengaktifkannya di proyek yang sudah berjalan membutuhkan perbaikan ratusan atau ribuan error sekaligus. Jika kamu harus mengaktifkannya secara bertahap di proyek lama, aktifkan satu opsi strict per sprint — mulai darinoImplicitAny, lalustrictNullChecks, dan seterusnya.
compilerOptions — Module Resolution
#
{
"compilerOptions": {
"moduleResolution": "bundler", // Atau "node", "node16", "nodenext"
"esModuleInterop": true, // Izinkan import default dari CommonJS modules
"allowSyntheticDefaultImports": true, // Izinkan default import dari modul tanpa default
"resolveJsonModule": true, // Izinkan import file .json
"allowJs": true, // Izinkan file .js dalam proyek TypeScript
"checkJs": false, // Jangan periksa tipe file .js (berguna saat migrasi)
"forceConsistentCasingInFileNames": true // Error untuk import dengan kapitalisasi berbeda
}
}
moduleResolution — Cara TypeScript Mencari Modul
#
node — Algoritma Node.js klasik (require), cocok untuk CommonJS lama
node16 — Node.js 16+ ESM aware, perlu ekstensi eksplisit di import
nodenext — Terbaru untuk Node.js ESM, alias node16
bundler — Untuk Vite/webpack/esbuild, tidak perlu ekstensi, resolusi fleksibel
Path Alias dengan baseUrl dan paths
#
Path alias memungkinkan import yang bersih tanpa ../../../ yang panjang:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@utils/*": ["./src/utils/*"],
"@types/*": ["./src/types/*"],
"@config": ["./src/config/index.ts"]
}
}
}
Dengan konfigurasi di atas:
// Tanpa alias — verbose dan rapuh saat refactoring
import { format } from "../../../utils/format";
// Dengan alias — bersih dan tidak bergantung pada lokasi file
import { format } from "@utils/format";
import { Button } from "@components/Button";
import type { Pengguna } from "@types/pengguna";
Path alias ditsconfig.jsonhanya dikenali oleh TypeScript compiler — tidak oleh Node.js runtime atau bundler secara otomatis. Untuk Node.js, kamu perlutsconfig-pathsatau@swc/register. Untuk bundler seperti Vite, Webpack, atau esbuild, kamu perlu mengkonfigurasi alias yang sama di konfigurasi bundler tersebut.
compilerOptions — Kualitas Kode
#
{
"compilerOptions": {
"skipLibCheck": true, // Skip type check pada file .d.ts di node_modules
"noEmitOnError": true, // Jangan hasilkan output jika ada error
"removeComments": false, // Pertahankan komentar di output (berguna untuk debug)
"stripInternal": true, // Hapus deklarasi bertanda @internal dari .d.ts
"experimentalDecorators": true, // Aktifkan decorator (dibutuhkan NestJS, dll)
"emitDecoratorMetadata": true, // Emit metadata untuk decorator (dibutuhkan NestJS)
"useDefineForClassFields": true // Gunakan defineProperty untuk class field (ES2022+)
}
}
include, exclude, dan files
#
{
// include — file/direktori yang dikompilasi (default: semua .ts di direktori tsconfig)
"include": [
"src/**/*", // Semua file di src/ dan subdirektorinya
"scripts/**/*.ts" // Script tambahan
],
// exclude — file/direktori yang DIKECUALIKAN dari kompilasi
// Default sudah include: node_modules, outDir, dan file dari exclude field
"exclude": [
"node_modules",
"dist",
"**/*.spec.ts", // File test tidak perlu dikompilasi untuk produksi
"**/*.test.ts"
],
// files — daftar file eksplisit (digunakan bersama include atau sendiri)
// Jarang digunakan — lebih sering pakai include dengan glob
"files": [
"src/index.ts",
"src/types/global.d.ts"
]
}
extends — Mewarisi Konfigurasi
#
extends memungkinkan satu tsconfig.json mewarisi semua pengaturan dari file lain. Ini sangat berguna untuk:
- Berbagi base config antar beberapa proyek dalam monorepo
- Override sebagian opsi untuk lingkungan yang berbeda (dev vs produksi)
- Menggunakan preset komunitas seperti
@tsconfig/node20
// tsconfig.base.json — konfigurasi shared
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true
}
}
// tsconfig.json — extend dari base, tambahkan yang spesifik
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
// tsconfig.test.json — untuk testing dengan Jest/Vitest
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true, // Jangan hasilkan output saat type-check test
"types": ["jest"] // Tambahkan type definition Jest
},
"include": ["src/**/*", "tests/**/*", "**/*.spec.ts", "**/*.test.ts"]
}
Preset Komunitas #
# Install preset untuk Node.js 20
npm install --save-dev @tsconfig/node20
# Gunakan dalam tsconfig.json
{
"extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
}
}
Project References — Untuk Monorepo #
Project references memungkinkan TypeScript memahami dependensi antar paket dalam monorepo, memungkinkan kompilasi inkremental yang sangat cepat:
monorepo/
├── packages/
│ ├── shared/ # Library bersama
│ │ ├── src/
│ │ └── tsconfig.json
│ ├── backend/ # Backend service
│ │ ├── src/
│ │ └── tsconfig.json
│ └── frontend/ # Frontend app
│ ├── src/
│ └── tsconfig.json
└── tsconfig.json # Root tsconfig
// tsconfig.json root — hanya untuk orchestration
{
"files": [], // Tidak ada file yang dikompilasi di root
"references": [
{ "path": "./packages/shared" },
{ "path": "./packages/backend" },
{ "path": "./packages/frontend" }
]
}
// packages/backend/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true, // Wajib untuk project references
"outDir": "./dist",
"rootDir": "./src"
},
"references": [
{ "path": "../shared" } // Backend bergantung pada shared
]
}
Template Konfigurasi Siap Pakai #
Untuk Node.js Backend (Express, Fastify, NestJS) #
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"lib": ["ES2022"],
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"sourceMap": true,
"declaration": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmitOnError": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
}
Untuk Frontend (React, Vue dengan Vite) #
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
"noEmit": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Untuk Library npm (Dipublikasikan) #
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"lib": ["ES2020"],
"moduleResolution": "bundler",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noEmitOnError": true,
"skipLibCheck": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}
Tabel Referensi Opsi Penting #
| Opsi | Default | Rekomendasi | Keterangan |
|---|---|---|---|
strict | false | true | Aktifkan semua pemeriksaan ketat |
target | ES3 | ES2022 | Versi JS output |
module | Bergantung target | CommonJS/NodeNext | Sistem modul |
outDir | — | ./dist | Direktori output |
rootDir | — | ./src | Direktori source |
sourceMap | false | true | Source map untuk debug |
noEmitOnError | false | true | Jangan emit jika ada error |
esModuleInterop | false | true | Interop CommonJS |
skipLibCheck | false | true | Skip check node_modules |
noUncheckedIndexedAccess | false | true | Array index lebih aman |
noUnusedLocals | false | true | Tangkap variabel tak terpakai |
forceConsistentCasingInFileNames | false | true | Konsistensi nama file |
Ringkasan #
strict: trueadalah opsi terpenting — ia mengaktifkan delapan pemeriksaan ketat sekaligus; aktifkan dari hari pertama proyek, jangan menunggu.targetmenentukan fitur JS yang di-transpile — gunakanES2022untuk Node.js 16+ atau browser modern; TypeScript akan men-downlevel fitur yang lebih baru secara otomatis.noEmitOnError: truewajib aktif untuk mencegah output JavaScript dihasilkan saat ada error tipe — tanpanya kamu bisa tidak sengaja men-deploy kode bermasalah.noUncheckedIndexedAccesstidak termasuk dalamstricttapi sangat disarankan — ia membuat akses array/object dengan indeks menghasilkanT | undefinedyang lebih akurat.- Path alias di
tsconfigtidak dikenali runtime — kamu perlu konfigurasi tambahan di bundler atau gunakantsconfig-pathsuntuk Node.js.- Gunakan
extendsuntuk berbagi konfigurasi dasar antar lingkungan dan proyek — pertahankan satutsconfig.base.jsondan override seperlunya ditsconfig.json,tsconfig.test.json, dan seterusnya.skipLibCheck: truemempercepat kompilasi dengan melewatkan type check pada.d.tsdinode_modules— aman diaktifkan karena masalah tipe di library biasanya sudah diperbaiki di versi terbaru.sourceMap: truewajib untuk debugging yang efektif — tanpanya stack trace menunjuk ke JavaScript yang dikompilasi, bukan ke TypeScript aslinya.- Untuk monorepo, gunakan project references dengan
composite: true— ini memungkinkan kompilasi inkremental yang jauh lebih cepat karena TypeScript hanya mengkompilasi ulang paket yang berubah.