JavaScript pemisahan kode

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 panggilan import() dinamis.
  • chunks: initial merujuk pada panggilan import statis.
  • chunks: all mencakup impor import() dinamis dan statis, sehingga Anda dapat berbagi potongan antara impor async dan initial.

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 potongan initial—yang mencakup modul main.js dan ./my-function.js.
  • Potongan async, yang hanya menyertakan form-validation.js (berisi hash file dalam nama resource jika dikonfigurasi). Potongan ini hanya akan didownload jika dan ketika condition 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.
Benar.
import statis.
Coba lagi.

Manakah jenis pernyataan import yang harus berada di bagian atas modul JavaScript, dan bukan di lokasi lain?

import() dinamis.
Coba lagi.
import statis.
Benar.

Saat menggunakan SplitChunksPlugin dalam webpack, apa perbedaan antara potongan async dan potongan initial?

Potongan async dimuat menggunakan potongan import() dan initial dinamis yang dimuat menggunakan import statis.
Benar.
Potongan async dimuat menggunakan potongan import statis dan initial dimuat menggunakan import() dinamis.
Coba lagi.

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.