Kompilasi Lanjutan

Ringkasan

Menggunakan Closure Compiler dengan compilation_level ADVANCED_OPTIMIZATIONS menawarkan rasio kompresi yang lebih baik daripada kompilasi dengan SIMPLE_OPTIMIZATIONS atau WHITESPACE_ONLY. Kompilasi dengan ADVANCED_OPTIMIZATIONS mencapai kompresi ekstra dengan lebih agresif dalam cara mengubah kode dan mengganti nama simbol. Namun, pendekatan yang lebih agresif ini berarti Anda harus lebih berhati-hati saat menggunakan ADVANCED_OPTIMIZATIONS untuk memastikan kode output berfungsi dengan cara yang sama seperti kode input.

Tutorial ini menggambarkan fungsi tingkat kompilasi ADVANCED_OPTIMIZATIONS dan apa yang dapat Anda lakukan untuk memastikan kode Anda berfungsi setelah dikompilasi dengan ADVANCED_OPTIMIZATIONS. Bagian ini juga memperkenalkan konsep extern: simbol yang ditentukan dalam kode di luar kode yang diproses oleh compiler.

Sebelum membaca tutorial ini, Anda harus memahami proses mengompilasi JavaScript dengan salah satu alat Closure Compiler, seperti aplikasi compiler berbasis Java.

Catatan tentang terminologi: tanda command line --compilation_level mendukung singkatan ADVANCED dan SIMPLE yang lebih umum digunakan serta ADVANCED_OPTIMIZATIONS dan SIMPLE_OPTIMIZATIONS yang lebih presisi. Dokumen ini menggunakan bentuk yang lebih panjang, tetapi nama tersebut dapat digunakan secara bergantian di command line.

  1. Kompresi yang Lebih Baik
  2. Cara Mengaktifkan ADVANCED_OPTIMIZATIONS
  3. Hal yang Harus Diperhatikan Saat Menggunakan ADVANCED_OPTIMIZATIONS
    1. Penghapusan Kode yang Ingin Anda Pertahankan
    2. Nama Properti yang Tidak Konsisten
    3. Mengompilasi Dua Bagian Kode Secara Terpisah
    4. Referensi yang Rusak antara Kode yang Dikompilasi dan Tidak Dikompilasi

Kompresi yang Lebih Baik

Dengan tingkat kompilasi default SIMPLE_OPTIMIZATIONS, Closure Compiler membuat JavaScript lebih kecil dengan mengganti nama variabel lokal. Ada simbol selain variabel lokal yang dapat disingkat, dan ada cara untuk mengecilkan kode selain mengganti nama simbol. Kompilasi dengan ADVANCED_OPTIMIZATIONS memanfaatkan berbagai kemungkinan penyingkatan kode.

Bandingkan output untuk SIMPLE_OPTIMIZATIONS dan ADVANCED_OPTIMIZATIONS untuk kode berikut:

function unusedFunction(note) {
  alert(note['text']);
}

function displayNoteTitle(note) {
  alert(note['title']);
}

var flowerNote = {};
flowerNote['title'] = "Flowers";
displayNoteTitle(flowerNote);

Kompilasi dengan SIMPLE_OPTIMIZATIONS akan memperpendek kode menjadi seperti ini:

function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);

Kompilasi dengan ADVANCED_OPTIMIZATIONS sepenuhnya memperpendek kode menjadi seperti ini:

alert("Flowers");

Kedua skrip ini menghasilkan pemberitahuan yang berbunyi "Flowers", tetapi skrip kedua jauh lebih kecil.

Tingkat ADVANCED_OPTIMIZATIONS melampaui sekadar memendekkan nama variabel dalam beberapa cara, termasuk:

  • penggantian nama yang lebih agresif:

    Kompilasi dengan SIMPLE_OPTIMIZATIONS saja mengganti nama parameter note dari fungsi displayNoteTitle() dan unusedFunction(), karena ini adalah satu-satunya variabel dalam skrip yang bersifat lokal untuk fungsi. ADVANCED_OPTIMIZATIONS juga mengganti nama variabel global flowerNote.

  • penghapusan kode tidak terpakai:

    Kompilasi dengan ADVANCED_OPTIMIZATIONS akan menghapus fungsi unusedFunction() sepenuhnya, karena tidak pernah dipanggil dalam kode.

  • penyisipan fungsi:

    Kompilasi dengan ADVANCED_OPTIMIZATIONS menggantikan panggilan ke displayNoteTitle() dengan alert() tunggal yang menyusun isi fungsi. Penggantian panggilan fungsi dengan isi fungsi ini dikenal sebagai "inlining". Jika fungsinya lebih panjang atau lebih rumit, menyisipkannya dapat mengubah perilaku kode, tetapi Closure Compiler menentukan bahwa dalam kasus ini, menyisipkan aman dan menghemat ruang. Kompilasi dengan ADVANCED_OPTIMIZATIONS juga menyisipkan konstanta dan beberapa variabel saat menentukan bahwa hal itu dapat dilakukan dengan aman.

