Memuat resource JavaScript yang besar sangat memengaruhi kecepatan halaman. Membagi JavaScript Anda menjadi potongan-potongan kecil dan hanya mendownload elemen yang diperlukan agar halaman dapat berfungsi selama startup dapat sangat meningkatkan respons pemuatan halaman, yang pada akhirnya dapat meningkatkan Interaction to Next Paint (INP) halaman Anda.
Saat halaman mendownload, mengurai, dan mengompilasi file JavaScript berukuran besar, halaman dapat menjadi tidak responsif selama jangka waktu tertentu. 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 mengurai dan mengeksekusi JavaScript agar elemen tersebut berfungsi. Hasilnya, pengguna mungkin merasa seolah-olah interaksinya tertunda secara signifikan, atau bahkan terputus sama sekali.
Hal ini sering terjadi karena thread utama diblokir, saat JavaScript diuraikan dan dikompilasi pada thread utama. Jika proses ini memakan waktu terlalu lama, elemen halaman interaktif mungkin tidak merespons input pengguna dengan cukup cepat. Salah satu solusi untuk hal ini adalah dengan hanya memuat JavaScript yang diperlukan agar halaman berfungsi, sementara menunda pemuatan JavaScript lain melalui teknik yang dikenal sebagai pemisahan kode. Modul ini berfokus pada teknik yang terakhir.
Mengurangi penguraian dan eksekusi JavaScript selama startup melalui pemisahan kode
Lighthouse menampilkan peringatan saat eksekusi JavaScript memerlukan waktu lebih dari 2 detik, dan gagal jika lebih dari 3,5 detik. Penguraian dan eksekusi JavaScript yang berlebihan dapat menjadi masalah kapan saja 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.
Lebih dari itu, eksekusi dan penguraian JavaScript yang berlebihan sangat bermasalah selama pemuatan halaman awal, karena ini adalah titik dalam siklus proses halaman yang kemungkinan besar berinteraksi dengan pengguna. Faktanya, Total Waktu Pemblokiran (TBT)—metrik responsivitas pemuatan—sangat berkorelasi dengan INP, yang menunjukkan bahwa pengguna memiliki kecenderungan tinggi untuk mencoba berinteraksi selama pemuatan halaman awal.
Audit Lighthouse yang melaporkan waktu yang dihabiskan untuk menjalankan setiap file JavaScript yang diminta halaman Anda akan berguna karena dapat membantu Anda mengidentifikasi dengan tepat skrip yang mungkin menjadi kandidat untuk pemisahan kode. Anda kemudian dapat melangkah lebih jauh dengan menggunakan alat cakupan di Chrome DevTools untuk mengidentifikasi dengan tepat bagian JavaScript halaman yang tidak digunakan selama pemuatan halaman.
Pemisahan kode adalah teknik bermanfaat yang dapat mengurangi payload JavaScript awal halaman. Ini memungkinkan Anda membagi paket JavaScript menjadi dua bagian:
- JavaScript yang diperlukan saat pemuatan halaman, sehingga tidak dapat dimuat di lain waktu.
- JavaScript tersisa yang dapat dimuat di lain waktu, paling sering pada saat pengguna berinteraksi dengan elemen interaktif tertentu di halaman.
Pemisahan kode dapat dilakukan dengan menggunakan sintaksis import()
dinamis. Sintaksis
ini—tidak seperti elemen <script>
yang meminta resource JavaScript
tertentu selama startup—membuat permintaan untuk resource JavaScript di lain waktu 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, diurai, dan dieksekusi hanya saat pengguna memburamkan salah satu kolom
<input>
formulir. Dalam situasi ini, resource JavaScript yang bertanggung jawab untuk
mendorong logika validasi formulir hanya pernah dilibatkan dengan halaman saat
kemungkinan besar akan benar-benar digunakan.
Pemaket JavaScript seperti webpack, Parcel, Rollup, dan esbuild dapat dikonfigurasi untuk membagi paket JavaScript menjadi potongan-potongan yang lebih kecil setiap kali menemui panggilan import()
dinamis dalam kode sumber Anda. Sebagian besar alat ini melakukannya
secara otomatis, tetapi esbuild khususnya mengharuskan Anda untuk ikut serta
dalam pengoptimalan ini.
Catatan berguna tentang pemisahan kode
Meskipun pemisahan kode merupakan metode yang efektif untuk mengurangi pertentangan thread utama selama pemuatan halaman awal, ada baiknya untuk tetap memperhatikan beberapa hal jika Anda memutuskan untuk mengaudit kode sumber JavaScript guna mendapatkan peluang pemisahan kode.
Gunakan bundler jika Anda bisa
Developer biasanya menggunakan modul JavaScript selama proses pengembangan. Ini adalah peningkatan pengalaman developer yang sangat baik yang meningkatkan keterbacaan kode dan pemeliharaan. 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 tidak hanya dalam menerapkan pengoptimalan ke 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 pemaket juga mencoba memastikan bahwa paket tidak terlalu besar sehingga menimbulkan tugas yang lama karena evaluasi skrip.
Paket juga menghindari masalah pengiriman modul yang tidak dipaketkan dalam jumlah besar
melalui jaringan. Arsitektur yang menggunakan modul JavaScript cenderung memiliki hierarki modul
yang besar dan kompleks. Jika hierarki modul tidak dikelompokkan, setiap modul mewakili
permintaan HTTP terpisah, dan interaktivitas di aplikasi web dapat tertunda jika Anda
tidak memaketkan modul. Meskipun Anda dapat menggunakan
petunjuk resource <link rel="modulepreload">
untuk memuat hierarki modul besar sedini mungkin, paket JavaScript masih lebih disukai dari sudut pandang performa
pemuatan.
Jangan nonaktifkan kompilasi streaming tanpa sengaja
Mesin JavaScript V8 Chromium menawarkan sejumlah pengoptimalan siap pakai untuk memastikan kode JavaScript produksi Anda dimuat seefisien mungkin. Salah satu pengoptimalan ini dikenal sebagai kompilasi streaming, yang—seperti penguraian bertahap HTML yang di-streaming ke browser—mengompilasi potongan JavaScript yang di-streaming saat masuk dari jaringan.
Anda memiliki beberapa cara untuk memastikan kompilasi streaming terjadi untuk aplikasi web Anda di Chromium:
- Transformasikan kode produksi Anda untuk menghindari penggunaan modul JavaScript. Bundler dapat mengubah kode sumber JavaScript berdasarkan target kompilasi, dan targetnya sering kali spesifik untuk lingkungan tertentu. V8 akan menerapkan kompilasi streaming ke kode JavaScript apa pun 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
. Baik JavaScript produksi Anda menggunakan modul atau tidak, tidak ada jenis konten khusus untuk JavaScript yang menggunakan modul versus JavaScript yang tidak menggunakan modul. Jika V8 bermasalah, Anda secara efektif memilih untuk tidak melakukan 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 rusak.
Jangan biarkan pertimbangan ini menghalangi Anda untuk menggunakan pemisahan kode. Pemisahan kode adalah cara yang efektif untuk mengurangi payload JavaScript awal kepada pengguna, tetapi dengan menggunakan pemaket dan mengetahui cara mempertahankan perilaku kompilasi streaming V8, Anda dapat memastikan bahwa kode JavaScript produksi Anda berjalan secepat mungkin bagi pengguna.
Demo impor dinamis
webpack
webpack dikirimkan dengan plugin bernama SplitChunksPlugin
, yang memungkinkan Anda mengonfigurasi cara pemaket membagi file JavaScript. webpack mengenali pernyataan import()
dinamis dan import
statis. Perilaku
SplitChunksPlugin
dapat diubah dengan menentukan opsi chunks
dalam
konfigurasinya:
chunks: async
adalah nilai default, dan merujuk pada panggilanimport()
dinamis.chunks: initial
merujuk pada panggilanimport
statis.chunks: all
mencakup imporimport()
dinamis dan statis, sehingga Anda dapat berbagi potongan antara imporasync
daninitial
.
Secara default, setiap kali webpack menemukan pernyataan import()
dinamis, webpack akan membuat potongan 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 potongan terpisah:
- Potongan
main.js
—yang diklasifikasikan webpack sebagai potonganinitial
—yang mencakup modulmain.js
dan./my-function.js
. - Potongan
async
, yang hanya menyertakanform-validation.js
(berisi hash file dalam nama resource jika dikonfigurasi). Potongan ini hanya akan didownload jika dan ketikacondition
adalah truthy.
Konfigurasi ini memungkinkan Anda menunda pemuatan potongan 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 jika suatu kondisi tertentu terpenuhi, dalam hal ini, modul yang diimpor secara dinamis akan didownload. Salah satu contohnya mungkin 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
akan memastikan bahwa kode hanya dibagi pada potongan awal. Ini adalah
bagian seperti yang diimpor secara statis, atau tercantum dalam properti
entry
webpack. Dengan melihat contoh sebelumnya, potongan yang dihasilkan adalah kombinasi form-validation.js
dan main.js
dalam satu file skrip, sehingga berpotensi menghasilkan performa pemuatan halaman awal yang berpotensi 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 membagi potongan menjadi file terpisah jika melebihi apa yang ditentukan oleh maxSize
. Membagi file skrip besar menjadi file yang lebih kecil dapat
meningkatkan responsivitas pemuatan, seperti dalam beberapa kasus, tugas evaluasi skrip yang menggunakan CPU
dibagi menjadi tugas-tugas yang lebih kecil, sehingga cenderung tidak memblokir thread
utama untuk jangka waktu yang lebih lama.
Selain itu, membuat file JavaScript yang lebih besar juga berarti skrip lebih cenderung mengalami pembatalan cache. Misalnya, jika Anda mengirimkan skrip yang sangat besar dengan framework dan kode aplikasi pihak pertama, seluruh paket dapat dibatalkan jika hanya framework yang diperbarui, tetapi bukan dalam resource yang dipaketkan.
Di sisi lain, file skrip yang lebih kecil akan meningkatkan kemungkinan pengunjung yang kembali mengambil resource dari cache, sehingga menghasilkan pemuatan halaman yang lebih cepat pada kunjungan berulang. Namun, file yang lebih kecil akan lebih jarang mendapatkan manfaat dari kompresi daripada file berukuran besar, dan dapat meningkatkan waktu round-trip jaringan pada pemuatan halaman dengan cache browser yang belum disiapkan. Anda harus berhati-hati untuk mencapai keseimbangan antara efisiensi caching, 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()
dinamis.import
statis.
Manakah jenis pernyataan import
yang harus berada di bagian atas
modul JavaScript, dan bukan di lokasi lain?
import()
dinamis.import
statis.
Saat menggunakan SplitChunksPlugin
dalam webpack, apa
perbedaan antara potongan async
dan
potongan initial
?
async
dimuat menggunakan potongan import()
dan initial
dinamis yang dimuat menggunakan
import
statis.
async
dimuat menggunakan potongan import
statis dan initial
dimuat menggunakan import()
dinamis.
Berikutnya: Pemuatan lambat gambar dan elemen <iframe>
Meskipun cenderung menjadi jenis resource yang cukup mahal, JavaScript bukanlah
satu-satunya jenis resource yang dapat Anda tunda pemuatannya. Elemen gambar dan <iframe>
merupakan resource yang berpotensi mahal dengan sendirinya. Serupa dengan JavaScript, Anda
dapat menunda pemuatan gambar dan elemen <iframe>
dengan menjalankan lambat
pada gambar dan elemen tersebut, yang akan dijelaskan dalam modul berikutnya dalam kursus ini.