Pengoptimalan startup JavaScript

Saat membangun situs yang sangat bergantung pada JavaScript, terkadang kita harus membayar apa yang kita kirimkan dengan cara yang tidak selalu mudah dilihat. Dalam artikel ini, kami akan membahas alasan sedikit disiplin dapat membantu jika Anda ingin situs Anda dimuat dan interaktif dengan cepat di perangkat seluler. Mengirimkan lebih sedikit JavaScript dapat berarti lebih sedikit waktu dalam transmisi jaringan, lebih sedikit waktu yang dihabiskan untuk mendekompresi kode, dan lebih sedikit waktu untuk mengurai dan mengompilasi JavaScript ini.

Jaringan

Saat sebagian besar developer memikirkan biaya JavaScript, mereka memikirkannya dalam hal biaya download dan eksekusi. Mengirim lebih banyak byte JavaScript melalui jaringan akan memerlukan waktu lebih lama jika koneksi pengguna lebih lambat.

Saat browser meminta
resource, resource tersebut perlu diambil, lalu didekompresi. Dalam kasus
resource seperti JavaScript, resource tersebut harus diurai dan dikompilasi sebelum
dieksekusi.

Hal ini dapat menjadi masalah, karena jenis koneksi jaringan efektif yang dimiliki pengguna mungkin sebenarnya bukan 3G, 4G, atau Wi-Fi. Anda dapat menggunakan Wi-Fi kedai kopi, tetapi terhubung ke hotspot seluler dengan kecepatan 2G.

Anda dapat mengurangi biaya transfer jaringan JavaScript melalui:

  • Hanya mengirim kode yang dibutuhkan pengguna.
    • Gunakan pemisahan kode untuk memisahkan JavaScript menjadi bagian yang penting dan tidak penting. Bundler modul seperti webpack mendukung pemisahan kode.
    • Pemuatan lambat dalam kode yang tidak penting.
  • Minifikasi
  • Kompresi
    • Setidaknya, gunakan gzip untuk mengompresi resource berbasis teks.
    • Sebaiknya gunakan Brotli ~q11. Brotli mengungguli gzip dalam rasio kompresi. Brotli membantu CertSimple menghemat 17% ukuran byte JS yang dikompresi dan LinkedIn menghemat 4% waktu pemuatan.
  • Menghapus kode yang tidak digunakan.
  • Men-cache kode untuk meminimalkan perjalanan jaringan.
    • Gunakan caching HTTP untuk memastikan browser meng-cache respons secara efektif. Tentukan masa aktif optimal untuk skrip (max-age) dan berikan token validasi (ETag) untuk menghindari transfer byte yang tidak berubah.
    • Caching Service Worker dapat membuat jaringan aplikasi Anda tangguh dan memberi Anda akses cepat ke fitur seperti cache kode V8.
    • Gunakan caching jangka panjang untuk menghindari pengambilan ulang resource yang belum berubah. Jika menggunakan Webpack, lihat hashing nama file.

Mengurai/mengompilasi

Setelah didownload, salah satu biaya terberat JavaScript adalah waktu yang diperlukan mesin JS untuk mengurai/mengompilasi kode ini. Di Chrome DevTools, penguraian dan kompilasi adalah bagian dari waktu "Pembuatan skrip" berwarna kuning di panel Performa.

ALT_TEXT_HERE

Tab Bottom-Up dan Call Tree menampilkan waktu Parse/compile yang tepat:

ALT_TEXT_HERE
Panel Performa Chrome DevTools > Bottom-Up. Dengan mengaktifkan Runtime Call Stats V8, kita dapat melihat waktu yang dihabiskan dalam fase seperti Parse dan Compile

Namun, mengapa hal ini penting?

ALT_TEXT_HERE

Menghabiskan waktu lama untuk mengurai/mengompilasi kode dapat sangat menunda seberapa cepat pengguna dapat berinteraksi dengan situs Anda. Semakin banyak JavaScript yang Anda kirim, semakin lama waktu yang diperlukan untuk mengurai dan mengompilasinya sebelum situs Anda menjadi interaktif.

Byte demi byte, JavaScript lebih mahal untuk diproses oleh browser daripada gambar atau Font Web dengan ukuran yang setara — Tom Dale

Dibandingkan dengan JavaScript, ada banyak biaya yang terkait dengan pemrosesan gambar berukuran setara (gambar tersebut masih harus didekode), tetapi pada hardware seluler rata-rata, JS lebih cenderung berdampak negatif pada interaktivitas halaman.

ALT_TEXT_HERE
Byte JavaScript dan gambar memiliki biaya yang sangat berbeda. Gambar biasanya tidak memblokir thread utama atau mencegah antarmuka menjadi interaktif saat didekode dan dirasterisasi. Namun, JS dapat menunda interaktivitas karena biaya penguraian, kompilasi, dan eksekusi.

Saat kita membahas tentang lambatnya penguraian dan kompilasi, konteksnya penting — kita membahas tentang ponsel rata-rata di sini. Pengguna rata-rata dapat memiliki ponsel dengan CPU dan GPU yang lambat, tanpa cache L2/L3, dan bahkan mungkin memiliki batasan memori.

Kemampuan jaringan dan kemampuan perangkat tidak selalu cocok. Pengguna dengan koneksi Fiber yang luar biasa belum tentu memiliki CPU terbaik untuk mengurai dan mengevaluasi JavaScript yang dikirim ke perangkatnya. Hal ini juga berlaku sebaliknya… koneksi jaringan yang buruk, tetapi CPU yang sangat cepat. — Kristofer Baxter, LinkedIn

Di bawah ini, kita dapat melihat biaya penguraian JavaScript (sederhana) yang didekompresi sebesar ~1 MB pada hardware kelas bawah dan atas. Ada perbedaan waktu 2–5x untuk mengurai/mengompilasi kode antara ponsel tercepat di pasar dan ponsel rata-rata.

ALT_TEXT_HERE
Grafik ini menyoroti waktu penguraian untuk paket JavaScript 1 MB (~250 KB yang di-gzip) di seluruh perangkat desktop dan seluler dari berbagai kelas. Saat melihat biaya penguraian, angka yang didekompresi yang harus dipertimbangkan, misalnya JS yang di-gzip sebesar ~250 KB didekompresi menjadi kode sebesar ~1 MB.

Bagaimana dengan situs di dunia nyata, seperti CNN.com?

Di iPhone 8 kelas atas, hanya perlu waktu ~4 detik untuk mengurai/mengompilasi JS CNN dibandingkan dengan ~13 detik untuk ponsel biasa (Moto G4). Hal ini dapat berdampak signifikan pada seberapa cepat pengguna dapat berinteraksi sepenuhnya dengan situs ini.

ALT_TEXT_HERE
Di atas, kita melihat waktu penguraian yang membandingkan performa chip A11 Bionic Apple dengan Snapdragon 617 di hardware Android yang lebih umum.

Hal ini menekankan pentingnya pengujian pada hardware rata-rata (seperti Moto G4), bukan hanya ponsel yang mungkin ada di saku Anda. Namun, konteks itu penting: optimalkan untuk perangkat dan kondisi jaringan yang dimiliki pengguna Anda.

ALT_TEXT_HERE
Google Analytics dapat memberikan insight tentang class perangkat seluler yang digunakan pengguna sebenarnya untuk mengakses situs Anda. Hal ini dapat memberikan peluang untuk memahami batasan CPU/GPU sebenarnya yang mereka operasikan.

Apakah kita benar-benar mengirimkan terlalu banyak JavaScript? Err, possibly :)

Dengan menggunakan HTTP Archive (sekitar 500 ribu situs teratas) untuk menganalisis status JavaScript di perangkat seluler, kita dapat melihat bahwa 50% situs memerlukan waktu lebih dari 14 detik untuk menjadi interaktif. Situs ini menghabiskan hingga 4 detik hanya untuk mengurai dan mengompilasi JS.

ALT_TEXT_HERE

Dengan mempertimbangkan waktu yang diperlukan untuk mengambil dan memproses JS serta resource lainnya, mungkin tidak mengherankan jika pengguna harus menunggu beberapa saat sebelum merasa halaman siap digunakan. Kita pasti bisa melakukan yang lebih baik di sini.

Menghapus JavaScript yang tidak penting dari halaman Anda dapat mengurangi waktu transmisi, penguraian dan kompilasi yang menggunakan banyak CPU, serta potensi overhead memori. Hal ini juga membantu membuat halaman Anda interaktif lebih cepat.

Waktu eksekusi

Tidak hanya penguraian dan kompilasi yang dapat menimbulkan biaya. Eksekusi JavaScript (menjalankan kode setelah diurai/dikompilasi) adalah salah satu operasi yang harus terjadi di thread utama. Waktu eksekusi yang lama juga dapat memperlambat seberapa cepat pengguna dapat berinteraksi dengan situs Anda.

ALT_TEXT_HERE

Jika skrip dieksekusi selama lebih dari 50 milidetik, waktu interaktif akan tertunda selama seluruh waktu yang diperlukan untuk mendownload, mengompilasi, dan mengeksekusi JS — Alex Russell

Untuk mengatasinya, JavaScript mendapatkan manfaat dari bagian kecil untuk menghindari penguncian thread utama. Pelajari apakah Anda dapat mengurangi jumlah pekerjaan yang dilakukan selama eksekusi.