Daftar ini hanyalah contoh transformasi yang mengurangi ukuran yang dapat dilakukan kompilasi ADVANCED_OPTIMIZATIONS.

Cara Mengaktifkan ADVANCED_OPTIMIZATIONS

Untuk mengaktifkan ADVANCED_OPTIMIZATIONS untuk aplikasi Closure Compiler, sertakan flag command line --compilation_level ADVANCED_OPTIMIZATIONS, seperti pada perintah berikut:

java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js

Yang Perlu Diperhatikan Saat Menggunakan ADVANCED_OPTIMIZATIONS

Di bawah ini tercantum beberapa efek tidak diinginkan umum dari ADVANCED_OPTIMIZATIONS, dan langkah-langkah yang dapat Anda lakukan untuk menghindarinya.

Menghapus Kode yang Ingin Anda Pertahankan

Jika Anda mengompilasi hanya fungsi di bawah dengan ADVANCED_OPTIMIZATIONS, Closure Compiler akan menghasilkan output kosong:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}

Karena fungsi tidak pernah dipanggil di JavaScript yang Anda teruskan ke compiler, Closure Compiler mengasumsikan bahwa kode ini tidak diperlukan.

Dalam banyak kasus, perilaku ini persis seperti yang Anda inginkan. Misalnya, jika Anda mengompilasi kode bersama dengan library besar, Closure Compiler dapat menentukan fungsi dari library tersebut yang benar-benar Anda gunakan dan menghapus fungsi yang tidak Anda gunakan.

Namun, jika Anda mendapati bahwa Closure Compiler menghapus fungsi yang ingin Anda pertahankan, ada dua cara untuk mencegahnya:

  • Pindahkan panggilan fungsi Anda ke dalam kode yang diproses oleh Closure Compiler.
  • Sertakan eksternal untuk fungsi yang ingin Anda ekspos.

Bagian berikutnya membahas setiap opsi secara lebih mendetail.

Solusi: Memindahkan Panggilan Fungsi ke dalam Kode yang Diproses oleh Closure Compiler

Anda dapat mengalami penghapusan kode yang tidak diinginkan jika hanya mengompilasi sebagian kode dengan Closure Compiler. Misalnya, Anda mungkin memiliki file library yang hanya berisi definisi fungsi, dan file HTML yang menyertakan library dan yang berisi kode yang memanggil fungsi tersebut. Dalam hal ini, jika Anda mengompilasi file library dengan ADVANCED_OPTIMIZATIONS, Closure Compiler akan menghapus semua fungsi library Anda.

Solusi paling sederhana untuk masalah ini adalah mengompilasi fungsi Anda bersama dengan bagian program yang memanggil fungsi tersebut. Misalnya, Closure Compiler tidak akan menghapus displayNoteTitle() saat mengompilasi program berikut:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
displayNoteTitle({'myTitle': 'Flowers'});

Fungsi displayNoteTitle() tidak dihapus dalam kasus ini karena Closure Compiler melihat bahwa fungsi tersebut dipanggil.

Dengan kata lain, Anda dapat mencegah penghapusan kode yang tidak diinginkan dengan menyertakan titik entri program dalam kode yang Anda teruskan ke Closure Compiler. Titik entri program adalah tempat dalam kode saat program mulai dieksekusi. Misalnya, dalam program catatan bunga dari bagian sebelumnya, tiga baris terakhir dieksekusi segera setelah JavaScript dimuat di browser. Ini adalah titik entri untuk program ini. Untuk menentukan kode yang perlu Anda pertahankan, Closure Compiler dimulai dari titik entri ini dan melacak alur kontrol program ke depan dari sana.

Solusi: Sertakan Eksternal untuk Fungsi yang Ingin Anda Ekspos

Informasi selengkapnya tentang solusi ini diberikan di bawah dan di halaman tentang eksternal dan ekspor.

Nama Properti yang Tidak Konsisten

