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:
| Langkah | Yang dilakukan | Yang didapat | Yang berubah di produksi |
|---|---|---|---|
// @ts-check + JSDoc | periksa berkas .js apa adanya | galat tipe pada kode lama | tidak ada |
Berkas deklarasi .d.ts | tipe untuk module .js dari luar | autocomplete + pemeriksaan bagi pemakai | tidak ada |
Ganti ekstensi .ts | anotasi langsung di berkas | seluruh fitur bahasa | berkas ikut ter-compile |
strict penuh | nyalakan keluarga pemeriksaan ketat | jaminan Chapter Strict Mode | tidak 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:
// @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:
15000
15000Perhatikan 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:
npx --yes [email protected] --noEmit --allowJs --checkJs --strict cek-saldo.js
echo "kode keluar: $?"Output yang Dihasilkan:
cek-saldo.js(14,31): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
kode keluar: 1Kompiler 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:
export function formatRupiah(nilai) {
return `Rp${nilai.toLocaleString("id-ID")}`;
}
export const VERSI = "1.0.0";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:
import { formatRupiah, VERSI } from "./utilitas.js";
console.log(formatRupiah(250000), VERSI);Output yang Dihasilkan:
Rp250.000 1.0.0Periksa dengan kompiler ketat, lalu jalankan:
npx --yes [email protected] --noEmit --strict aplikasi.ts
echo "kode keluar: $?"Output yang Dihasilkan:
kode keluar: 0Tiga 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:
export function sapaPelanggan(nama: string): string {
return `Selamat datang, ${nama}!`;
}import { sapaPelanggan } from "./pelanggan.ts";
console.log(sapaPelanggan("Siti"));Output yang Dihasilkan:
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.