Pendalaman tsconfig

Keluarga opsi strict, module dan target, serta noUncheckedIndexedAccess.

Daftar isi

Berkas tsconfig.json mengatur perilaku compiler TypeScript untuk satu proyek, mulai dari seberapa ketat pemeriksaan tipe dilakukan sampai bentuk kode JavaScript yang dihasilkan. Topik ini membahas tiga kelompok opsi yang paling menentukan kualitas sebuah proyek, yaitu keluarga opsi strict, pasangan opsi module dan target, serta opsi noUncheckedIndexedAccess. Setiap kelompok diuji lewat proyek kecil yang konfigurasinya dapat dibandingkan secara langsung. Setelah membacanya, isi sebuah tsconfig.json dapat dibaca dan disusun sambil memahami akibat dari setiap opsi yang disertakan.

Keluarga opsi strict#

Opsi strict menyalakan sekelompok pemeriksaan tipe yang ketat sekaligus melalui satu nilai. Dengan "strict": true, compiler mengaktifkan noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, dan alwaysStrict. Dua pemeriksaan yang paling sering memengaruhi kode sehari-hari adalah noImplicitAny, yaitu penolakan terhadap parameter atau variabel yang tipenya tidak dapat disimpulkan, dan strictNullChecks, yaitu perlakuan null serta undefined sebagai tipe yang berdiri sendiri dan tidak bisa menempel pada tipe lain. Keduanya menutup dua sumber bug yang paling umum pada JavaScript, sehingga proyek baru hampir selalu dimulai dengan strict yang menyala.

Perbedaan perilakunya paling mudah diamati dari dua proyek yang berkas sumbernya identik tetapi konfigurasinya berbeda. Proyek proyek-ketat di bawah ini memakai strict yang menyala, sedangkan proyek-lemah pada bagian berikutnya mematikannya. Berkas kuadrat.ts pada kedua proyek sama persis isinya, sehingga setiap perbedaan hasil murni berasal dari konfigurasi.

json proyek-ketat/tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "module": "nodenext",
    "target": "es2022"
  }
}
ts proyek-ketat/kuadrat.ts
// tidak-dijalankan
function kuadrat(nilai) {
  return nilai * nilai;
}

function statusBacaan(nilai: number | null): string {
  return "Bacaan: " + nilai.toFixed(1);
}

console.log(kuadrat(6));
console.log(kuadrat("empat"));
console.log(statusBacaan(24.5));
bash
# memeriksa proyek dengan strict tanpa menghasilkan berkas keluaran
tsc --project proyek-ketat --noEmit --pretty false
text Keluaran
proyek-ketat/kuadrat.ts(2,18): error TS7006: Parameter 'nilai' implicitly has an 'any' type.
proyek-ketat/kuadrat.ts(7,23): error TS18047: 'nilai' is possibly 'null'.

Pesan pertama berasal dari noImplicitAny. Parameter nilai pada fungsi kuadrat tidak dianotasi dan tidak ada informasi lain yang bisa menyimpulkan tipenya, sehingga compiler menolak parameter tersebut alih-alih membiarkan nilai bertipe any. Pesan kedua berasal dari strictNullChecks. Tipe kembalian fungsi statusBacaan dinyatakan string, tetapi isi fungsinya memanggil toFixed pada nilai yang bertipe number | null, sehingga kemungkinan null harus ditangani lebih dahulu sebelum pemeriksaan lolos tanpa pesan.

Perilaku tanpa strict#

Proyek proyek-lemah memuat berkas sumber yang sama persis dengan proyek-ketat, dan satu-satunya perbedaan ada pada nilai strict yang dimatikan. Pada konfigurasi ini kedua masalah tadi lolos tanpa pesan error apa pun, karena compiler tidak lagi menuntut anotasi maupun penanganan null.

json proyek-lemah/tsconfig.json
{
  "compilerOptions": {
    "strict": false,
    "module": "nodenext",
    "target": "es2022"
  }
}
bash
# mengompilasi proyek tanpa strict, lalu menjalankan hasilnya
tsc --project proyek-lemah && node proyek-lemah/kuadrat.js
text Keluaran
36
NaN
Bacaan: 24.5

Program berjalan tanpa error sintaks, tetapi baris kedua keluarannya memperlihatkan akibat dari konfigurasi ini. Pemanggilan kuadrat("empat") mengalikan string dengan string, dan JavaScript mengonversi keduanya menjadi angka secara paksa, sehingga hasilnya NaN. Bug semacam ini baru terlihat saat program dijalankan, sedangkan konfigurasi strict menangkapnya pada waktu kompilasi sebelum program sempat berjalan. Bahaya ketiga yang tidak terlihat pada keluaran juga tetap ada, yaitu statusBacaan yang berpotensi dipanggil dengan null tanpa pernah ditolak compiler.

Opsi module dan target#

Opsi module menentukan bentuk kode modul pada hasil kompilasi, misalnya require dan exports untuk commonjs atau import dan export untuk nodenext dan esnext. Opsi target menentukan tingkat bahasa JavaScript dari hasil keluaran, sehingga sintaks yang lebih baru daripada targetnya diturunkan menjadi bentuk yang setara untuk versi yang lebih tua. Proses penurunan ini disebut downleveling. Kedua opsi tidak mengubah hasil pemeriksaan tipe, melainkan hanya mengubah wujud berkas yang diproduksi.

Proyek proyek-modul memakai module commonjs dan target es2017. Berkas sumbernya memuat operator nullish coalescing yang baru dikenal JavaScript pada ES2020, yakni operator untuk menangani null dan undefined. Proses penurunannya dapat diamati langsung.

