Pengantar Peta Sumber JavaScript

Pernahkah Anda berharap agar kode sisi klien tetap dapat dibaca dan, yang lebih penting, dapat di-debug, bahkan setelah Anda menggabungkan dan mengecilkan kode tersebut, tanpa memengaruhi performa? Sekarang Anda dapat menjelajahi keajaiban peta sumber.

Peta sumber adalah cara untuk memetakan file gabungan/yang diminifikasi kembali ke status tidak dibuat. Saat membangun untuk produksi, bersama dengan minifikasi dan penggabungan file JavaScript, Anda akan menghasilkan peta sumber yang menyimpan informasi tentang file asli Anda. Saat membuat kueri untuk nomor baris dan kolom tertentu di JavaScript yang dihasilkan, Anda dapat melakukan pencarian di peta sumber yang menampilkan lokasi aslinya. Alat developer (saat ini WebKit versi malam, Google Chrome, atau Firefox 23+) dapat mengurai peta sumber secara otomatis dan membuatnya seolah-olah Anda menjalankan file yang tidak diminifikasi dan tidak digabungkan.

Demo ini memungkinkan Anda mengklik kanan di mana saja di textarea yang berisi sumber yang dihasilkan. Pilih "Dapatkan lokasi asli" akan melakukan kueri peta sumber dengan meneruskan baris dan nomor kolom yang dihasilkan, serta menampilkan posisi dalam kode asli. Pastikan konsol Anda terbuka agar Anda dapat melihat outputnya.

Contoh penerapan library peta sumber Mozilla JavaScript.

Dunia nyata

Sebelum melihat penerapan nyata Source Maps berikut, pastikan Anda telah mengaktifkan fitur peta sumber di Chrome Canary atau WebKit setiap malam dengan mengklik roda gigi setelan pada panel alat developer dan mencentang kotak "Aktifkan peta sumber" sebelumnya.

Cara mengaktifkan peta sumber di alat developer WebKit.

Firefox 23+ memiliki peta sumber yang diaktifkan secara default di alat pengembangan bawaan.

Cara mengaktifkan peta sumber di alat developer Firefox.

Mengapa saya harus memperhatikan peta sumber?

Saat ini, source mapping hanya berfungsi antara JavaScript yang tidak dikompresi/digabungkan ke JavaScript yang dikompresi/tidak digabungkan, tetapi di masa depan akan terlihat cerah dengan perbincangan bahasa yang dikompilasi ke JavaScript seperti CoffeeScript dan bahkan kemungkinan penambahan dukungan untuk praprosesor CSS seperti SASS atau LESS.

Di masa mendatang, kita dapat dengan mudah menggunakan hampir semua bahasa seolah-olah bahasa tersebut didukung secara native di browser dengan peta sumber:

  • CoffeeScript
  • ECMAScript 6 dan versi yang lebih baru
  • SASS/LESS dan lainnya
  • Hampir semua bahasa yang dikompilasi menjadi JavaScript

Lihat screencast ini dari CoffeeScript yang di-debug dalam build eksperimental konsol Firefox:

Google Web Toolkit (GWT) baru-baru ini telah menambahkan dukungan untuk Source Maps. Ray Cromwell dari tim GWT melakukan screencast luar biasa yang menunjukkan cara kerja dukungan peta sumber.

Contoh lain yang saya susun menggunakan library Traceur Google yang memungkinkan Anda menulis ES6 (ECMAScript 6 atau Next) dan mengompilasinya ke kode yang kompatibel dengan ES3. Compiler Traceur juga menghasilkan peta sumber. Lihat demo karakteristik dan class ES6 yang digunakan seolah-olah keduanya didukung secara native di browser, berkat peta sumber.

Textarea dalam demo ini juga memungkinkan Anda untuk menulis ES6 yang akan dikompilasi dengan cepat dan menghasilkan peta sumber ditambah kode ES3 yang setara.

Proses debug Traceur ES6 menggunakan peta sumber.

Demo: Tulis ES6, debug, lihat cara kerja pemetaan sumber

Bagaimana cara kerja peta sumber?

Satu-satunya compiler/minifier JavaScript yang saat ini memiliki dukungan untuk pembuatan peta sumber adalah compiler Closure. (Saya akan menjelaskan cara menggunakannya nanti.) Setelah Anda menggabungkan dan meminifikasi JavaScript, selanjutnya file peta sumber akan tersedia.

Saat ini, compiler Closure tidak menambahkan komentar khusus di bagian akhir yang diperlukan untuk menandakan alat developer browser bahwa peta sumber tersedia:

//# sourceMappingURL=/path/to/file.js.map

