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:#fff

compilerOptions — 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
Aktifkan strict: true dari 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 dari noImplicitAny, lalu strictNullChecks, 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 di tsconfig.json hanya dikenali oleh TypeScript compiler — tidak oleh Node.js runtime atau bundler secara otomatis. Untuk Node.js, kamu perlu tsconfig-paths atau @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:

  1. Berbagi base config antar beberapa proyek dalam monorepo
  2. Override sebagian opsi untuk lingkungan yang berbeda (dev vs produksi)
  3. 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 #

OpsiDefaultRekomendasiKeterangan
strictfalsetrueAktifkan semua pemeriksaan ketat
targetES3ES2022Versi JS output
moduleBergantung targetCommonJS/NodeNextSistem modul
outDir./distDirektori output
rootDir./srcDirektori source
sourceMapfalsetrueSource map untuk debug
noEmitOnErrorfalsetrueJangan emit jika ada error
esModuleInteropfalsetrueInterop CommonJS
skipLibCheckfalsetrueSkip check node_modules
noUncheckedIndexedAccessfalsetrueArray index lebih aman
noUnusedLocalsfalsetrueTangkap variabel tak terpakai
forceConsistentCasingInFileNamesfalsetrueKonsistensi nama file

Ringkasan #

  • strict: true adalah opsi terpenting — ia mengaktifkan delapan pemeriksaan ketat sekaligus; aktifkan dari hari pertama proyek, jangan menunggu.
  • target menentukan fitur JS yang di-transpile — gunakan ES2022 untuk Node.js 16+ atau browser modern; TypeScript akan men-downlevel fitur yang lebih baru secara otomatis.
  • noEmitOnError: true wajib aktif untuk mencegah output JavaScript dihasilkan saat ada error tipe — tanpanya kamu bisa tidak sengaja men-deploy kode bermasalah.
  • noUncheckedIndexedAccess tidak termasuk dalam strict tapi sangat disarankan — ia membuat akses array/object dengan indeks menghasilkan T | undefined yang lebih akurat.
  • Path alias di tsconfig tidak dikenali runtime — kamu perlu konfigurasi tambahan di bundler atau gunakan tsconfig-paths untuk Node.js.
  • Gunakan extends untuk berbagi konfigurasi dasar antar lingkungan dan proyek — pertahankan satu tsconfig.base.json dan override seperlunya di tsconfig.json, tsconfig.test.json, dan seterusnya.
  • skipLibCheck: true mempercepat kompilasi dengan melewatkan type check pada .d.ts di node_modules — aman diaktifkan karena masalah tipe di library biasanya sudah diperbaiki di versi terbaru.
  • sourceMap: true wajib 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.

← Sebelumnya: Regex Identifier   Berikutnya: Vendoring →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact