Dasbor Siti memiliki satu panel grafik yang jarang dibuka — mungkin satu dari sepuluh sesi. Karena panel itu tergolong “berat” (ratusan baris kode gambar grafik plus pustaka pendukungnya), memuatnya sejak halaman dibuka berarti sembilan dari sepuluh pengunjung membayar biaya untuk sesuatu yang tidak mereka lihat.
Chapter ini melatih pola lazy loading: menunda pemuatan module sampai interaksi yang membutuhkannya benar-benar terjadi. Alatnya tetap import() yang sudah kita kenal di section Modules — yang berubah adalah sudut pandangnya, dari “cara memilih module saat berjalan” menjadi “cara menunda biaya pemuatan”.
import(): Muat Saat Dibutuhkan #
Supaya proses pemuatan bisa diamati, module berat kita diberi satu penanda penting: console.log di tingkat atas yang mencetak setiap kali module-nya dievaluasi. Dengan penanda itu, kita bisa melihat dengan pasti kapan pemuatan terjadi:
// panel-chart.mjs — module "berat": di sini hanya data contoh, di dunia nyata
// berisi ratusan baris kode gambar grafik beserta pustaka pendukungnya.
console.log("module panel-chart dimuat");
export function render(angka) {
const total = angka.reduce((a, b) => a + b, 0);
return "Total penjualan: " + total;
}Dua hal di module ini yang berperan dalam pengukuran: baris 3 adalah penanda evaluasi (akan tercetak sekali seumur program, tepat ketika module dimuat), sedangkan render pada baris 5–8 adalah API yang dipakai peminjamnya. Sisanya — aplikasi yang memakainya — berada di berkas berikutnya:
// aplikasi.mjs — panel berat hanya dimuat ketika tombolnya ditekan
let panelTerpasang = null;
async function klikTombolPanel() {
if (!panelTerpasang) {
panelTerpasang = await import("./panel-chart.mjs");
console.log("pemuatan pertama selesai");
}
console.log(panelTerpasang.render([12, 30, 7]));
}
// Siti menekan tombol panel untuk pertama kali
await klikTombolPanel();
// lalu menekannya sekali lagi
await klikTombolPanel();
// bukti cache: dua pemanggilan import() mengembalikan namespace yang sama
const permintaanA = import("./panel-chart.mjs");
const permintaanB = import("./panel-chart.mjs");
console.log((await permintaanA) === (await permintaanB));Menjalankan node aplikasi.mjs menghasilkan:
Output yang Dihasilkan:
module panel-chart dimuat
pemuatan pertama selesai
Total penjualan: 49
Total penjualan: 49
trueMari kita telaah baris-baris pentingnya:
- Baris 3 (
let panelTerpasang = null): penanda bahwa panel belum pernah dimuat. Selama nilainyanull, setiap tekanan tombol dianggap belum punya panel terpasang. - Baris 6–9 (
if (!panelTerpasang) { panelTerpasang = await import(...) }): hanya tekanan pertama yang masuk ke percabangan ini.import()dimulai saat itu juga — dan keluaran membuktikannya: barismodule panel-chart dimuatmuncul tepat sekali, di ataspemuatan pertama selesai, karena kode tingkat atas module dieksekusi sebelum Promiseimport()terpenuhi. - Baris 10 (
panelTerpasang.render([12, 30, 7])): namespace module dipakai seperti biasa. Tekanan pertama dan kedua sama-sama mencetakTotal penjualan: 49— perilaku render identik; yang berbeda hanyalah apakah pemuatan terjadi. - Baris 14 dan 16 (dua
await klikTombolPanel()): tekanan kedua melompati percabangan pemuatan (tidak ada log baru) dan langsung merender. Inilah inti lazy loading — biaya dibayar sekali, manfaat dirasakan setiap kali. - Baris 19–21 (dua
import()dan perbandingan===): dua pemanggilanimport()terhadap jalur yang sama menghasilkantrue— keduanya merujuk namespace yang sama persis. Cache module bekerja pada level runtime, bukan pada kode kita.
Cache Module: Dievaluasi Sekali, Dipakai Berkali-kali #
Pembandingan === di baris 21 memperlihatkan aturan penting dari import(): runtime menyimpan module yang sudah dimuat dalam module cache, berdasarkan jalurnya. Konsekuensinya ada dua.
Pertama, kode tingkat atas sebuah module dieksekusi tepat satu kali seumur program — inilah sebabnya log module panel-chart dimuat tidak muncul lagi pada tekanan tombol kedua. Kedua, import() berulang terhadap jalur yang sama praktis gratis: ia tidak mengunduh ulang apa pun, hanya mengembalikan Promise yang sudah terpenuhi berisi namespace yang sama.
Prinsip cache ini berlaku justru karena kita memakai jalur yang stabil. Jalur "./panel-chart.mjs" ditulis sama persis di setiap pemanggilan, sehingga runtime mengenalinya sebagai satu module. Dua jalur tulisan berbeda menuju berkas yang sama dianggap dua module — bebas tulis jalurnya seperti apa, asal konsisten.
Karena cache sudah bekerja dari sisi runtime, variabel panelTerpasang sebenarnya bukan satu-satunya penjaga. Bentuk yang lebih ringkas pun sah:
async function klikTombolPanel() {
const panel = await import("./panel-chart.mjs");
console.log(panel.render([12, 30, 7]));
}Pada variasi ini setiap klik tetap menjalankan await import(...), tetapi hanya klik pertama yang benar-benar menunggu unduhan; klik berikutnya mendapat hasil cache hampir seketika. Variabel penanda tetap berguna ketika kita ingin menambahkan logika sekali-pasang — misalnya mencatat “pemuatan pertama selesai” atau mengunci tombol selagi module sedang diunduh.
Pola Klik-untuk-Muat #
Pola pada aplikasi.mjs bisa digeneralisasi menjadi resep empat langkah untuk fitur mana pun yang jarang dipakai:
- Tandai pemicunya: interaksi yang menandakan fitur benar-benar dipakai — klik tombol, perluasan panel, masuk ke mode tertentu.
- Buat handler
async: di dalamnya panggilawait import("./fitur.mjs"). Karena handlerasync, tampilan tidak ikut membeku selama unduhan. - Pakai namespace hasilnya: panggil fungsi ekspor, pasang komponen, atau render konten.
- Biarkan cache menutup sisanya: pemakaian berikutnya tidak perlu pengondisian khusus.
Contoh fitur yang lazim dimuat dengan pola ini: editor teks kaya di aplikasi catatan, peta interaktif yang baru aktif saat bagian peta dibuka, dialog pengaturan lanjutan, dan pembuatan berkas ekspor yang jarang dipakai.
Sama pentingnya dengan kapan pola ini dipakai: kapan pola ini tidak layak dipakai. Tiga keadaan yang membuat lazy loading justru memperburuk pengalaman:
- Fitur yang nyaris selalu dipakai. Memuat bertahap fitur inti hanya menunda hal yang pasti dibutuhkan — unduhannya tetap dibayar, ditambah jeda menunggu.
- Interaksi yang butuh respons seketika. Tombol undo, aksi simpan, atau navigasi inti sebaiknya tidak menunggu unduhan module pada momen ditekan.
- Module kecil yang murah. Memecah berkas sekecil beberapa kilo menambah permintaan jaringan tanpa hemat yang terasa — ingat faktor jumlah berkas di chapter JavaScript Loading.
Ukuran keputusannya sederhana: bandingkan biaya memuat module dengan seberapa sering ia benar-benar dipakai. Makin jarang dipakai dan makin berat module-nya, makin besar hematnya.
Dengan code splitting dan lazy loading, biaya pemuatan kode sudah kita kelola. Babak berikutnya pindah ke sisi yang berbeda: apa yang terjadi di memori setelah kode dan datanya berjalan.