Biaya lain

JavaScript dapat memengaruhi performa halaman dengan cara lain:

  • Memori. Halaman dapat tampak tersendat atau sering dijeda karena GC (pembersihan sampah memori). Saat browser mengklaim kembali memori, eksekusi JS dijeda sehingga browser yang sering mengumpulkan sampah dapat menjeda eksekusi lebih sering daripada yang kita inginkan. Hindari kebocoran memori dan jeda gc yang sering untuk menjaga halaman bebas jank.
  • Selama runtime, JavaScript yang berjalan lama dapat memblokir thread utama sehingga menyebabkan halaman tidak responsif. Membagi pekerjaan menjadi bagian-bagian yang lebih kecil (menggunakan requestAnimationFrame() atau requestIdleCallback() untuk penjadwalan) dapat meminimalkan masalah responsivitas yang dapat membantu meningkatkan Interaction to Next Paint (INP).

Pola untuk mengurangi biaya pengiriman JavaScript

Saat Anda mencoba menjaga waktu transmisi jaringan dan penguraian/kompilasi untuk JavaScript tetap lambat, ada pola yang dapat membantu seperti pengelompokan berbasis rute atau PRPL.

PRPL

PRPL (Push, Render, Pre-cache, Lazy-load) adalah pola yang mengoptimalkan interaktivitas melalui pemisahan kode dan caching yang agresif:

ALT_TEXT_HERE

Mari kita visualisasikan dampaknya.

Kami menganalisis waktu pemuatan situs seluler populer dan Progressive Web App menggunakan Statistik Panggilan Runtime V8. Seperti yang dapat kita lihat, waktu penguraian (ditampilkan dalam warna oranye) adalah bagian penting dari waktu yang dihabiskan banyak situs ini:

ALT_TEXT_HERE

Wego, situs yang menggunakan PRPL, berhasil mempertahankan waktu penguraian yang rendah untuk rutenya, sehingga menjadi interaktif dengan sangat cepat. Banyak situs lain di atas yang menerapkan pemisahan kode dan anggaran performa untuk mencoba menurunkan biaya JS mereka.

Bootstrapping progresif

Banyak situs mengoptimalkan visibilitas konten dengan mengorbankan interaktivitas. Untuk mendapatkan first paint yang cepat saat Anda memiliki paket JavaScript yang besar, developer terkadang menggunakan rendering sisi server; lalu "mengupgradenya" untuk melampirkan pengendali peristiwa saat JavaScript akhirnya diambil.

Berhati-hatilah — hal ini memiliki biaya tersendiri. Anda 1) umumnya mengirimkan respons HTML yang lebih besar yang dapat mendorong interaktivitas kita, 2) dapat membuat pengguna berada dalam lembah yang luar biasa di mana separuh pengalaman tidak dapat benar-benar interaktif hingga JavaScript selesai diproses.

Bootstrap Progresif mungkin merupakan pendekatan yang lebih baik. Kirim halaman dengan fungsi minimal (hanya terdiri dari HTML/JS/CSS yang diperlukan untuk rute saat ini). Seiring dengan tersedianya lebih banyak resource, aplikasi dapat melakukan pemuatan lambat dan membuka lebih banyak fitur.

ALT_TEXT_HERE
Progressive Bootstrapping oleh Paul Lewis

Memuat kode yang proporsional dengan apa yang terlihat adalah hal yang ideal. PRPL dan Progressive Bootstrapping adalah pola yang dapat membantu mencapai hal ini.

Kesimpulan

Ukuran transmisi sangat penting untuk jaringan low-end. Waktu penguraian penting untuk perangkat yang terikat CPU. Menjaga latensi ini tetap rendah sangatlah penting.

Tim telah berhasil menerapkan anggaran performa yang ketat untuk menjaga waktu transmisi dan penguraian/waktu kompilasi JavaScript tetap rendah. Lihat "Can You Afford It?: Real-world Web Performance Budgets" untuk mendapatkan panduan tentang anggaran untuk perangkat seluler.

ALT_TEXT_HERE
Sebaiknya pertimbangkan seberapa banyak "ruang" JS yang dapat diberikan oleh keputusan arsitektur yang kita buat untuk logika aplikasi.

Jika Anda membangun situs yang menargetkan perangkat seluler, lakukan yang terbaik untuk mengembangkan pada hardware yang representatif, jaga agar waktu penguraian/waktu kompilasi JavaScript tetap rendah dan terapkan anggaran performa untuk memastikan tim Anda dapat mengawasi biaya JavaScript mereka.

Pelajari lebih lanjut