json proyek-modul/tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2017"
  }
}
ts proyek-modul/total.ts
export function hargaTotal(jumlah: number, hargaSatuan: number): number {
  return jumlah * hargaSatuan;
}

const catatan = null;
const label = catatan ?? "tanpa catatan";

console.log(label);
console.log(hargaTotal(3, 25000));
text Keluaran
tanpa catatan
75000
bash
# mengompilasi proyek, menampilkan hasilnya, lalu menjalankannya
tsc --project proyek-modul && cat proyek-modul/total.js && node proyek-modul/total.js
text Keluaran
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hargaTotal = hargaTotal;
function hargaTotal(jumlah, hargaSatuan) {
    return jumlah * hargaSatuan;
}
const catatan = null;
const label = catatan !== null && catatan !== void 0 ? catatan : "tanpa catatan";
console.log(label);
console.log(hargaTotal(3, 25000));
tanpa catatan
75000

Hasil kompilasinya memperlihatkan cara kerja kedua opsi tersebut. Baris exports.hargaTotal = hargaTotal adalah bentuk khas commonjs, sehingga berkas ini dapat dijalankan di Node.js tanpa alat tambahan. Operator ?? diturunkan menjadi ekspresi bersyarat yang setara karena target es2017 belum mengenal operator itu, sedangkan pada proyek-proyek sebelumnya yang memakai target es2022, operator yang sama dipertahankan apa adanya. Anotasi tipe hilang seluruhnya dari hasil kompilasi karena pemeriksaan tipe memang tidak menghasilkan kode apa pun.

noUncheckedIndexedAccess#

Secara bawaan, compiler menganggap setiap akses indeks pada array selalu berhasil, sehingga bacaan[0] pada array bertipe number[] langsung bertipe number walaupun posisi tersebut bisa saja belum berisi nilai. Opsi noUncheckedIndexedAccess mengubah asumsi ini dengan menambahkan undefined pada hasil setiap akses indeks, sehingga nilai dari indeks wajib diperiksa lebih dahulu sebelum dipakai. Pemeriksaan ini menutup sekelas bug berupa akses elemen di luar batas yang pada JavaScript biasanya baru terlihat sebagai undefined di tempat yang jauh dari sumbernya.

json proyek-index/tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "module": "nodenext",
    "target": "es2022"
  }
}
ts proyek-index/bacaan.ts
// tidak-dijalankan
const bacaan: number[] = [21.4, 22.1, 20.8];

const pertama = bacaan[0];
console.log(pertama.toFixed(1));
bash
# memeriksa proyek dengan noUncheckedIndexedAccess
tsc --project proyek-index --noEmit --pretty false
text Keluaran
proyek-index/bacaan.ts(5,13): error TS18048: 'pertama' is possibly 'undefined'.

Pesan error ini muncul walaupun strict sudah menyala, karena opsi ini memang bukan bagian dari strict. Perbaikannya menuntut pemeriksaan eksplisit sebelum nilai indeks dipakai.

ts Perbaikan: pemeriksaan sebelum digunakan
const bacaan: number[] = [21.4, 22.1, 20.8];

const pertama = bacaan[0];
if (pertama !== undefined) {
  console.log(pertama.toFixed(1));
}
text Keluaran
21.4

Biaya tambahan dari opsi ini adalah kode yang lebih verbose karena setiap akses indeks perlu diperiksa, sedangkan manfaatnya adalah setiap kemungkinan kegagalan akses tercatat pada tipe dan wajib ditangani. Pada proyek yang memuat banyak pengolahan array, opsi ini biasanya dinyalakan sejak awal agar pola pemeriksaannya melekat pada seluruh basis kode.

Kesalahan umum#

Kesalahan berikut sering muncul saat menyusun tsconfig.json untuk proyek nyata.

  • Mengira noUncheckedIndexedAccess ikut menyala bersama strict. Opsi ini berdiri sendiri, sehingga proyek dengan strict penuh tetap mengizinkan akses indeks tanpa pemeriksaan bila opsi ini tidak ditulis.
  • Mengubah module tanpa menyesuaikan lingkungan eksekusi. Hasil commonjs tidak dapat dijalankan di browser tanpa bundler, sedangkan hasil nodenext menuntut ekstensi berkas yang ditulis lengkap pada setiap import.
  • Menurunkan target tanpa kebutuhan nyata. Target yang rendah memaksa compiler menghasilkan kode bantuan yang lebih panjang untuk fitur modern, dan sejak TypeScript 7 target ES5 bahkan sudah dihapus dari pilihannya.
  • Menyalin tsconfig.json antarproyek tanpa membaca isinya. Opsi yang tepat untuk satu lingkungan kerja sering keliru di lingkungan lain, sehingga hasil pemeriksaan dan hasil kompilasinya menyesatkan.

Ringkasan#

  • Opsi strict mengaktifkan sekelompok pemeriksaan sekaligus, dan dua yang paling terlihat adalah noImplicitAny untuk parameter tanpa tipe serta strictNullChecks untuk penanganan null dan undefined.
  • Opsi module menentukan bentuk kode modul pada hasil kompilasi, sedangkan target menentukan tingkat bahasa JavaScript keluarannya beserta proses downleveling yang menyertainya.
  • Opsi noUncheckedIndexedAccess menambahkan undefined pada hasil setiap akses indeks array dan harus dinyalakan terpisah dari strict.
  • Perilaku sebuah konfigurasi dapat diuji dengan dua proyek kecil yang berkas sumbernya identik, sehingga setiap perbedaan hasil pasti berasal dari konfigurasinya.

Referensi#