Hal ini memungkinkan alat developer memetakan kembali panggilan ke lokasi mereka dalam file sumber asli. Sebelumnya pragma komentar adalah //@ tetapi karena beberapa masalah dengannya dan komentar kompilasi bersyarat IE keputusan dibuat untuk mengubahnya menjadi //#. Saat ini Chrome Canary, WebKit Nightly, dan Firefox 24+ mendukung pragma komentar baru. Perubahan sintaksis ini juga memengaruhi sourceURL.

Jika Anda tidak menyukai gagasan tentang komentar yang aneh, Anda dapat mengatur {i>header<i} khusus pada file JavaScript yang telah dikompilasi:

X-SourceMap: /path/to/file.js.map

Menyukai komentar, hal ini akan memberi tahu konsumen peta sumber di mana peta sumber yang terkait dengan file JavaScript harus dicari. Header ini juga mengatasi masalah referensi peta sumber dalam bahasa yang tidak mendukung komentar baris tunggal.

Contoh WebKit Devtools untuk peta sumber aktif dan peta sumber nonaktif.

File peta sumber hanya akan didownload jika Anda telah mengaktifkan peta sumber dan alat developer Anda terbuka. Anda juga harus mengupload file asli sehingga alat developer dapat mereferensikan dan menampilkannya saat diperlukan.

Bagaimana cara membuat peta sumber?

Anda harus menggunakan Closure compiler untuk meminifikasi, menggabungkan, dan membuat peta sumber untuk file JavaScript Anda. Perintahnya adalah sebagai berikut:

java -jar compiler.jar \
--js script.js \
--create_source_map ./script-min.js.map \
--source_map_format=V3 \
--js_output_file script-min.js

Dua flag perintah yang penting adalah --create_source_map dan --source_map_format. Ini diperlukan karena versi {i>default<i} adalah V2 dan kita hanya ingin bekerja dengan V3.

Anatomi peta sumber

Untuk lebih memahami peta sumber, kita akan mengambil contoh kecil dari file peta sumber yang akan dihasilkan oleh compiler Closure dan mempelajari lebih detail tentang bagaimana "pemetaan" bagian ini. Contoh berikut adalah sedikit variasi dari contoh spesifikasi V3.

{
    version : 3,
    file: "out.js",
    sourceRoot : "",
    sources: ["foo.js", "bar.js"],
    names: ["src", "maps", "are", "fun"],
    mappings: "AAgBC,SAAQ,CAAEA"
}

Di atas, Anda dapat melihat bahwa peta sumber adalah literal objek yang berisi banyak info penting:

  • Nomor versi yang didasarkan pada peta sumber
  • Nama file dari kode yang dihasilkan (File produksi gabungan/minim Anda)
  • sourceRoot memungkinkan Anda menambahkan struktur folder ke sumbernya - ini juga merupakan teknik penghematan ruang
  • berisi semua nama file yang digabungkan
  • berisi semua nama variabel/metode yang muncul di seluruh kode Anda.
  • Terakhir, properti pemetaan adalah tempat keajaiban terjadi menggunakan nilai Base64 VLQ. Penghematan ruang yang sebenarnya dilakukan di sini.

Base64 VLQ dan menjaga agar peta sumber tetap kecil

Awalnya, spesifikasi peta sumber memiliki output yang sangat panjang dari semua pemetaan dan menghasilkan peta sumber sekitar 10 kali ukuran kode yang dihasilkan. Versi dua menguranginya sekitar 50% dan versi tiga menguranginya lagi hingga 50% lagi, jadi untuk file 133kB Anda akan mendapatkan source map ~300kB.

Jadi bagaimana mereka mengurangi ukuran sambil tetap mempertahankan pemetaan yang kompleks?

VLQ (Jumlah Panjang Variabel) digunakan bersama dengan mengenkode nilai ke dalam nilai Base64. Properti pemetaan adalah string yang sangat besar. Dalam string ini terdapat titik koma (;) yang mewakili nomor baris dalam file yang dihasilkan. Dalam setiap baris terdapat koma (,) yang mewakili setiap segmen dalam baris tersebut. Masing-masing segmen ini adalah 1, 4 atau 5 dalam isian panjang yang bervariasi. Beberapa mungkin tampak lebih panjang tetapi berisi bit kelanjutan. Setiap segmen dibuat berdasarkan segmen sebelumnya, sehingga membantu mengurangi ukuran file karena setiap bit terkait dengan segmen sebelumnya.

Pengelompokan segmen dalam file JSON peta sumber.

Seperti disebutkan di atas, setiap segmen dapat memiliki panjang variabel 1, 4 atau 5. Diagram ini dianggap sebagai variabel panjang empat dengan satu bit kelanjutan (g). Kami akan mengelompokkan segmen ini dan menunjukkan kepada Anda bagaimana peta sumber menghasilkan lokasi asalnya.

