Memuat resource JavaScript berukuran besar akan memengaruhi kecepatan halaman secara signifikan. Membagi JavaScript Anda menjadi bagian yang lebih kecil dan hanya mendownload apa yang diperlukan agar halaman berfungsi selama startup dapat sangat meningkatkan responsivitas pemuatan halaman Anda, yang pada gilirannya dapat meningkatkan Interaksi dengan Gambar Berikutnya (INP) halaman Anda.
Saat mendownload, mengurai, dan mengompilasi file JavaScript besar, halaman dapat menjadi tidak responsif selama beberapa waktu. Elemen halaman terlihat, karena merupakan bagian dari HTML awal halaman dan diberi gaya oleh CSS. Namun, karena JavaScript yang diperlukan untuk mendukung elemen interaktif tersebut—serta skrip lain yang dimuat oleh halaman—mungkin sedang mem-parsing dan mengeksekusi JavaScript agar berfungsi. Akibatnya, pengguna mungkin merasa interaksi tertunda secara signifikan, atau bahkan rusak sama sekali.
Hal ini sering terjadi karena thread utama diblokir, karena JavaScript diuraikan dan dikompilasi di thread utama. Jika proses ini terlalu lama, elemen halaman interaktif mungkin tidak merespons input pengguna dengan cukup cepat. Salah satu solusi untuk masalah ini adalah memuat hanya JavaScript yang Anda butuhkan agar halaman berfungsi, sambil menunda pemuatan JavaScript lainnya nanti melalui teknik yang dikenal sebagai pemisahan kode. Modul ini berfokus pada teknik kedua.
Mengurangi penguraian dan eksekusi JavaScript selama startup melalui pemisahan kode
Lighthouse akan menampilkan peringatan jika eksekusi JavaScript memerlukan waktu lebih dari 2 detik, dan akan gagal jika memerlukan waktu lebih dari 3,5 detik. Penguraian dan eksekusi JavaScript yang berlebihan berpotensi menjadi masalah di setiap titik dalam siklus proses halaman, karena berpotensi meningkatkan penundaan input interaksi jika waktu saat pengguna berinteraksi dengan halaman bertepatan dengan saat tugas thread utama yang bertanggung jawab untuk memproses dan mengeksekusi JavaScript sedang berjalan.
Selain itu, eksekusi dan penguraian JavaScript yang berlebihan sangat bermasalah selama pemuatan halaman awal, karena pada tahap siklus proses halaman ini, pengguna cenderung berinteraksi dengan halaman. Faktanya, Total Blocking Time (TBT)—metrik responsivitas pemuatan—sangat berkorelasi dengan INP, yang menunjukkan bahwa pengguna cenderung mencoba melakukan interaksi selama pemuatan halaman awal.
Audit Lighthouse yang melaporkan waktu yang dihabiskan untuk mengeksekusi setiap file JavaScript yang diminta halaman Anda berguna karena dapat membantu Anda mengidentifikasi secara tepat skrip mana yang mungkin menjadi kandidat untuk pemisahan kode. Kemudian, Anda dapat melangkah lebih jauh dengan menggunakan alat cakupan di Chrome DevTools untuk mengidentifikasi secara tepat bagian JavaScript halaman yang tidak digunakan selama pemuatan halaman.
Pemisahan kode adalah teknik berguna yang dapat mengurangi payload JavaScript awal halaman. Hal ini memungkinkan Anda membagi paket JavaScript menjadi dua bagian:
- JavaScript diperlukan saat halaman dimuat, dan oleh karena itu tidak dapat dimuat di waktu lain.
- JavaScript yang tersisa dapat dimuat di lain waktu, paling sering pada saat pengguna berinteraksi dengan elemen interaktif tertentu di halaman.
Pemisahan kode dapat dilakukan dengan menggunakan sintaksis dinamis import()
. Sintaksis ini—tidak seperti elemen <script>
yang meminta resource JavaScript tertentu selama startup—membuat permintaan untuk resource JavaScript di kemudian hari selama siklus proses halaman.
document.querySelectorAll('#myForm input').addEventListener('blur', async () => {
// Get the form validation named export from the module through destructuring:
const { validateForm } = await import('/validate-form.mjs');
// Validate the form:
validateForm();
}, { once: true });
Dalam cuplikan JavaScript sebelumnya, modul validate-form.mjs
didownload, diuraikan, dan dieksekusi hanya saat pengguna menghilangkan fokus dari salah satu kolom <input>
formulir. Dalam situasi ini, resource JavaScript yang bertanggung jawab untuk
mendorong logika validasi formulir hanya terlibat dengan halaman saat
kemungkinan besar akan benar-benar digunakan.
Bundler JavaScript seperti webpack, Parcel, Rollup, dan esbuild dapat
dikonfigurasi untuk membagi paket JavaScript menjadi potongan yang lebih kecil setiap kali
mereka menemukan panggilan import()
dinamis dalam kode sumber Anda. Sebagian besar alat ini melakukannya secara otomatis, tetapi khususnya esbuild mengharuskan Anda memilih untuk mengaktifkan pengoptimalan ini.
Catatan bermanfaat tentang pemisahan kode
Meskipun pemisahan kode adalah metode yang efektif untuk mengurangi persaingan thread utama selama pemuatan halaman awal, ada baiknya untuk mengingat beberapa hal jika Anda memutuskan untuk mengaudit kode sumber JavaScript untuk peluang pemisahan kode.
Gunakan bundler jika Anda bisa
Developer biasanya menggunakan modul JavaScript selama proses pengembangan. Fitur ini adalah peningkatan kualitas pengalaman developer yang sangat baik karena meningkatkan keterbacaan dan kemudahan pemeliharaan kode. Namun, ada beberapa karakteristik performa yang kurang optimal yang dapat terjadi saat mengirimkan modul JavaScript ke produksi.
Yang terpenting, Anda harus menggunakan bundler untuk memproses dan mengoptimalkan kode sumber, termasuk modul yang ingin Anda pisahkan kodenya. Bundler sangat efektif dalam tidak hanya menerapkan pengoptimalan pada kode sumber JavaScript, tetapi juga cukup efektif dalam menyeimbangkan pertimbangan performa seperti ukuran paket terhadap rasio kompresi. Efektivitas kompresi meningkat seiring dengan ukuran paket, tetapi bundler juga mencoba memastikan bahwa paket tidak terlalu besar sehingga menimbulkan tugas yang panjang karena evaluasi skrip.
Bundler juga menghindari masalah pengiriman sejumlah besar modul yang tidak dibundel melalui jaringan. Arsitektur yang menggunakan modul JavaScript cenderung memiliki hierarki modul yang besar dan kompleks. Saat pohon modul tidak digabungkan, setiap modul merepresentasikan
permintaan HTTP terpisah, dan interaktivitas di aplikasi web Anda mungkin tertunda jika Anda
tidak menggabungkan modul. Meskipun Anda dapat menggunakan
petunjuk resource <link rel="modulepreload">
untuk memuat hierarki modul besar sedini
mungkin, paket JavaScript tetap lebih disukai dari sudut pandang
performa pemuatan.
Jangan menonaktifkan kompilasi streaming secara tidak sengaja
Mesin JavaScript V8 Chromium menawarkan sejumlah pengoptimalan langsung untuk memastikan kode JavaScript produksi Anda dimuat seefisien mungkin. Salah satu pengoptimalan ini dikenal sebagai kompilasi streaming yang—seperti penguraian inkremental HTML yang di-streaming ke browser—mengompilasi potongan JavaScript yang di-streaming saat tiba dari jaringan.
Anda memiliki beberapa cara untuk memastikan kompilasi streaming terjadi untuk aplikasi web Anda di Chromium:
- Ubah kode produksi Anda agar tidak menggunakan modul JavaScript. Bundler dapat mengubah kode sumber JavaScript Anda berdasarkan target kompilasi, dan target sering kali khusus untuk lingkungan tertentu. V8 akan menerapkan kompilasi streaming ke kode JavaScript yang tidak menggunakan modul, dan Anda dapat mengonfigurasi bundler untuk mengubah kode modul JavaScript menjadi sintaksis yang tidak menggunakan modul JavaScript dan fiturnya.
- Jika Anda ingin mengirimkan modul JavaScript ke produksi, gunakan ekstensi
.mjs
. Terlepas dari apakah JavaScript produksi Anda menggunakan modul atau tidak, tidak ada jenis konten khusus untuk JavaScript yang menggunakan modul dibandingkan dengan JavaScript yang tidak menggunakan modul. Terkait V8, Anda secara efektif menonaktifkan kompilasi streaming saat mengirimkan modul JavaScript dalam produksi menggunakan ekstensi.js
. Jika Anda menggunakan ekstensi.mjs
untuk modul JavaScript, V8 dapat memastikan bahwa kompilasi streaming untuk kode JavaScript berbasis modul tidak terganggu.
Jangan biarkan pertimbangan ini menghalangi Anda menggunakan pemisahan kode. Pemisahan kode adalah cara efektif untuk mengurangi payload JavaScript awal bagi pengguna, tetapi dengan menggunakan bundler dan mengetahui cara mempertahankan perilaku kompilasi streaming V8, Anda dapat memastikan bahwa kode JavaScript produksi Anda secepat mungkin bagi pengguna.
Demo impor dinamis
webpack
webpack dilengkapi dengan plugin bernama SplitChunksPlugin
, yang memungkinkan Anda
mengonfigurasi cara bundler memisahkan file JavaScript. webpack mengenali pernyataan
dinamis import()
dan statis import
. Perilaku
SplitChunksPlugin
dapat diubah dengan menentukan opsi chunks
dalam
konfigurasinya:
chunks: async
adalah nilai default, dan merujuk pada panggilanimport()
dinamis.chunks: initial
mengacu pada panggilanimport
statis.chunks: all
mencakup impor dinamisimport()
dan statis, sehingga Anda dapat membagikan potongan di antara imporasync
daninitial
.
Secara default, setiap kali webpack menemukan pernyataan import()
dinamis, webpack akan membuat chunk terpisah untuk modul tersebut:
/* main.js */
// An application-specific chunk required during the initial page load:
import myFunction from './my-function.js';
myFunction('Hello world!');
// If a specific condition is met, a separate chunk is downloaded on demand,
// rather than being bundled with the initial chunk:
if (condition) {
// Assumes top-level await is available. More info:
// https://v8.dev/features/top-level-await
await import('/form-validation.js');
}
Konfigurasi webpack default untuk cuplikan kode sebelumnya menghasilkan dua chunk terpisah:
- Chunk
main.js
—yang diklasifikasikan webpack sebagai chunkinitial
—yang mencakup modulmain.js
dan./my-function.js
. - Chunk
async
, yang hanya mencakupform-validation.js
(berisi hash file dalam nama resource jika dikonfigurasi). Chunk ini hanya didownload jika dan saatcondition
bernilai truthy.
Konfigurasi ini memungkinkan Anda menunda pemuatan chunk form-validation.js
hingga
benar-benar diperlukan. Hal ini dapat meningkatkan responsivitas pemuatan dengan mengurangi waktu evaluasi
skrip selama pemuatan halaman awal. Download dan evaluasi skrip
untuk potongan form-validation.js
terjadi saat kondisi tertentu terpenuhi, yang
dalam hal ini, modul yang diimpor secara dinamis akan didownload. Salah satu contohnya adalah
kondisi saat polyfill hanya didownload untuk browser tertentu, atau—seperti dalam
contoh sebelumnya—modul yang diimpor diperlukan untuk interaksi pengguna.
Di sisi lain, mengubah konfigurasi SplitChunksPlugin
untuk menentukan
chunks: initial
memastikan bahwa kode hanya dibagi pada chunk awal. Chunk ini adalah
chunk seperti yang diimpor secara statis, atau tercantum dalam properti entry
webpack. Melihat contoh sebelumnya, potongan yang dihasilkan akan menjadi
kombinasi form-validation.js
dan main.js
dalam satu file skrip,
yang berpotensi menghasilkan performa pemuatan halaman awal yang lebih buruk.
Opsi untuk SplitChunksPlugin
juga dapat dikonfigurasi untuk memisahkan skrip yang lebih besar menjadi beberapa skrip yang lebih kecil—misalnya dengan menggunakan opsi maxSize
untuk menginstruksikan webpack agar membagi chunk menjadi file terpisah jika melebihi yang ditentukan oleh maxSize
. Membagi file skrip besar menjadi file yang lebih kecil dapat meningkatkan respons pemuatan, karena dalam beberapa kasus, evaluasi skrip yang intensif CPU dibagi menjadi tugas-tugas yang lebih kecil, yang cenderung tidak memblokir thread utama dalam jangka waktu yang lebih lama.
Selain itu, membuat file JavaScript yang lebih besar juga berarti skrip lebih mungkin mengalami invalidasi cache. Misalnya, jika Anda mengirimkan skrip yang sangat besar dengan kode aplikasi pihak pertama dan framework, seluruh paket dapat dibatalkan validasinya jika hanya framework yang diupdate, tetapi tidak ada yang lain dalam resource paket.
Di sisi lain, file skrip yang lebih kecil meningkatkan kemungkinan pengunjung yang kembali mengambil resource dari cache, sehingga halaman dimuat lebih cepat pada kunjungan berulang. Namun, file yang lebih kecil kurang diuntungkan dari kompresi dibandingkan file yang lebih besar, dan dapat meningkatkan waktu perjalanan pulang pergi jaringan pada pemuatan halaman dengan cache browser yang tidak dipersiapkan. Keseimbangan harus dijaga antara efisiensi penyiapan cache, efektivitas kompresi, dan waktu evaluasi skrip.
demo webpack
Demo SplitChunksPlugin
webpack.
Menguji pengetahuan Anda
Jenis pernyataan import
mana yang digunakan saat melakukan pemisahan
kode?
import()
.import
.
Jenis pernyataan import
mana yang harus berada di bagian atas
modul JavaScript, dan tidak di lokasi lain?
import()
.import
.
Saat menggunakan SplitChunksPlugin
di webpack, apa perbedaan antara potongan async
dan potongan initial
?
async
dimuat menggunakan import()
dinamis
dan chunk initial
dimuat menggunakan import
statis.
async
dimuat menggunakan import
statis
dan chunk initial
dimuat menggunakan import()
dinamis.
Berikutnya: Memuat lambat gambar dan elemen <iframe>
Meskipun cenderung menjadi jenis resource yang cukup mahal, JavaScript bukan satu-satunya jenis resource yang dapat Anda tunda pemuatannya. Gambar dan elemen <iframe>
berpotensi menjadi resource yang mahal. Mirip dengan JavaScript, Anda
dapat menunda pemuatan gambar dan elemen <iframe>
dengan pemuatan lambat, yang dijelaskan dalam modul berikutnya di kursus ini.