Migrasi JavaScript ke TypeScript — TypeScript

Migrasi project JavaScript ke TypeScript tidak perlu sekaligus. TS dirancang untuk gradual adoption. Strateginya: mulai longgar, perketat bertahap. Langkah 1…

Memindahkan proyek JavaScript ke TypeScript gak harus sekaligus. TypeScript bisa membaca file .js dan .ts berdampingan, jadi kamu bisa pindah sedikit demi sedikit, sambil aplikasinya tetap jalan. Mirip renovasi rumah yang masih ditinggali: satu kamar dulu, penghuninya gak perlu mengungsi, dan tiap kamar yang selesai langsung bisa dipakai.

🧪 Tebakan cepat: sebuah file .js sudah diberi tipe lewat komentar JSDoc, misalnya @param {number} diskon. Lalu file-nya cuma kamu ganti nama jadi .ts, isinya gak diubah. Apakah TypeScript masih memakai tipe dari komentar itu? Tebak dulu, jawabannya ada di bagian "Langkah 5: ganti jadi .ts".

Langkah 1: allowJs, biar .js dan .ts bisa campur

Ini src/keranjang.js yang kita pindahkan (dan yang dipakai di Lab):

export function totalHarga(items, diskon) {
  let total = 0
  for (const item of items) {
    total += item.harga * item.jumlah
  }
  return total - total * diskon
}

const belanja = [
  { nama: "Buku", harga: 25000, jumlah: 2 },
  { nama: "Pensil", harga: 3000, jumlah: 5 },
]

const label = document.querySelector("#total")
label.textContent = totalHarga(belanja, "10%")

Ada bug di baris terakhir: diskonnya ditulis "10%", teks, bukan angka 0.1. Di browser, labelnya menampilkan NaN. Kita lihat di langkah mana TypeScript menangkapnya.

Tanpa pengaturan apa-apa, tsc mengabaikan file .js. Kalau isi src cuma file .js, dia malah bilang No inputs were found in config file ... ts(18003). Jadi tsconfig awalnya:

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": false,
    "strict": false,
    "target": "es2022",
    "module": "esnext",
    "noEmit": true
  },
  "include": ["src"]
}

Langkah 2: checkJs, mulai memeriksa .js

Ganti jadi "checkJs": true. Sekarang file .js ikut diperiksa, dan keluar 1 error di baris terakhir: Type 'number' is not assignable to type 'string'. ts(2322). totalHarga mengembalikan angka, sedangkan textContent butuh teks.

Mau memeriksa satu file saja? Tulis // @ts-check di baris pertama file itu, tanpa checkJs. Kebalikannya, // @ts-nocheck mematikan pemeriksaan untuk satu file.

Bug "10%"-nya belum ketahuan. TypeScript belum tahu diskon harus angka, karena parameternya belum punya tipe.

Langkah 3: JSDoc, tipe di dalam komentar

Sebelum mengganti nama file, kamu bisa memberi tipe lewat komentar JSDoc. Kodenya tetap JavaScript biasa:

/** @typedef {{ nama: string, harga: number, jumlah: number }} Item */

/**
 * @param {Item[]} items
 * @param {number} diskon
 * @returns {number}
 */
export function totalHarga(items, diskon) {

Sekarang 2 error: yang tadi, ditambah Argument of type 'string' is not assignable to parameter of type 'number'. ts(2345) di "10%". Bug-nya ketahuan.

Langkah 4: nyalakan strict satu per satu

Lalu perketat pemeriksaannya, satu pengaturan tiap kali. Di file ini, "noImplicitAny": true masih 2 error (semua parameter sudah bertipe), "strictNullChecks": true jadi 3 error: 'label' is possibly 'null'. ts(18047), karena querySelector mengembalikan null kalau elemennya gak ada. Terakhir, ganti semuanya dengan "strict": true.

Kenapa gak langsung strict dari awal? Di file kecil ini saja, checkJs dengan "strict": true tanpa JSDoc langsung memberi 4 error, bukan 1: dua parameter tanpa tipe (ts(7006)), label yang bisa null, dan angka ke teks. Proyek dengan 200 file bisa langsung punya ratusan error sekaligus, dan migrasinya macet. Satu pengaturan, perbaiki error-nya, commit, lalu lanjut ke pengaturan berikutnya.

Langkah 5: ganti jadi .ts

Nama file jadi keranjang.ts, dan JSDoc diganti tipe TypeScript:

interface Item {
  nama: string
  harga: number
  jumlah: number
}

export function totalHarga(items: Item[], diskon: number): number {

Masih 3 error yang sama. Hati-hati: kalau file-nya cuma diganti nama dan JSDoc-nya dibiarkan, TypeScript gak memakai tipe dari JSDoc di file .ts. items dan diskon jadi any tanpa tipe, dan muncul Parameter 'items' implicitly has an 'any' type. ts(7006). Itu jawaban tebakannya: JSDoc cuma dipakai untuk tipe di file .js.

Terakhir, perbaiki bug-nya:

const label = document.querySelector("#total")
if (label) {
  label.textContent = String(totalHarga(belanja, 0.1))
}

0 error, dan labelnya menampilkan 58500.

Langkah Error
1. allowJs 0
2. checkJs 1
3. JSDoc 2
4. strict 3
5. .ts 3
6. diperbaiki 0

⚠️ Jebakan yang sering ketemu

TL;DR: Migrasi itu bertahap. "allowJs": true membuat .js dan .ts bisa campur (0 error dulu), "checkJs": true atau // @ts-check mulai memeriksa .js, JSDoc (@param {number} diskon) memberi tipe tanpa ganti nama file, lalu pengaturan ketat dinyalakan satu per satu sampai "strict": true, karena strict dari awal bisa memunculkan ratusan error sekaligus. Saat diganti jadi .ts, tulis tipenya dengan sintaks TypeScript, karena JSDoc gak dipakai di file .ts. Ingat juga, sejak TypeScript 6.0 strict nyala kalau gak ditulis.

Yang akan kamu pelajari