Next.js ve Gatsby'deki daha yeni bir Webpack parçalara ayırma stratejisi, sayfa yükleme performansını artırmak için yinelenen kodu en aza indirir.
Chrome, JavaScript açık kaynak ekosistemindeki araç ve çerçevelerle ortak çalışıyor. Next.js ve Gatsby'nin yükleme performansını artırmak için kısa süre önce bir dizi yeni optimizasyon eklendi. Bu makalede artık her iki çerçevede de varsayılan olarak kullanıma sunulan iyileştirilmiş ayrıntılı parçalama stratejisi ele alınmaktadır.
Giriş
Birçok web çerçevesi gibi Next.js ve Gatsby de temel paketleyici olarak webpack'ı kullanır. webpack v3, farklı giriş noktaları arasında paylaşılan modülleri tek bir (veya birkaç) "ortak" parçada (veya parçalarda) yayınlamayı mümkün kılmak için CommonsChunkPlugin
'i kullanıma sundu. Paylaşılan kod ayrı olarak indirilebilir ve tarayıcı önbelleğine erkenden depolanabilir. Bu da daha iyi bir yükleme performansı sağlayabilir.
Bu kalıp, aşağıdaki gibi bir giriş noktası ve paket yapılandırmasını benimseyen birçok tek sayfalık uygulama çerçevesiyle popüler hale geldi:
Tüm paylaşılan modül kodlarını tek bir parçaya toplama kavramı pratik olsa da sınırlamaları vardır. Her giriş noktasında paylaşılmayan modüller, kullanmayan rotalar için indirilebilir. Bu durumda, gereğinden fazla kod indirilir. Örneğin, page1
common
parçasını yüklediğinde, page1
moduleC
kullanmasa bile moduleC
kodunu yükler.
Bu nedenle, webpack v4, birkaç ek eklentiyle birlikte bu eklentiyi kaldırıp yerine yeni bir eklenti ekledi: SplitChunksPlugin
.
İyileştirilmiş Chunking
SplitChunksPlugin
için varsayılan ayarlar çoğu kullanıcı için uygundur. Birden fazla rotada yinelenen kodun getirilmesini önlemek için çeşitli koşullara bağlı olarak birden fazla bölünmüş parça oluşturulur.
Bununla birlikte, bu eklentiyi kullanan birçok web çerçevesi hâlâ parça bölme için "tek ortak" yaklaşımını izlemektedir. Örneğin Next.js, sayfaların %50'sinden fazlasında kullanılan tüm modülleri ve tüm çerçeve bağımlılıkları (react
, react-dom
vb.) içeren bir commons
paketi oluşturur.
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)[\\/]/,
},
},
},
Paylaşılan bir parçaya çerçeveye bağımlı kod eklemek, bu kodun herhangi bir giriş noktası için indirilip önbelleğe alınabileceği anlamına gelse de sayfaların yarısından fazlasında kullanılan ortak modülleri eklemeyle ilgili kullanıma dayalı sezgisel yaklaşım çok etkili değildir. Bu oran değiştirildiğinde yalnızca iki sonuçtan biri elde edilir:
- Oranı düşürürseniz daha fazla gereksiz kod indirilir.
- Oran arttıkça birden fazla rotada daha fazla kod kopyalanır.
Next.js, bu sorunu çözmek amacıyla SplitChunksPlugin
için herhangi bir rota için gereksiz kodları azaltan farklı bir yapılandırma kullandı.
- Yeterince büyük üçüncü taraf modülleri (160 KB'tan büyük) kendi ayrı parçalarına ayrılır
- Çerçeve bağımlılıkları (
react
,react-dom
vb.) için ayrı birframeworks
yığını oluşturulur - Gerektiği kadar paylaşılan parça oluşturulur (en fazla 25)
- Yığının oluşturulması için minimum boyut 20 KB olarak değiştirildi
Bu ayrıntılı parçalara ayırma stratejisi aşağıdaki avantajları sağlar:
- Sayfa yüklenme süreleri iyileştirildi. Tek bir paylaşılamaz blok yerine birden fazla paylaşılamaz blok yayınlamak, herhangi bir giriş noktası için gereksiz (veya yinelenen) kod miktarını en aza indirir.
- Gezinme sırasında iyileştirilmiş önbelleğe alma. Büyük kitaplıkları ve çerçeve bağımlılıkları ayrı parçalara bölmek, yükseltme yapılana kadar ikisinin de değişme olasılığı düşük olduğundan önbelleğin geçersiz kılınmasını azaltır.
Next.js'in benimsediği yapılandırmaların tamamını webpack-config.ts
adresinde görebilirsiniz.
Daha fazla HTTP isteği
SplitChunksPlugin
, ayrıntılı parçalara ayırmanın temelini tanımladı ve bu yaklaşımı Next.js gibi bir çerçeveye uygulamak tamamen yeni bir kavram değildi. Ancak birçok çerçeve, birkaç nedenden dolayı tek bir sezgisel ve "ortak" paket stratejisi kullanmaya devam etti. Bu durum, çok daha fazla HTTP isteğinin site performansını olumsuz yönde etkileyebileceği endişesini de içerir.
Tarayıcılar tek bir kaynakla yalnızca sınırlı sayıda TCP bağlantısı açabilir (Chrome için 6). Bu nedenle, bir birleştirici tarafından oluşturulan parça sayısını en aza indirmek, toplam istek sayısının bu eşiğin altında kalmasını sağlayabilir. Ancak bu durum yalnızca HTTP/1.1 için geçerlidir. HTTP/2'de Multiplex, tek bir kaynak üzerinden tek bir bağlantı kullanılarak birden fazla isteğin paralel olarak akışına olanak tanır. Diğer bir deyişle, genellikle paketleyicimiz tarafından yayınlanan parça sayısını sınırlama konusunda endişelenmemize gerek yoktur.
Başlıca tarayıcıların tümü HTTP/2'yi destekler. Chrome ve Next.js ekipleri, Next.js'in tek "commons" paketini birden fazla paylaşılan parçaya ayırarak istek sayısını artırmanın yükleme performansını herhangi bir şekilde etkileyip etkilemediğini görmek istedi. maxInitialRequests
mülkünü kullanarak maksimum paralel istek sayısını değiştirirken tek bir sitenin performansını ölçerek başladılar.
Tek bir web sayfasında ortalama üç kez yapılan birden fazla denemede, maksimum ilk istek sayısı (5 ila 15 arasında) değiştiğinde load
, render'ı başlatma ve İlk Zengin İçerikli Boyama sürelerinin tümü yaklaşık olarak aynı kalmıştır. İlginç bir şekilde, yalnızca yüzlerce isteğe agresif bir şekilde bölündükten sonra küçük bir performans yükü fark ettik.
Bu sonuçlar, güvenilir bir eşiğin (20-25 istek) altında kalmanın, yükleme performansı ile önbelleğe alma verimliliği arasında doğru dengeyi sağladığını gösterdi. Bazı temel testlerden sonra maxInitialRequest
sayısı olarak 25 seçildi.
Paralel olarak gerçekleşen maksimum istek sayısını değiştirmek, tek bir paylaşılan paketten daha fazlasına yol açtı ve bunları her giriş noktası için uygun şekilde ayırmak, aynı sayfa için gereksiz kod miktarını önemli ölçüde azalttı.
Bu deneme, yalnızca sayfa yükleme performansında olumsuz bir etki olup olmayacağını görmek için istek sayısını değiştirmekle ilgiliydi. Sonuçlar, sayfayı yavaşlatmadan JavaScript yükü boyutunu azalttığı için test sayfasında maxInitialRequests
değerinin 25
olarak ayarlanmasının en uygun seçenek olduğunu gösteriyor. Sayfayı beslemek için gereken toplam JavaScript miktarı yaklaşık olarak aynı kalmıştır. Bu da sayfa yükleme performansının, kod miktarının azaltılmasıyla mutlaka iyileşmediğini açıklar.
webpack, oluşturulacak bir parça için varsayılan minimum boyut olarak 30 KB'yı kullanır. Ancak 25 değerine sahip bir maxInitialRequests
değerini 20 KB minimum boyutla birlikte kullanmak daha iyi önbelleğe almayla sonuçlandı.
Ayrıntılı parçalarla boyut azaltma
Next.js dahil olmak üzere birçok çerçeve, her rota geçişi için yeni komut dosyası etiketleri eklemek üzere istemci tarafı yönlendirmeyi (JavaScript tarafından yönetilir) kullanır. Peki bu dinamik parçaları derleme sırasında nasıl önceden belirleyebilirler?
Next.js, farklı giriş noktaları tarafından hangi çıkış parçalarının kullanıldığını belirlemek için sunucu tarafı bir derleme manifest dosyası kullanır. Bu bilgileri istemciye de sağlamak için her giriş noktasının tüm bağımlılıkları eşlemek üzere kısaltılmış bir istemci tarafı derleme manifest dosyası oluşturuldu.
// 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}`)) || []
)
}
Bu yeni ayrıntılı parçalara ayırma stratejisi, ilk olarak Next.js'de bir işaretle kullanıma sunuldu ve burada bir dizi ilk kullanıcı üzerinde test edildi. Birçok kullanıcı, sitelerinin tamamı için kullanılan toplam JavaScript miktarında önemli düşüşler elde etti:
Web sitesi | Toplam JS Değişikliği | % Farkı |
---|---|---|
https://www.barnebys.com/ | -238 KB | -%23 |
https://sumup.com/ | -220 KB | -%30 |
https://www.hashicorp.com/ | -11 MB | -%71 |
Nihai sürüm varsayılan olarak 9.2 sürümünde yayınlandı.
Gatsby
Gatsby, ortak modülleri tanımlamak için kullanıma dayalı bulgusal bir yöntemden yararlanırken aynı yaklaşımı benimsemiştir:
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)[\\/]/,
},
Benzer bir ayrıntılı parçalara ayırma stratejisi benimsemek için webpack yapılandırmalarını optimize ederek birçok büyük sitede JavaScript'te önemli ölçüde düşüşler de fark ettiler:
Web sitesi | Toplam JS Değişikliği | % Farkı |
---|---|---|
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 |
Bu mantığı, varsayılan olarak v2.20.7 sürümünde sunulan webpack yapılandırmalarına nasıl uyguladıklarını öğrenmek için PR'ye göz atın.
Sonuç
Ayrıntılı parçaları gönderme kavramı Next.js, Gatsby'ye, hatta webpack'e özgü değildir. Kullanılan çerçeve veya modül paketleyiciden bağımsız olarak, büyük bir "ortak" paket yaklaşımı izliyorsa herkes uygulamalarının parçalara ayırma stratejisini iyileştirmeyi düşünmelidir.
- Aynı parçalara ayırma optimizasyonlarının, basit bir React uygulamasına uygulandığını görmek istiyorsanız bu örnek React uygulamasına göz atın. Ayrıntılı parçalara ayırma stratejisinin basitleştirilmiş bir sürümünü kullanan bu uygulama, sitenize aynı tür mantığı uygulamaya başlamanıza yardımcı olabilir.
- Toplama için parçalar varsayılan olarak ayrıntılı şekilde oluşturulur. Davranışı manuel olarak yapılandırmak istiyorsanız
manualChunks
bölümüne göz atın.