Nilai yang ditampilkan di atas sepenuhnya merupakan nilai hasil dekode Base64. Ada beberapa pemrosesan lainnya untuk mendapatkan nilai sebenarnya. Setiap segmen biasanya membahas lima hal:

  • Kolom yang dihasilkan
  • File asli tempat ini muncul
  • Nomor baris asli
  • Kolom asli
  • Dan, jika tersedia, nama asli

Tidak setiap segmen memiliki nama, nama metode, atau argumen, jadi seluruh segmen akan beralih antara empat hingga lima panjang variabel. Nilai g dalam diagram segmen di atas disebut sebagai bit kelanjutan yang memungkinkan pengoptimalan lebih lanjut pada tahap decoding Base64 VLQ. Bit kelanjutan memungkinkan Anda membuat nilai segmen sehingga Anda dapat menyimpan angka yang besar tanpa harus menyimpan angka yang besar. Teknik penghematan ruang yang sangat cerdas ini berakar pada format midi.

Diagram AAgBC di atas setelah diproses lebih lanjut akan menampilkan 0, 0, 32, 16, 1 - 32 menjadi bit kelanjutan yang membantu membangun nilai 16 berikut. B yang sepenuhnya didekode dalam Base64 adalah 1. Jadi nilai penting yang digunakan adalah 0, 0, 16, 1. Ini kemudian memungkinkan kita tahu bahwa baris 1 (baris terus dihitung oleh titik koma) kolom 0 dari peta file yang dihasilkan ke file 0 (array file 0 adalah foo.js), baris 16 di kolom 1.

Untuk menunjukkan cara segmen didekode, saya akan merujuk ke library JavaScript Source Map Mozilla. Anda juga dapat melihat kode pemetaan sumber alat developer WebKit, yang juga ditulis dalam JavaScript.

Untuk memahami dengan benar bagaimana kita mendapatkan nilai 16 dari B, kita perlu memiliki pemahaman dasar tentang operator bitwise dan cara kerja spesifikasi untuk pemetaan sumber. Digit sebelumnya, g, ditandai sebagai bit kelanjutan dengan membandingkan digit (32) dan VLQ_CONTINUATION_BIT (biner 100000 atau 32) menggunakan operator AND (&) bitwise.

32 & 32 = 32
// or
100000
|
|
V
100000

Proses ini mengembalikan angka 1 di setiap posisi bit tempat keduanya muncul. Jadi, nilai 33 & 32 yang didekode Base64 akan menampilkan 32 karena hanya berbagi lokasi 32 bit seperti yang dapat Anda lihat pada diagram di atas. Tindakan ini kemudian akan meningkatkan nilai pergeseran bit sebesar 5 untuk setiap bit lanjutan sebelumnya. Dalam kasus di atas, hanya bergeser 5 sekali, jadi meninggalkan pergeseran 1 (B) sebesar 5.

1 <<../ 5 // 32

// Shift the bit by 5 spots
______
|    |
V    V
100001 = 100000 = 32

Nilai tersebut kemudian dikonversi dari nilai yang ditandatangani VLQ dengan menggeser ke kanan angka (32) satu titik.

32 >> 1 // 16
//or
100000
|
 |
 V
010000 = 16

Seperti itu: begitulah cara Anda mengubah 1 menjadi 16. Ini mungkin tampak sebagai proses yang terlalu rumit, tetapi semakin besar angkanya, semakin masuk akal.

Potensi masalah XSSI

Spesifikasi tersebut menyebutkan masalah penyertaan skrip lintas situs yang dapat muncul dari konsumsi peta sumber. Untuk mengurangi hal ini, sebaiknya tambahkan awalan ")]}" di baris pertama peta sumber Anda untuk dengan sengaja membatalkan validasi JavaScript sehingga error sintaksis akan ditampilkan. Alat developer WebKit sudah dapat menangani hal ini.

if (response.slice(0, 3) === ")]}") {
    response = response.substring(response.indexOf('\n'));
}

Seperti ditunjukkan di atas, tiga karakter pertama dipotong untuk memeriksa apakah cocok dengan error sintaks dalam spesifikasi dan jika demikian akan menghapus semua karakter yang mengarah ke entity baris baru pertama (\n).

Penerapan sourceURL dan displayName: Fungsi evaluasi dan anonim

Meskipun bukan bagian dari spesifikasi peta sumber, dua konvensi berikut memungkinkan Anda membuat pengembangan jauh lebih mudah saat menggunakan evaluasi dan fungsi anonim.

Helper pertama terlihat sangat mirip dengan properti //# sourceMappingURL dan sebenarnya disebutkan dalam spesifikasi peta sumber V3. Dengan menyertakan komentar khusus berikut dalam kode Anda, yang akan dievaluasi, Anda bisa memberi nama evaluasi agar muncul sebagai nama yang lebih logis di alat pengembangan. Lihat demo sederhana menggunakan compiler CoffeeScript:

