Proyek nyata jarang dimulai dari kertas kosong: sudah ada ribuan baris JavaScript yang berjalan setiap hari, dan tidak ada yang bisa memberi tim seminggu cuti untuk “migrasi”. Kabar baiknya, TypeScript tidak menuntut migrasi serentak — seluruh rantai alatnya dirancang untuk hidup berdampingan dengan JavaScript lama, satu berkas demi satu berkas, dengan kereta tetap berjalan. Chapter ini menyusun tangga migrasinya dari anak tangga paling rendah: memeriksa JavaScript tanpa mengubahnya, memberi tipe dari luar lewat berkas deklarasi, lalu mengganti ekstensi satu per satu — semuanya dijalankan di atas proyek campuran yang benar-benar hidup.

Tangga Migrasi dalam Satu Pandangan #

Setiap anak tangga memberi sesuatu tanpa merenggut apa pun:

LangkahYang dilakukanYang didapatYang berubah di produksi
// @ts-check + JSDocperiksa berkas .js apa adanyagalat tipe pada kode lamatidak ada
Berkas deklarasi .d.tstipe untuk module .js dari luarautocomplete + pemeriksaan bagi pemakaitidak ada
Ganti ekstensi .tsanotasi langsung di berkasseluruh fitur bahasaberkas ikut ter-compile
strict penuhnyalakan keluarga pemeriksaan ketatjaminan Chapter Strict Modetidak ada

Cara membaca tabelnya: dua anak tangga pertama tidak menyentuh satu byte pun kode produksi — mereka hanya menambah lapisan pemeriksaan di atasnya. Anak tangga ketiga mulai mengubah berkas, dan itulah satu-satunya momen yang menuntut koordinasi tim. Konfigurasi allowJs pada tsconfig.json menjadi lem di antara semuanya: kompiler diminta mengelola proyek yang berisi kedua bahasa sekaligus.

Memeriksa Tanpa Migrasi #

Anak tangga pertama hampir memalukan murahnya: satu komentar // @ts-check di baris pertama berkas .js, dan TypeScript memeriksanya layaknya berkas .ts — tanpa mengubah ekstensi, tanpa langkah build. Tipe tidak bisa ditulis sebagai anotasi di JavaScript, jadi bentuknya dinyatakan lewat JSDoc:

js
// @ts-check

/**
 * Menghitung total harga.
 * @param {number} harga - harga satuan
 * @param {number} jumlah - banyaknya item
 * @returns {number} total harga
 */
function hitungTotal(harga, jumlah) {
  return harga * jumlah;
}

console.log(hitungTotal(5000, 3));
console.log(hitungTotal(5000, "3"));

Output yang Dihasilkan:

text
15000
15000

Perhatikan baris terakhir: argumen kedua sengaja dikutip menjadi "3". Saat dijalankan dengan Node.js, JavaScript memaksa "3" menjadi angka lewat koersi — kedua panggilan mencetak 15000, program “benar”, tidak ada yang mengeluh. Sekarang periksa berkas yang sama dengan kompiler:

bash
npx --yes [email protected] --noEmit --allowJs --checkJs --strict cek-saldo.js
echo "kode keluar: $?"

Output yang Dihasilkan:

text
cek-saldo.js(14,31): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
kode keluar: 1

Kompiler menolak argumen "3" untuk parameter number — tepat di baris yang diam-diam lolos pada eksekusi. Dua flag yang menyertainya, --allowJs dan --checkJs, menyuruh tsc membaca berkas .js dan memeriksanya; pada proyek nyata keduanya hidup di tsconfig.json, dan komentar @ts-check tinggal dipasang pada berkas yang sudah rapi agar tidak tenggelam oleh laporan besar di awal.

Memberi Tipe dari Luar #

Anak tangga kedua menjawab pertanyaan yang muncul begitu satu berkas ditukar: bagaimana module .js lama yang belum diganti bisa dipakai kode .ts baru tanpa kehilangan pemeriksaan? Jawabannya berkas deklarasi — tipe ditulis di berkas .d.ts terpisah yang hidup bersebelahan dengan aslinya:

