Pengguna etalase toko mengirim tangkapan layar berisi laporan kesalahan: baris galatnya menunjuk galat.min.js:1:146. Siti membuka berkas itu dan menemukan satu baris teks yang membentang ratusan kolom, penuh nama variabel singkat seperti n, e, dan o. Galat yang sesungguhnya ada di galat.js baris 3 — tetapi tidak ada petunjuk apa pun yang menghubungkan keduanya.
Inilah persoalan yang dijawab source map: berkas peta yang menyimpan hubungan antara setiap posisi di kode hasil build dan posisi asalnya di kode sumber. Dengan peta itu, DevTools di browser maupun Node dapat menampilkan stack trace yang menunjuk baris dan nama asli. Chapter ini merekonstruksi laporan Siti langkah demi langkah: melihat masalahnya, membaca isi peta, lalu mengaktifkan pemetaannya.
Masalah: Kode Hasil Build Sulit Dilacak #
Berkas sumber kita sengaja dibuat ringkas — sebuah fungsi yang melempar Error bila pembaginya nol:
function bagiTotal(total, jumlahOrang) {
if (jumlahOrang === 0) {
throw new Error("pembagi tidak boleh nol");
}
return total / jumlahOrang;
}
console.log(bagiTotal(150000, 0));Bundler esbuild (dibahas penuh di chapter berikutnya) mengompilasi berkas ini menjadi versi mini:
npx --yes esbuild@latest galat.js --bundle --minify --sourcemap --outfile=dist/galat.min.jsOutput yang Dihasilkan:
dist/galat.min.js 266b
dist/galat.min.js.map 524b
⚡ Done in 2msDua berkas tercipta di folder dist. Yang pertama, galat.min.js, isinya satu baris panjang — nama fungsi dipangkas menjadi r, spasi dihilangkan, dan kode bantu bundler ditambahkan:
(()=>{var n=(e,o)=>()=>{try{return o||e((o={exports:{}}).exports,o),o.exports}catch(i){throw o=0,i}};var t=n(()=>{function r(e,o){if(o===0)throw new Error("pembagi tidak boleh nol");return e/o}console.log(r(15e4,0))});t();})();
//# sourceMappingURL=galat.min.js.map
Menjalankannya di Node memperlihatkan stack trace yang persis seperti keluhan pengguna:
node dist/galat.min.jsOutput yang Dihasilkan:
/tmp/tooling-final/dist/galat.min.js:1
(()=>{var n=(e,o)=>()=>{try{return o||e((o={exports:{}}).exports,o),o.exports}catch(i){throw o=0,i}};var t=n(()=>{function r(e,o){if(o===0)throw new Error("pembagi tidak boleh nol");return e/o}console.log(r(15e4,0))});t();})();
^
Error: pembagi tidak boleh nol
at r (/tmp/tooling-final/dist/galat.min.js:1:146)
at /tmp/tooling-final/dist/galat.min.js:1:206
at /tmp/tooling-final/dist/galat.min.js:1:39
at /tmp/tooling-final/dist/galat.min.js:1:219
at Object.<anonymous> (/tmp/tooling-final/dist/galat.min.js:1:225)
at Module._compile (node:internal/modules/cjs/loader:1929:14)
at Object..js (node:internal/modules/cjs/loader:2060:10)
at Module.load (node:internal/modules/cjs/loader:1651:32)
at Module._load (node:internal/modules/cjs/loader:1443:12)
at wrapModuleLoad (node:internal/modules/cjs/loader:261:19)
Node.js v24.21.0Frame pertama, at r (...galat.min.js:1:146), membaca nama r dan posisi kolom 146 — teknisnya benar, tetapi tidak mengatakan apa-apa tentang bagiTotal di galat.js. Tanpa peta, inilah yang dilihat debugger.
Anatomi Berkas .map #
Berkas kedua hasil build, galat.min.js.map, adalah teks JSON. Berikut isi lengkapnya — alat yang membuatnya adalah esbuild; peta tidak pernah ditulis tangan:
{
"version": 3,
"sources": ["../galat.js"],
"sourcesContent": ["function bagiTotal(total, jumlahOrang) {\n if (jumlahOrang === 0) {\n throw new Error(\"pembagi tidak boleh nol\");\n }\n return total / jumlahOrang;\n}\nconsole.log(bagiTotal(150000, 0));\n"],
"mappings": "qGAAA,IAAAA,EAAAC,EAAA,cAASC,EAAUC,EAAOC,EAAa,CACrC,GAAIA,IAAgB,EAClB,MAAM,IAAI,MAAM,yBAAyB,EAE3C,OAAOD,EAAQC,CACjB,CACA,QAAQ,IAAIF,EAAU,KAAQ,CAAC,CAAC",
"names": ["require_galat", "__commonJSMin", "bagiTotal", "total", "jumlahOrang"]
}"version": 3: nomor spesifikasi format peta yang dipakai — versi 3 adalah format standar yang dibaca semua alat modern."sources": daftar berkas sumber, ditulis relatif terhadap folder peta."../galat.js"berarti satu folder di atasdist/."sourcesContent": isi lengkap berkas sumber, disematkan ke dalam peta. Karena itu debugger bisa menampilkan kode asli tanpa mengunduh ulang berkas sumbernya."mappings": inti dari peta — rangkaian angka terenkode yang menghubungkan setiap posisi di berkas hasil build dengan baris-kolom di berkas sumber. Enkodenya disebut base64 VLQ: nilai-nilai kecil dikemas rapat agar ukuran peta tetap hemat. Kita tidak pernah menghitungnya manual; debugger yang menerjemahkannya."names": kumpulan nama variabel dan fungsi asli yang dirujuk pemetaan — dari daftar inilah namabagiTotaldipulihkan darir.
//# sourceMappingURL: Tautan di Ujung Berkas #
Bagaimana debugger tahu bahwa peta tersedia? Jawabannya ada di baris terakhir galat.min.js:
//# sourceMappingURL=galat.min.js.map
Komentar khusus ini adalah kontrak baku: nama berkas peta ditulis relatif terhadap folder berkas yang memuatnya. Saat DevTools browser memuat galat.min.js, ia membaca komentar tersebut, mengambil galat.min.js.map, dan menyediakan pemetaan secara otomatis — tanpa pengaturan apa pun dari kita. Di browser, panel Sources DevTools kemudian menampilkan galat.js yang asli, lengkap dengan breakpoint di baris aslinya. Node melakukan hal yang sama setelah diaktifkan.
Membaca Stack Trace yang Terpetakan #
Node membaca peta bila dijalankan dengan flag --enable-source-maps. Berkasnya sama persis dengan percobaan pertama — hanya flag yang berubah:
node --enable-source-maps dist/galat.min.jsOutput yang Dihasilkan:
/tmp/tooling-final/dist/galat.min.js:1
(()=>{var n=(e,o)=>()=>{try{return o||e((o={exports:{}}).exports,o),o.exports}catch(i){throw o=0,i}};var t=n(()=>{function r(e,o){if(o===0)throw new Error("pembagi tidak boleh nol");return e/o}console.log(r(15e4,0))});t();})();
^
Error: pembagi tidak boleh nol
at bagiTotal (/tmp/tooling-final/galat.js:3:11)
at <anonymous> (/tmp/tooling-final/galat.js:7:13)
at /tmp/tooling-final/dist/galat.min.js:1:39
at bagiTotal (/tmp/tooling-final/galat.js:7:33)
at Object.<anonymous> (/tmp/tooling-final/galat.js:7:33)
at Module._compile (node:internal/modules/cjs/loader:1929:14)
at Object..js (node:internal/modules/cjs/loader:2060:10)
at Module.load (node:internal/modules/cjs/loader:1651:32)
at Module._load (node:internal/modules/cjs/loader:1443:12)
at wrapModuleLoad (node:internal/modules/cjs/loader:261:19)
Node.js v24.21.0Laporan yang sama kini terbaca seperti kode sumbernya:
at bagiTotal (/tmp/tooling-final/galat.js:3:11): frame yang tadinyaat r ... :1:146kini menampilkan nama aslibagiTotal, berkas asligalat.js, dan baris 3 — persis baristhrow new Error(...)tempat galat dilempar. Node membacamappings, menerjemahkan posisi kolom 146, dan mengganti tampilannya.at <anonymous> (galat.js:7:13): frame pemanggil juga terpetakan — baris 7 berisiconsole.log(bagiTotal(...)), tempat kejadian dari sisi pemanggil.at ...dist/galat.min.js:1:39: satu frame tetap menunjuk berkas hasil build. Frame itu berada di kode bantu bundler yang tidak punya padanan langsung di berkas sumber, sehingga peta tidak mengubahnya. Stack trace nyata sering berisi campuran seperti ini — yang penting frame milik kode kita sudah terbaca.
Satu pertimbangan menutup chapter ini: peta yang dipublikasikan bersama kode hasil build membawa sourcesContent — artinya kode sumber asli ikut terbaca oleh siapa pun yang membukanya. Untuk aplikasi publik, tim biasanya mengunggah peta hanya ke layanan pelacak galat, bukan ke folder yang bisa diakses publik.
Yang kita jalankan tadi — perintah esbuild yang menghasilkan dist/ beserta petanya — adalah kerja seorang bundler. Chapter berikutnya membedah perannya secara utuh.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
galat.js
Unduh berkasfunction bagiTotal(total, jumlahOrang) {
if (jumlahOrang === 0) {
throw new Error("pembagi tidak boleh nol");
}
return total / jumlahOrang;
}
console.log(bagiTotal(150000, 0));