Demo: Melihat kode eval() yang ditampilkan sebagai skrip melalui sourceURL

//# sourceURL=sqrt.coffee
Tampilan komentar khusus sourceURL di alat developer

Helper lainnya memungkinkan Anda memberi nama fungsi anonim dengan menggunakan properti displayName yang tersedia pada konteks fungsi anonim saat ini. Buat profil demo berikut untuk melihat cara kerja properti displayName.

btns[0].addEventListener("click", function(e) {
    var fn = function() {
        console.log("You clicked button number: 1");
    };

    fn.displayName = "Anonymous function of button 1";

    return fn();
}, false);
Menampilkan cara kerja properti displayName.

Saat membuat profil kode dalam alat developer, properti displayName akan ditampilkan, bukan seperti (anonymous). Namun, displayName hampir mati di dalam air dan tidak akan berhasil masuk ke Chrome. Namun, semua harapan tidak hilang dan proposal yang jauh lebih baik telah disarankan bernama debugName.

Saat penulisan, penamaan evaluasi hanya tersedia di browser Firefox dan WebKit. Properti displayName hanya ada di malam WebKit.

Ayo berjuang bersama

Saat ini ada diskusi yang sangat panjang tentang dukungan peta sumber yang ditambahkan ke CoffeeScript. Periksa masalahnya dan tambahkan dukungan Anda untuk menambahkan pembuatan peta sumber ke compiler CoffeeScript. Ini akan menjadi keuntungan besar bagi CoffeeScript dan pengikut setianya.

UglifyJS juga memiliki masalah peta sumber yang harus Anda lihat juga.

Banyak alat menghasilkan peta sumber, termasuk compiler Coffeescript. Aku sudah menganggap hal ini sebagai perdebatan.

Semakin banyak alat yang tersedia bagi kami yang dapat menghasilkan peta sumber, semakin baik hasil yang kami peroleh, jadi teruskan dan minta atau tambahkan dukungan peta sumber ke proyek open source favorit Anda.

Tidak sempurna

Satu hal yang tidak dapat dipenuhi peta sumber saat ini adalah ekspresi smartwatch. Masalahnya adalah bahwa mencoba memeriksa argumen atau nama variabel dalam konteks eksekusi saat ini tidak akan mengembalikan apa pun karena hal itu tidak benar-benar ada. Ini akan membutuhkan semacam pemetaan terbalik untuk mencari nama sebenarnya dari argumen/variabel yang ingin Anda periksa dibandingkan dengan argumen/nama variabel sebenarnya dalam JavaScript yang Anda kompilasi.

Ini tentu saja merupakan masalah yang dapat dipecahkan dan dengan lebih memperhatikan peta sumber, kita dapat mulai melihat beberapa fitur yang luar biasa dan stabilitas yang lebih baik.

Masalah

Baru-baru ini, jQuery 1.9 menambahkan dukungan untuk peta sumber saat disalurkan dari CDN resmi. Error ini juga menunjukkan bug aneh saat komentar kompilasi bersyarat IE (//@cc_on) digunakan sebelum jQuery dimuat. Sejak saat itu, ada komitmen untuk mengurangi hal ini dengan menggabungkan sourceMappingURL dalam komentar multi-baris. Pelajaran yang akan dipelajari tidak menggunakan komentar bersyarat.

Masalah ini telah telah ditangani dengan mengubah sintaksis menjadi //#.

Alat dan referensi

Berikut adalah beberapa referensi dan alat lebih lanjut yang harus Anda lihat:

  • Nick Fitzgerald memiliki fork UglifyJS dengan dukungan peta sumber
  • Paul Irlandia memiliki demo kecil praktis yang menampilkan peta sumber
  • Lihat kumpulan perubahan WebKit tentang kapan ini diturunkan
  • Kumpulan perubahan juga mencakup pengujian tata letak yang memulai seluruh artikel ini
  • Mozilla memiliki bug yang harus Anda ikuti terkait status peta sumber di konsol bawaan
  • Conrad Irwin telah menulis source map gem yang sangat berguna untuk semua pengguna Ruby
  • Beberapa bacaan lebih lanjut tentang penamaan eval dan properti displayName
  • Anda dapat melihat sumber Closure Compiler untuk membuat peta sumber
  • Ada beberapa screenshot dan pembahasan dukungan untuk peta sumber GWT

Peta sumber adalah utilitas yang sangat canggih di set alat developer. Sangat berguna untuk membuat aplikasi web Anda tetap ramping tetapi mudah di-debug. Produk ini juga merupakan alat pembelajaran yang sangat berguna bagi developer baru untuk melihat seberapa berpengalaman developer dalam membangun dan menulis aplikasi mereka tanpa harus mengacaukan kode yang diminifikasi yang tidak dapat dibaca.

Tunggu apa lagi? Mulai buat peta sumber untuk semua project sekarang.