JavaScript pemisahan kode

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

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 chunk initial—yang mencakup modul main.js dan ./my-function.js.
  • Chunk async, yang hanya mencakup form-validation.js (berisi hash file dalam nama resource jika dikonfigurasi). Chunk ini hanya didownload jika dan saat condition 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?

Dinamis import().
Benar.
Statis import.
Coba lagi.

Jenis pernyataan import mana yang harus berada di bagian atas modul JavaScript, dan tidak di lokasi lain?

Dinamis import().
Coba lagi.
Statis import.
Benar.

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

Chunk async dimuat menggunakan import() dinamis dan chunk initial dimuat menggunakan import statis.
Benar.
Chunk async dimuat menggunakan import statis dan chunk initial dimuat menggunakan import() dinamis.
Coba lagi.

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.