Kompilasi Closure Compiler tidak pernah mengubah literal string dalam kode Anda, apa pun tingkat kompilasi yang Anda gunakan. Artinya, kompilasi dengan ADVANCED_OPTIMIZATIONS memperlakukan properti secara berbeda bergantung pada apakah kode Anda mengaksesnya dengan string. Jika Anda mencampur referensi string ke properti dengan referensi sintaksis titik, Closure Compiler akan mengganti nama beberapa referensi ke properti tersebut, tetapi tidak yang lainnya. Akibatnya, kode Anda mungkin tidak berjalan dengan benar.

Misalnya, perhatikan kode berikut:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
var flowerNote = {};
flowerNote.myTitle = 'Flowers';

alert(flowerNote.myTitle);
displayNoteTitle(flowerNote);

Dua pernyataan terakhir dalam kode sumber ini melakukan hal yang sama persis. Namun, saat Anda memadatkan kode dengan ADVANCED_OPTIMIZATIONS, Anda akan mendapatkan ini:

var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);

Pernyataan terakhir dalam kode yang dikompresi menghasilkan error. Referensi langsung ke properti myTitle telah diganti namanya menjadi a, tetapi referensi yang dikutip ke myTitle dalam fungsi displayNoteTitle belum diganti namanya. Akibatnya, pernyataan terakhir merujuk pada properti myTitle yang sudah tidak ada.

Solusi: Konsisten dalam Nama Properti Anda

Solusi ini cukup sederhana. Untuk setiap jenis atau objek tertentu, gunakan sintaks titik atau string yang dikutip secara eksklusif. Jangan mencampur sintaksis, terutama dalam referensi ke properti yang sama.

Selain itu, jika memungkinkan, sebaiknya gunakan sintaksis titik, karena mendukung pemeriksaan dan pengoptimalan yang lebih baik. Gunakan akses properti string yang dikutip hanya jika Anda tidak ingin Closure Compiler melakukan penggantian nama, seperti saat nama berasal dari sumber luar, seperti JSON yang didekode.

Mengompilasi Dua Bagian Kode Secara Terpisah

Jika membagi aplikasi menjadi beberapa bagian kode yang berbeda, Anda mungkin ingin mengompilasi bagian-bagian tersebut secara terpisah. Namun, jika dua bagian kode berinteraksi, hal itu dapat menimbulkan kesulitan. Meskipun Anda berhasil, output dari dua proses Closure Compiler tidak akan kompatibel.

Misalnya, asumsikan bahwa aplikasi dibagi menjadi dua bagian: bagian yang mengambil data, dan bagian yang menampilkan data.

Berikut kode untuk mengambil data:

function getData() {
  // In an actual project, this data would be retrieved from the server.
  return {title: 'Flower Care', text: 'Flowers need water.'};
}

Berikut kode untuk menampilkan data:

var displayElement = document.getElementById('display');
function displayData(parent, data) {
  var textElement = document.createTextNode(data.text);
  parent.appendChild(textElement);
}
displayData(displayElement, getData());

Jika Anda mencoba mengompilasi dua bagian kode ini secara terpisah, Anda akan menemui beberapa masalah. Pertama, Closure Compiler menghapus fungsi getData(), karena alasan yang dijelaskan dalam Penghapusan Kode yang Ingin Anda Pertahankan. Kedua, Closure Compiler menghasilkan error fatal saat memproses kode yang menampilkan data.

input:6: ERROR - variable getData is undefined
displayData(displayElement, getData());

Karena compiler tidak memiliki akses ke fungsi getData() saat mengompilasi kode yang menampilkan data, compiler memperlakukan getData sebagai tidak ditentukan.

Solusi: Kompilasi Semua Kode untuk Halaman Secara Bersama-sama

Untuk memastikan kompilasi yang tepat, kompilasi semua kode untuk halaman secara bersamaan dalam satu proses kompilasi. Compiler Closure dapat menerima beberapa file JavaScript dan string JavaScript sebagai input, sehingga Anda dapat meneruskan kode library dan kode lainnya bersama-sama dalam satu permintaan kompilasi.

Catatan: Pendekatan ini tidak akan berfungsi jika Anda perlu menggabungkan kode yang dikompilasi dan tidak dikompilasi. Lihat Referensi yang Rusak antara Kode yang Dikompilasi dan Tidak Dikompilasi untuk mendapatkan tips tentang cara menangani situasi ini.

Referensi yang Rusak antara Kode yang Dikompilasi dan Tidak Dikompilasi

