Strategi pengelompokan webpack yang lebih baru di Next.js dan Gatsby meminimalkan kode duplikat untuk meningkatkan performa pemuatan halaman.
Chrome berkolaborasi dengan alat dan framework dalam ekosistem open source JavaScript. Sejumlah pengoptimalan yang lebih baru baru-baru ini ditambahkan untuk meningkatkan performa pemuatan Next.js dan Gatsby. Artikel ini membahas strategi pengelompokan terperinci yang ditingkatkan yang kini dikirimkan secara default di kedua framework.
Pengantar
Seperti banyak framework web, Next.js dan Gatsby menggunakan webpack sebagai
bundler intinya. webpack v3 memperkenalkan
CommonsChunkPlugin
untuk memungkinkan
modul output yang dibagikan di antara berbagai titik entri dalam satu (atau beberapa) bagian "umum" (atau
bagian). Kode bersama dapat didownload secara terpisah dan disimpan di cache browser sejak awal, yang dapat
menghasilkan performa pemuatan yang lebih baik.
Pola ini menjadi populer dengan banyak framework aplikasi web satu halaman yang mengadopsi konfigurasi titik entri dan paket yang terlihat seperti ini:
Meskipun praktis, konsep menggabungkan semua kode modul bersama ke dalam satu bagian memiliki
batasan. Modul yang tidak dibagikan di setiap titik entri dapat didownload untuk rute yang tidak menggunakannya
sehingga lebih banyak kode yang didownload daripada yang diperlukan. Misalnya, saat page1
memuat
bagian common
, page1
akan memuat kode untuk moduleC
meskipun page1
tidak menggunakan moduleC
.
Karena alasan ini, bersama dengan beberapa plugin lainnya, webpack v4 menghapus plugin tersebut dan menggantinya dengan plugin baru: SplitChunksPlugin
.
Peningkatan Pembagian
Setelan default untuk SplitChunksPlugin
berfungsi dengan baik untuk sebagian besar pengguna. Beberapa bagian terpisah
dibuat bergantung pada sejumlah kondisi
untuk mencegah pengambilan kode duplikat di beberapa rute.
Namun, banyak framework web yang menggunakan plugin ini masih mengikuti pendekatan "single-commons" untuk pemisahan
bagian. Misalnya, Next.js akan menghasilkan paket commons
yang berisi modul apa pun yang
digunakan di lebih dari 50% halaman dan semua dependensi framework (react
, react-dom
, dan sebagainya).
const splitChunksConfigs = {
…
prod: {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
commons: {
name: 'commons',
chunks: 'all',
minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
},
react: {
name: 'commons',
chunks: 'all',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
},
},
},
Meskipun menyertakan kode yang bergantung pada framework ke dalam bagian bersama berarti kode tersebut dapat didownload dan di-cache untuk titik entri apa pun, heuristik berbasis penggunaan yang menyertakan modul umum yang digunakan di lebih dari setengah halaman tidak terlalu efektif. Mengubah rasio ini hanya akan menghasilkan salah satu dari dua hasil:
- Jika Anda mengurangi rasio, lebih banyak kode yang tidak perlu akan didownload.
- Jika Anda meningkatkan rasio, lebih banyak kode yang akan diduplikasi di beberapa rute.
Untuk mengatasi masalah ini, Next.js mengadopsi konfigurasi
yang berbeda untukSplitChunksPlugin
yang mengurangi
kode yang tidak diperlukan untuk rute apa pun.
- Setiap modul pihak ketiga yang cukup besar (lebih dari 160 KB) akan dibagi menjadi beberapa bagian
- Potongan
frameworks
terpisah dibuat untuk dependensi framework (react
,react-dom
, dan seterusnya) - Membuat sebanyak mungkin bagian bersama yang diperlukan (hingga 25)
- Ukuran minimum untuk chunk yang akan dibuat diubah menjadi 20 KB
Strategi pengelompokan terperinci ini memberikan manfaat berikut:
- Waktu pemuatan halaman ditingkatkan. Mengeluarkan beberapa bagian bersama, bukan satu bagian, akan meminimalkan jumlah kode yang tidak diperlukan (atau duplikat) untuk titik entri apa pun.
- Peningkatan cache selama navigasi. Memisahkan library besar dan dependensi framework menjadi beberapa bagian terpisah akan mengurangi kemungkinan pembatalan validasi cache karena keduanya tidak mungkin berubah hingga upgrade dilakukan.
Anda dapat melihat seluruh konfigurasi yang diadopsi Next.js di webpack-config.ts
.
Permintaan HTTP lainnya
SplitChunksPlugin
menentukan dasar untuk pengelompokan terperinci, dan menerapkan pendekatan ini ke
framework seperti Next.js bukanlah konsep yang sepenuhnya baru. Namun, banyak framework masih terus
menggunakan satu heuristik dan strategi paket "commons" karena beberapa alasan. Hal ini termasuk kekhawatiran bahwa
ada lebih banyak permintaan HTTP yang dapat memengaruhi performa situs secara negatif.
Browser hanya dapat membuka koneksi TCP dalam jumlah terbatas ke satu origin (6 untuk Chrome), sehingga meminimalkan jumlah potongan yang dihasilkan oleh bundler dapat memastikan bahwa jumlah total permintaan tetap di bawah nilai minimum ini. Namun, hal ini hanya berlaku untuk HTTP/1.1. Multiplexing di HTTP/2 memungkinkan beberapa permintaan di-streaming secara paralel menggunakan satu koneksi melalui satu asal. Dengan kata lain, kita umumnya tidak perlu khawatir untuk membatasi jumlah potongan yang dikeluarkan oleh bundler.
Semua browser utama mendukung HTTP/2. Tim Chrome dan Next.js
ingin melihat apakah meningkatkan jumlah permintaan dengan memisahkan satu paket "commons" Next.js
menjadi beberapa bagian bersama akan memengaruhi performa pemuatan dengan cara apa pun. Mereka memulai dengan mengukur
performa satu situs sambil mengubah jumlah maksimum permintaan paralel menggunakan
properti
maxInitialRequests
.
Dalam rata-rata tiga kali pengoperasian beberapa uji coba di satu halaman web, waktu
load
,
render awal,
dan First Contentful Paint semuanya tetap sama saat memvariasikan jumlah permintaan awal
maksimum (dari 5 hingga 15). Yang menarik, kami melihat sedikit overhead performa hanya
setelah memisahkan secara agresif ke ratusan permintaan.
Hal ini menunjukkan bahwa tetap berada di bawah nilai minimum yang andal (20~25 permintaan) akan mencapai keseimbangan yang tepat
antara performa pemuatan dan efisiensi penyimpanan dalam cache. Setelah beberapa pengujian dasar, 25 dipilih sebagai jumlah maxInitialRequest
.
Mengubah jumlah maksimum permintaan yang terjadi secara paralel menghasilkan lebih dari satu paket bersama, dan memisahkannya dengan tepat untuk setiap titik entri secara signifikan mengurangi jumlah kode yang tidak diperlukan untuk halaman yang sama.
Eksperimen ini hanya bertujuan mengubah jumlah permintaan untuk melihat apakah akan ada
pengaruh negatif pada performa pemuatan halaman. Hasilnya menunjukkan bahwa menetapkan maxInitialRequests
ke
25
di halaman pengujian adalah optimal karena mengurangi ukuran payload JavaScript tanpa memperlambat
halaman. Jumlah total JavaScript yang diperlukan untuk melakukan hidrasi halaman masih
tetap sama, yang menjelaskan mengapa performa pemuatan halaman tidak selalu meningkat dengan jumlah
kode yang dikurangi.
webpack menggunakan 30 KB sebagai ukuran minimum default untuk potongan yang akan dibuat. Namun, menggabungkan
nilai maxInitialRequests
25 dengan ukuran minimum 20 KB akan menghasilkan penyimpanan dalam cache yang lebih baik.
Pengurangan ukuran dengan potongan terperinci
Banyak framework, termasuk Next.js, mengandalkan pemilihan rute sisi klien (ditangani oleh JavaScript) untuk memasukkan tag skrip yang lebih baru untuk setiap transisi rute. Namun, bagaimana cara menentukan potongan dinamis ini terlebih dahulu pada waktu build?
Next.js menggunakan file manifes build sisi server untuk menentukan bagian output mana yang digunakan oleh titik entri yang berbeda. Untuk memberikan informasi ini kepada klien juga, file manifes build sisi klien yang disingkat dibuat untuk memetakan semua dependensi untuk setiap titik entri.
// Returns a promise for the dependencies for a particular route
getDependencies (route) {
return this.promisedBuildManifest.then(
man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
)
}
Strategi pengelompokan terperinci yang lebih baru ini pertama kali diluncurkan di Next.js dengan flag, yang diuji pada sejumlah pengguna awal. Banyak situs mengalami pengurangan yang signifikan pada total JavaScript yang digunakan untuk seluruh situs mereka:
Situs | Total Perubahan JS | % Perbedaan |
---|---|---|
https://www.barnebys.com/ | -238 KB | -23% |
https://sumup.com/ | -220 KB | -30% |
https://www.hashicorp.com/ | -11 MB | -71% |
Versi final dikirim secara default dalam versi 9.2.
Gatsby
Gatsby sebelumnya mengikuti pendekatan yang sama dalam menggunakan heuristik berbasis penggunaan untuk menentukan modul umum:
config.optimization = {
…
splitChunks: {
name: false,
chunks: `all`,
cacheGroups: {
default: false,
vendors: false,
commons: {
name: `commons`,
chunks: `all`,
// if a chunk is used more than half the components count,
// we can assume it's pretty global
minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
},
react: {
name: `commons`,
chunks: `all`,
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
},
Dengan mengoptimalkan konfigurasi webpack untuk mengadopsi strategi pengelompokan terperinci yang serupa, mereka juga melihat pengurangan JavaScript yang cukup besar di banyak situs besar:
Situs | Total Perubahan JS | % Perbedaan |
---|---|---|
https://www.gatsbyjs.org/ | -680 KB | -22% |
https://www.thirdandgrove.com/ | -390 KB | -25% |
https://ghost.org/ | -1,1 MB | -35% |
https://reactjs.org/ | -80 Kb | -8% |
Lihat PR untuk memahami cara mereka menerapkan logika ini ke konfigurasi webpack, yang dikirimkan secara default di v2.20.7.
Kesimpulan
Konsep pengiriman potongan terperinci tidak khusus untuk Next.js, Gatsby, atau bahkan webpack. Semua orang harus mempertimbangkan untuk meningkatkan strategi pengelompokan aplikasi mereka jika mengikuti pendekatan app bundle "commons" yang besar, terlepas dari framework atau bundler modul yang digunakan.
- Jika Anda ingin melihat pengoptimalan pengelompokan yang sama diterapkan ke aplikasi React vanilla, lihat contoh aplikasi React ini. Aplikasi ini menggunakan versi sederhana dari strategi pengelompokan terperinci dan dapat membantu Anda mulai menerapkan jenis logika yang sama ke situs Anda.
- Untuk Rollup, bagian dibuat secara terperinci secara default. Lihat
manualChunks
jika Anda ingin mengonfigurasi perilaku secara manual.