Browser menjalankan JavaScript — tag <script> tidak mengenal berkas .ts, dan runtime pun sama. Artinya, setiap kode TypeScript yang kita tulis harus menempuh satu langkah pengolahan sebelum berjalan di mana pun. Langkah itu sering terasa seperti kotak tertutup: masuk .ts, keluar .js. Padahal di dalamnya terjadi dua pekerjaan yang berbeda sifatnya, dan membedakan keduanya menjelaskan banyak hal — termasuk mengapa galat tipe bisa lolos padahal program sudah dikompilasi.
Chapter ini membongkar proses tersebut: apa yang dikerjakan compiler, bagaimana anotasi tipe dihapus dari hasil keluaran, bagaimana opsi target menyesuaikan versi JavaScript hasilnya, dan bagaimana source map melacak galat kembali ke berkas .ts asal.
Dua Pekerjaan Compiler TypeScript #
TypeScript dikembangkan Microsoft, dirilis 2012, dan dikodekan sebagai proyek open source; alat baris perintahnya disebut tsc. Saat dijalankan, tsc mengerjakan dua hal yang sering disatukan dalam satu kata “kompilasi”:
- Pemeriksaan tipe (type checking) — compiler menelusuri seluruh proyek, membandingkan setiap penugasan nilai dengan deklarasi tipenya, lalu mencetak laporan galat. Pekerjaan ini tidak mengubah kode apa pun; hasilnya hanya teks laporan.
- Penghasil kode (code emission) — compiler menulis berkas
.jshasil terjemahan dari berkas.tssumber.
Konfigurasi keduanya hidup di tsconfig.json, berkas JSON di pangkal proyek yang memuat opsi seperti strict (ketatnya pemeriksaan), target (versi JavaScript keluaran), dan outDir (folder hasil). Pola pemakaian yang lazim: tsc --noEmit untuk pemeriksaan tipe tanpa menulis keluaran, dan alat lain untuk penghasil kode.
Pemisahan itu penting karena alat penerjemah juga datang dari arah lain. Bundler esbuild yang kita pelajari di section Tooling, misalnya, bisa menerjemahkan .ts menjadi .js dengan jauh lebih cepat — tetapi hanya mengerjakan pekerjaan kedua, tanpa satu pun pemeriksaan tipe. Pipeline proyek modern sering memakai keduanya: bundler yang cepat menghasilkan kode, tsc yang teliti memeriksa tipe.
Type Stripping: Anotasi Dihapus, Logika Dipertahankan #
Pertanyaan paling wajar tentang kompilasi TypeScript: ke mana anotasi tipenya pergi? Jawabannya: dibuang. Anotasi hanya ada untuk pemeriksaan; runtime JavaScript tidak mengenalinya dan tidak membutuhkannya. Keluaran compiler untuk kode yang tidak memakai fitur tua adalah kode sumber itu sendiri tanpa anotasi — proses pembuangannya disebut type stripping.
Kita bisa membuktikan idenya dengan simulator kecil. Aturannya tiga: blok interface dibuang utuh, anotasi : Tipe setelah nama dihapus, dan cast as Tipe dihapus. Berikut sumber TypeScript yang akan diproses — berkas yang sama tertanam sebagai string di dalam simulatornya:
interface Produk {
nama: string;
harga: number;
}
function label(p: Produk): string {
return p.nama + " - Rp" + p.harga;
}
const item: Produk = { nama: "Kopi Gayo", harga: 45000 };
console.log(label(item as Produk));Simulator di bawah menerapkan ketiga aturan itu, membandingkan hasilnya dengan versi JavaScript murni yang kita tulis sendiri, lalu menjalankan versi tersebut. Ini demonstrasi konsep dengan aturan terbatas — compiler sungguhan menangani seluruh bahasa, bukan hanya tiga pola:
// hapus-tipe.js — simulasi type stripping: anotasi tipe dihapus,
// logika JavaScript biasa dipertahankan dan tetap bisa dijalankan.
// Jalankan di terminal: node hapus-tipe.js
//
// Mini-simulator dengan aturan terbatas (hanya pola pada contoh di chapter):
// 1. blok interface ... { ... } dibuang utuh
// 2. anotasi ": Tipe" setelah parameter/variabel/function dihapus
// 3. cast "as Tipe" dihapus
const sumberTs = `interface Produk {
nama: string;
harga: number;
}
function label(p: Produk): string {
return p.nama + " - Rp" + p.harga;
}
const item: Produk = { nama: "Kopi Gayo", harga: 45000 };
console.log(label(item as Produk));`;
function hapusBlokInterface(teks) {
const hasil = [];
let diDalamInterface = false;
for (const baris of teks.split("\n")) {
if (baris.startsWith("interface ")) {
diDalamInterface = true;
continue;
}
if (diDalamInterface) {
if (baris === "}") {
diDalamInterface = false;
}
continue;
}
hasil.push(baris);
}
return hasil.join("\n");
}
function hapusAnotasiTipe(teks) {
return teks
.replace(/: [A-Za-z][A-Za-z0-9<>[\]]*/g, "")
.replace(/ as [A-Za-z][A-Za-z0-9<>[\]]*/g, "");
}
const hasilStripping = hapusAnotasiTipe(hapusBlokInterface(sumberTs)).trim();
console.log("=== Hasil type stripping ===");
console.log(hasilStripping);
// Versi JavaScript murni yang kita tulis sendiri sebagai pembanding.
const versiJs = `function label(p) {
return p.nama + " - Rp" + p.harga;
}
const item = { nama: "Kopi Gayo", harga: 45000 };
console.log(label(item));`;
console.log(
"Identik dengan versi JavaScript murni:",
hasilStripping === versiJs
);
// Dan versi JavaScript murni itu memang berjalan di Node tanpa alat apa pun:
function label(p) {
return p.nama + " - Rp" + p.harga;
}
const item = { nama: "Kopi Gayo", harga: 45000 };
console.log(label(item));Output yang Dihasilkan:
=== Hasil type stripping ===
function label(p) {
return p.nama + " - Rp" + p.harga;
}
const item = { nama: "Kopi Gayo", harga: 45000 };
console.log(label(item));
Identik dengan versi JavaScript murni: true
Kopi Gayo - Rp45000Mari kita telaah bagian-bagian pentingnya:
- Baris 10–20 (
const sumberTs = ...): kode TypeScript disimpan sebagai template string biasa. Ia data yang diproses, bukan kode yang dieksekusi — persis seperti berkas.tsyang dibaca compiler. - Baris 22–39 (
function hapusBlokInterface(teks) { ... }): pemeriksa berjalan baris demi baris; baris yang diawaliinterfacemembuka mode lewati, dan mode itu tertutup di baris penutup}. DeklarasiProdukikut dibuang utuh — tipe memang tidak perlu ada di keluaran. - Baris 41–45 (
function hapusAnotasiTipe(teks) { ... }): dua penggantian regex membuang: Tipedanas Tipe. Padaharga: 45000di dalam object literal, aturan tidak bersentuhan — nilainya dimulai angka, bukan huruf, sehinggaharga: 45000tetap utuh. - Baris 47 (
const hasilStripping = ... .trim()): hasil penghapusan dirapikan dari baris kosong sisa, lalu disandingkan denganversiJs— dan perbandingan menyatakantrue: keluaran stripping persis sama dengan JavaScript yang kita tulis tangan. - Baris 66–70 (
function label(p) { ... }): versi JavaScript murni dijalankan sungguhan oleh Node. Tanpa alat TypeScript, tanpa kompilasi — inilah bentuk yang dilihat runtime dari semua kode TypeScript.
Target ES: Menulis Sekali, Menyesuaikan Keluaran #
Opsi target di tsconfig.json menentukan versi JavaScript yang dihasilkan. Dengan target modern — dan browser stabil 2026 sudah mendukung ECMAScript terbaru — keluaran nyaris sama dengan sumbernya, hanya tanpa anotasi. Dengan target lama seperti ES5, compiler menurunkan fitur yang belum ada ke bentuk yang lebih tua; penurunan ini disebut downleveling.
Bentuknya bisa kita bayangkan dari satu class kecil:
class Keranjang {
items = [];
total() {
return this.items.reduce((s, i) => s + i.harga, 0);
}
}Pada target ES5, class tidak tersedia di JavaScript, sehingga compiler menuliskannya sebagai function constructor dengan method di prototype — pola yang sudah kita kenal dari section Object-Oriented JavaScript:
function Keranjang() {
this.items = [];
}
Keranjang.prototype.total = function () {
return this.items.reduce(function (s, i) {
return s + i.harga;
}, 0);
};Kedua blok di atas adalah ilustrasi bentuk, bukan salinan byte-per-byte dari tsc. Poinnya dua. Pertama, keluaran compiler ditentukan target: kode sumber yang sama bisa menjadi kode keluaran yang berbeda-beda. Kedua, makin tua target, makin jauh keluaran dari sumber — keluaran jadi lebih besar, dan pelacakan galat makin sulit tanpa bantuan berikutnya.
Source Map: Melacak Galat Kembali ke .ts #
Semakin jauh keluaran dari sumber, semakin samar stack trace: galat menunjuk keranjang.js baris hasil kompilasi, sementara kita menyunting keranjang.ts. Pemecahannya sudah kita bedah di chapter Source Maps (section Tooling dan Code Quality): berkas .map yang menyimpan pemetaan posisi keluaran ke posisi sumber.
Untuk TypeScript, rantainya sama persis. Opsi tsc --sourcemap menghasilkan keranjang.js.map di samping keluarannya, dan debugger membaca pemetaan itu secara otomatis:
tanpa peta : at total (dist/keranjang.js:1:214)
dengan peta : at total (src/keranjang.ts:5:12)Baris kedua menunjuk berkas .ts dan baris yang kita sunting. Di browser, DevTools memakai komentar sourceMappingURL di ujung berkas keluaran untuk menemukan petanya; di Node, flag --enable-source-maps mengaktifkan perilaku yang sama.
Dengan dua pekerjaan compiler, type stripping, target, dan source map, blok .ts di section ini bukan lagi tebakan: ia teks sumber yang menunggu diproses, dan prosesnya bisa kita bayangkan baris demi baris. Berikutnya kita melihat ke mana kode itu biasanya pergi: peta ringkas framework yang membangun antarmuka di atas JavaScript ini.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
hapus-tipe.js
Unduh berkas// hapus-tipe.js — simulasi type stripping: anotasi tipe dihapus,
// logika JavaScript biasa dipertahankan dan tetap bisa dijalankan.
// Jalankan di terminal: node hapus-tipe.js
//
// Mini-simulator dengan aturan terbatas (hanya pola pada contoh di chapter):
// 1. blok interface ... { ... } dibuang utuh
// 2. anotasi ": Tipe" setelah parameter/variabel/function dihapus
// 3. cast "as Tipe" dihapus
const sumberTs = `interface Produk {
nama: string;
harga: number;
}
function label(p: Produk): string {
return p.nama + " - Rp" + p.harga;
}
const item: Produk = { nama: "Kopi Gayo", harga: 45000 };
console.log(label(item as Produk));`;
function hapusBlokInterface(teks) {
const hasil = [];
let diDalamInterface = false;
for (const baris of teks.split("\n")) {
if (baris.startsWith("interface ")) {
diDalamInterface = true;
continue;
}
if (diDalamInterface) {
if (baris === "}") {
diDalamInterface = false;
}
continue;
}
hasil.push(baris);
}
return hasil.join("\n");
}
function hapusAnotasiTipe(teks) {
return teks
.replace(/: [A-Za-z][A-Za-z0-9<>[\]]*/g, "")
.replace(/ as [A-Za-z][A-Za-z0-9<>[\]]*/g, "");
}
const hasilStripping = hapusAnotasiTipe(hapusBlokInterface(sumberTs)).trim();
console.log("=== Hasil type stripping ===");
console.log(hasilStripping);
// Versi JavaScript murni yang kita tulis sendiri sebagai pembanding.
const versiJs = `function label(p) {
return p.nama + " - Rp" + p.harga;
}
const item = { nama: "Kopi Gayo", harga: 45000 };
console.log(label(item));`;
console.log(
"Identik dengan versi JavaScript murni:",
hasilStripping === versiJs
);
// Dan versi JavaScript murni itu memang berjalan di Node tanpa alat apa pun:
function label(p) {
return p.nama + " - Rp" + p.harga;
}
const item = { nama: "Kopi Gayo", harga: 45000 };
console.log(label(item));