Next.js ve Gatsby'deki daha yeni bir web paketi parçalama stratejisi, sayfa yükleme performansını iyileştirmek için yinelenen kodları en aza indirir.
Chrome, JavaScript açık kaynak ekosistemindeki araçlar ve çerçevelerle ortak çalışmalar yürütmektedir. Next.js ve Gatsby'nin yükleme performansını iyileştirmek için kısa süre önce birkaç yeni optimizasyon eklendi. Bu makalede, artık her iki çerçevede de varsayılan olarak gönderilen iyileştirilmiş ayrıntılı bir öbekleme stratejisi ele alınmaktadır.
Giriş
Birçok web çerçevesi gibi Next.js ve Gatsby de temel paketleyici olarak webpack'i kullanır. Webpack v3, tek bir (veya birkaç) "ortak" parçada (ya da parçalarda) farklı giriş noktaları arasında paylaşılan çıkış modüllerini mümkün kılmak için CommonsChunkPlugin
'i kullanıma sundu. Paylaşılan kod, ayrı olarak indirilip tarayıcı önbelleğinde erkenden depolanabilir. Bu da daha iyi bir yükleme performansı sağlayabilir.
Bu kalıp, tek sayfalık uygulama çerçevesinin bir giriş noktası ve paket yapılandırmasının benimsediği şu şekilde popüler hale geldi:
Pratik olsa da tüm paylaşılan modül kodlarını tek bir parçada paketleme kavramının bazı sınırlamaları vardır. Her giriş noktasında paylaşılmayan modüller, onu kullanmayan rotalar için indirilebilir. Bu da gereğinden fazla kodun indirilmesine neden olur. Örneğin, page1
, common
parçasını yüklediğinde page1
, moduleC
kullanmasa bile moduleC
kodunu yükler.
Bu nedenle, birkaç diğer eklentiyle birlikte webpack v4, eklentiyi yeni bir eklenti için kaldırdı: SplitChunksPlugin
.
Geliştirilmiş Yığın Oluşturma
SplitChunksPlugin
uygulamasının varsayılan ayarları çoğu kullanıcı için uygundur. Birden fazla rotada yinelenen kodun getirilmesini önlemek için bir dizi conditions bağlı olarak birden fazla bölünmüş parça oluşturulur.
Bununla birlikte, bu eklentiyi kullanan birçok web çerçevesi, parça bölme için hâlâ "tek yaygın" yaklaşımını izler. Örneğin Next.js, sayfaların% 50'sinden fazlasında ve tüm çerçeve bağımlılıklarında (react
, react-dom
vb.) kullanılan herhangi bir modülü 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)[\\/]/,
},
},
},
Çerçeveye bağlı kodların paylaşılan bir parçaya dahil edilmesi, 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 yaygın modüllerin dahil edilmesiyle ilgili kullanıma dayalı bulgular pek etkili değildir. Bu oranın değiştirilmesi iki sonuçtan yalnızca biriyle sonuçlanır:
- Oranı azaltırsanız daha fazla gereksiz kod indirilir.
- Oranı artırırsanız daha fazla kod birden çok rotada yinelenir.
Next.js, bu sorunu çözmek amacıylaSplitChunksPlugin
için herhangi bir rotadaki gereksiz kodları azaltan farklı bir yapılandırma benimsemiştir.
- Yeterli büyüklükte herhangi bir üçüncü taraf modülü (160 KB'tan büyük), kendi parçasına bölünür
- Çerçeve bağımlılıkları (
react
,react-dom
vb.) için ayrı birframeworks
parçası oluşturulur - Gerektiği kadar paylaşılan parça oluşturulur (en fazla 25)
- Oluşturulacak minimum veri parçası boyutu 20 KB olarak değiştirildi
Bu ayrıntılı parçalama stratejisi aşağıdaki avantajları sağlar:
- Sayfa yüklenme süreleri iyileştirildi. Tek bir yerine birden fazla paylaşılan parçanın yayınlanması, 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ını ayrı parçalara bölmek, yükseltme yapılana kadar ikisinin de değişme olasılığı düşük olduğundan önbelleği geçersiz kılma olasılığını azaltır.
Next.js'nin benimsediği yapılandırmanın tamamını webpack-config.ts
'te görebilirsiniz.
Daha fazla HTTP isteği
SplitChunksPlugin
, ayrıntılı öbeklemenin temelini tanımlamıştı. Bu yaklaşımı Next.js gibi bir çerçeveye uygulamak ise tamamen yeni bir kavram değildi. Bununla birlikte birçok çerçeve, birkaç nedenden ötürü tek bir bulgusal ve "yaygın kullanılanlar" paket stratejisi kullanmaya devam etti. Buna, çok daha fazla HTTP isteğinin site performansını olumsuz etkileyebileceği endişesi de dahildir.
Tarayıcılar, tek bir kaynağa yalnızca sınırlı sayıda TCP bağlantısı (Chrome için 6) açabilir. Bu nedenle, bir paketleyici tarafından oluşturulan parça sayısının en aza indirilmesi, toplam istek sayısının bu eşiğin altında kalmasını sağlayabilir. Ancak bu yalnızca HTTP/1.1 için geçerlidir. HTTP/2'de Multiplex, birden fazla isteğin tek bir kaynak üzerinden tek bir bağlantı kullanılarak paralel olarak akışına izin verir. Diğer bir deyişle, genellikle paketleyicimizin yayınladığı parça sayısını sınırlama konusunda endişelenmemize gerek yoktur.
Başlıca tüm tarayıcılar HTTP/2'yi destekler. Chrome ve Next.js ekipleri, Next.js'nin tek "yaygın" paketini birden fazla paylaşılan parçaya bölerek istek sayısını artırmanın, yükleme performansını herhangi bir şekilde etkileyip etkilemeyeceğini görmek istedi. maxInitialRequests
özelliğini kullanarak, tek bir sitenin performansını ölçerken maksimum paralel istek sayısını değiştirerek başladılar.
Tek bir web sayfasında birden çok denemenin ortalama üç çalışmasında, maksimum ilk istek sayısı (5'ten 15'e) değiştirilirken load
, start-render ve First Contentful Paint zamanlarının hepsi neredeyse aynı kaldı. İşin ilginç tarafı, ancak yüzlerce isteği agresif bir şekilde böldükten sonra hafif bir performans ek yükü fark ettik.
Bu, 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. Birkaç temel testten sonra maxInitialRequest
sayısı olarak 25 seçildi.
Birbirine paralel olarak gerçekleşen maksimum istek sayısının değiştirilmesi, birden fazla paylaşılan paketle sonuçlandı ve bunları her giriş noktası için uygun şekilde ayırmak, aynı sayfa için gereksiz kod miktarını önemli ölçüde azalttı.
Bu denemenin tek amacı, sayfa yükleme performansı üzerinde herhangi bir olumsuz etki olup olmadığını görmek için istek sayısının değiştirilmesiydi. Sonuçlar, sayfayı yavaşlatmadan JavaScript yükünün boyutunu azalttığı için test sayfasında maxInitialRequests
değerini 25
olarak ayarlamanın en uygun seçenek olduğunu göstermektedir. Sayfayı kullanılabilir hale getirmek için gereken toplam JavaScript miktarı hâlâ aynı kalmıştır. Bu durum, sayfa yükleme performansının neden az miktarda kod kullanılmasıyla birlikte iyileşmediğini açıklar.
webpack, parçanın oluşturulması için varsayılan minimum boyut olarak 30 KB kullanır. Ancak 25 değerindeki maxInitialRequests
değerini 20 KB'lık minimum boyutla birleştirmek, önbelleğe alma işleminin daha iyi olmasını sağladı.
Ayrıntılı parçalarla boyutu küçültme
Next.js de dahil olmak üzere birçok çerçeve, her rota geçişi için yeni komut dosyası etiketleri eklemek üzere istemci tarafı yönlendirme (JavaScript tarafından işlenir) kullanır. Peki bu dinamik parçaları derleme sırasında nasıl önceden belirliyorlar?
Next.js, hangi çıktı parçalarının farklı giriş noktaları tarafından kullanıldığını belirlemek için sunucu tarafı derleme manifest dosyası kullanır. Bu bilgileri istemciye de sağlamak için her giriş noktasının tüm bağımlılıklarını 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ı öbekleme stratejisi ilk olarak Next.js'de bir işaretin arkasında kullanıma sunuldu ve burada bir dizi ilk kullanıcı üzerinde test edildi. Birçok kişi, sitesinin tamamında kullanılan toplam JavaScript'te önemli ölçüde düşüş gördü:
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 |
Varsayılan olarak son sürüm, sürüm 9.2'de gönderilmiştir.
Gatsby
Gatsby, ortak modülleri tanımlamak için kullanıma dayalı buluşsal yaklaşımla aynı yaklaşımı izlerdi:
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)[\\/]/,
},
Web paketi yapılandırmasını benzer ayrıntılı bir öbekleme stratejisi kullanacak şekilde optimize ederek, birçok büyük sitede JavaScript'te önemli ölçüde azalma olduğunu da 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 2.20.7 sürümünde gönderilen web paketi yapılandırmasına nasıl uyguladıklarını anlamak için PR bölümüne göz atın.
Sonuç
Ayrıntılı parçalar gönderme kavramı Next.js, Gatsby ve hatta webpack'e özgü değildir. Kullanılan çerçeve veya modül paketleyiciden bağımsız olarak, büyük bir "yaygın" paket yaklaşımı benimserse herkes, uygulamasının yığın oluşturma stratejisini iyileştirmeyi düşünmelidir.
- Vanilla React uygulamasına aynı bölme optimizasyonlarının uygulandığını görmek istiyorsanız bu örnek React uygulamasına göz atın. Bu uygulama, ayrıntılı öbekleme stratejisinin basitleştirilmiş bir sürümünü kullanır ve sitenize aynı mantığı uygulamaya başlamanıza yardımcı olabilir.
- Toplayıcı Parçalar'da, parçalar varsayılan olarak ayrıntılı şekilde oluşturulur. Davranışı manuel olarak yapılandırmak istiyorsanız
manualChunks
platformuna göz atın.