Penggantian nama simbol di ADVANCED_OPTIMIZATIONS akan merusak komunikasi antara kode yang diproses oleh Closure Compiler dan kode lainnya. Kompilasi mengganti nama fungsi yang ditentukan dalam kode sumber Anda. Kode eksternal yang memanggil fungsi Anda akan rusak setelah Anda mengompilasi, karena masih merujuk ke nama fungsi lama. Demikian pula, referensi dalam kode yang dikompilasi ke simbol yang ditentukan secara eksternal dapat diubah oleh Closure Compiler.

Perlu diingat bahwa "kode yang tidak dikompilasi" mencakup kode apa pun yang diteruskan ke fungsi eval() sebagai string. Closure Compiler tidak pernah mengubah literal string dalam kode, sehingga Closure Compiler tidak mengubah string yang diteruskan ke pernyataan eval().

Perhatikan bahwa ini adalah masalah terkait, tetapi berbeda: mempertahankan komunikasi yang dikompilasi ke eksternal, dan mempertahankan komunikasi eksternal ke yang dikompilasi. Masalah terpisah ini memiliki solusi umum, tetapi ada nuansa di setiap sisi. Untuk mendapatkan hasil maksimal dari Closure Compiler, Anda harus memahami kasus yang Anda miliki.

Sebelum melanjutkan, sebaiknya Anda memahami extern dan ekspor.

Solusi untuk Memanggil Kode Eksternal dari Kode yang Dikompilasi: Mengompilasi dengan Externs

Jika Anda menggunakan kode yang dimasukkan ke halaman oleh skrip lain, Anda harus memastikan bahwa Closure Compiler tidak mengganti nama referensi Anda ke simbol yang ditentukan dalam library eksternal tersebut. Untuk melakukannya, sertakan file yang berisi extern untuk library eksternal ke dalam kompilasi Anda. Hal ini akan memberi tahu Closure Compiler nama mana yang tidak Anda kontrol dan oleh karena itu tidak dapat diubah. Kode Anda harus menggunakan nama yang sama dengan yang digunakan file eksternal.

Contoh umumnya adalah API seperti OpenSocial API dan Google Maps API. Misalnya, jika kode Anda memanggil fungsi OpenSocial opensocial.newDataRequest(), tanpa eksternal yang sesuai, Closure Compiler akan mengubah panggilan ini menjadi a.b().

Solusi untuk Memanggil Kode yang Dikompilasi dari Kode Eksternal: Menerapkan Eksternal

Jika memiliki kode JavaScript yang Anda gunakan kembali sebagai library, Anda mungkin ingin menggunakan Closure Compiler untuk mengecilkan hanya library sambil tetap mengizinkan kode yang tidak dikompilasi untuk memanggil fungsi di library.

Solusi dalam situasi ini adalah menerapkan serangkaian eksternal yang menentukan API publik library Anda. Kode Anda akan memberikan definisi untuk simbol yang dideklarasikan dalam eksternal ini. Artinya, semua class atau fungsi yang disebutkan oleh eksternal Anda. Hal ini juga dapat berarti membuat class Anda menerapkan antarmuka yang dideklarasikan di extern.

Eksternalitas ini juga berguna bagi orang lain, bukan hanya diri Anda sendiri. Konsumen library Anda harus menyertakannya jika mereka mengompilasi kode mereka, karena library Anda merepresentasikan skrip eksternal dari perspektif mereka. Anggaplah eksternal sebagai kontrak antara Anda dan konsumen Anda, Anda berdua memerlukan salinannya.

Untuk itu, pastikan saat mengompilasi kode, Anda juga menyertakan extern dalam kompilasi. Hal ini mungkin tampak tidak biasa, karena kita sering menganggap extern sebagai "berasal dari tempat lain", tetapi perlu untuk memberi tahu Closure Compiler simbol mana yang Anda ekspos, sehingga tidak diganti namanya.

Satu peringatan penting di sini adalah Anda mungkin mendapatkan diagnostik "duplikat definisi" tentang kode yang menentukan simbol ekstern. Closure Compiler mengasumsikan bahwa setiap simbol dalam externs disediakan oleh library luar, dan saat ini tidak dapat memahami bahwa Anda sengaja menyediakan definisi. Diagnostik ini aman untuk diabaikan, dan Anda dapat menganggap pengabaian ini sebagai konfirmasi bahwa Anda benar-benar memenuhi API Anda.

Selain itu, Closure Compiler dapat memeriksa jenis apakah definisi Anda cocok dengan jenis deklarasi eksternal. Hal ini memberikan konfirmasi tambahan bahwa definisi Anda sudah benar.