js
export function formatRupiah(nilai) {
  return `Rp${nilai.toLocaleString("id-ID")}`;
}

export const VERSI = "1.0.0";
ts
export declare function formatRupiah(nilai: number): string;
export declare const VERSI: string;

Berkas .js tetap murni — tidak satu anotasi pun menyentuhnya — sementara .d.ts memetakan bentuknya untuk kompiler. Kode TypeScript kini bisa mengimpor module itu dan mendapat pemeriksaan penuh:

ts
import { formatRupiah, VERSI } from "./utilitas.js";

console.log(formatRupiah(250000), VERSI);

Output yang Dihasilkan:

text
Rp250.000 1.0.0

Periksa dengan kompiler ketat, lalu jalankan:

bash
npx --yes [email protected] --noEmit --strict aplikasi.ts
echo "kode keluar: $?"

Output yang Dihasilkan:

text
kode keluar: 0

Tiga berkas, dua dunia: tsc membaca pasangan utilitas.js + utilitas.d.ts sebagai satu module bertipe, sementara Node.js saat menjalankan aplikasi.ts mengabaikan .d.ts sepenuhnya — penghapusan tipe tidak pernah menoleh ke berkas deklarasi. Pemakaian "./utilitas.js" sebagai specifier mengikuti aturan hasil akhir yang kita pelajari pada Chapter Menjalankan TypeScript di Node.js.

Mengganti Nama Satu per Satu #

Anak tangga ketiga adalah pertukaran sesungguhnya: berkas .js diganti .ts, anotasi pindah dari JSDoc ke dalam kode. Kuncinya arah pertukarannya: mulai dari daun — module kecil yang tidak diimpor siapa pun — lalu naik ke akar. Dan karena Node modern menjalankan kedua ekstensi berdampingan, kode lama boleh memakai module baru sejak hari pertama:

ts
export function sapaPelanggan(nama: string): string {
  return `Selamat datang, ${nama}!`;
}
js
import { sapaPelanggan } from "./pelanggan.ts";

console.log(sapaPelanggan("Siti"));

Output yang Dihasilkan:

text
Selamat datang, Siti!

konsumen.mjs adalah JavaScript lama yang belum diganti; ia mengimpor pelanggan.ts yang baru — dan semuanya berjalan dalam satu perintah node, tanpa build. Pada gilirannya konsumen.mjs ikut diganti namanya, .d.ts yang dulu menjaga pemakainya pensiun karena tipe kini tinggal di dalam, dan tangga naik satu tingkat. Strategi include pada tsconfig.json mengikuti ritme yang sama: mulai dengan satu folder yang sudah bersih, perluas daftarnya setiap kali gelombang penggantian nama tuntas.

Menjaga Dua Dunia Tetap Rapi #

Migrasi yang sehat punya tiga kebiasaan. Pertama, komit kecil dan sering: satu berkas diganti, pemeriksaan hijau, commit — bukan ratusan berkas sekaligus yang membuat review mustahil. Kedua, jangan tunda pemeriksaan: rantai tsc --noEmit dari Section Deployment dan Operations dijalankan pada proyek campuran apa pun keadaannya, sehingga berkas lama tak bertipe dan berkas baru ketat diawasi dengan satu standar yang sama. Ketiga, terima hasil antara: JSDoc yang tertinggal bukan kegagalan — ia tetap memberi pemeriksaan yang jauh lebih baik daripada tidak ada, dan bisa naik tangga kapan pun waktu mengizinkan.

Dengan ini perjalanan TypeScript kita genap: alasan memakainya, proyek yang berdiri, tipe yang bekerja, mode ketat yang menjaga, dua jalan menjalankannya, dan kini jalan memindahkan proyek lama. Section berikutnya menoleh ke perkembangan Node.js itu sendiri — fitur-fitur modern yang membuat banyak bab dalam Course ini jauh lebih ringkas daripada lima tahun silam.