Kod bölmeli JavaScript

Büyük JavaScript kaynaklarının yüklenmesi, sayfa hızını önemli ölçüde etkiler. JavaScript'inizi daha küçük parçalara bölmek ve yalnızca bir sayfanın başlatma sırasında çalışması için gerekenleri indirmek, sayfanızın yükleme yanıt hızını büyük ölçüde iyileştirebilir. Bu da sayfanızın Sonraki Boyamaya Kadar Etkileşim (INP) değerini iyileştirebilir.

Bir sayfa büyük JavaScript dosyalarını indirirken, ayrıştırırken ve derlerken bir süre yanıt vermeyebilir. Sayfa öğeleri, sayfanın ilk HTML'sinin bir parçası oldukları ve CSS ile stilize edildikleri için görünür. Ancak, bu etkileşimli öğeleri desteklemek için gereken JavaScript'in yanı sıra sayfa tarafından yüklenen diğer komut dosyaları, bu öğelerin çalışması için JavaScript'i ayrıştırıp yürütebilir. Sonuç olarak kullanıcı, etkileşimin önemli ölçüde geciktiğini veya tamamen bozulduğunu düşünebilir.

Bu durum genellikle ana iş parçacığı engellendiği için meydana gelir. Bunun nedeni, JavaScript'in ana iş parçacığında ayrıştırılıp derlenmesidir. Bu işlem çok uzun sürerse etkileşimli sayfa öğeleri, kullanıcı girişine yeterince hızlı yanıt vermeyebilir. Bunun bir çözümü, sayfanın çalışması için gereken JavaScript'i yüklemek ve diğer JavaScript'leri daha sonra yüklenmek üzere ertelemektir. Bu işlem, kod bölme olarak bilinen bir teknikle yapılır. Bu modülde, bu iki teknikten ikincisine odaklanılmaktadır.

Kod bölme yoluyla başlangıç sırasında JavaScript ayrıştırma ve yürütme süresini azaltma

Lighthouse, JavaScript yürütme süresi 2 saniyeyi aştığında uyarı verir ve 3,5 saniyeyi aştığında başarısız olur. Aşırı JavaScript ayrıştırma ve yürütme, sayfa yaşam döngüsünün herhangi bir noktasında olası bir sorundur. Çünkü kullanıcının sayfayla etkileşim kurduğu an, JavaScript'i işlemek ve yürütmekten sorumlu ana iş parçacığı görevlerinin çalıştığı ana denk gelirse etkileşimin giriş gecikmesini artırabilir.

Ayrıca, aşırı JavaScript yürütme ve ayrıştırma, özellikle ilk sayfa yükleme sırasında sorun yaratır. Çünkü bu, sayfa yaşam döngüsünde kullanıcıların sayfayla etkileşime girme olasılığının en yüksek olduğu noktadır. Hatta yükleme duyarlılığı metriği olan Toplam Engelleme Süresi (TBT), INP ile yüksek oranda ilişkilidir. Bu da kullanıcıların ilk sayfa yüklemesi sırasında etkileşimde bulunma eğiliminin yüksek olduğunu gösterir.

Sayfanızın istediği her JavaScript dosyasının yürütülmesinde harcanan süreyi bildiren Lighthouse denetimi, hangi komut dosyalarının kod bölme için uygun olabileceğini tam olarak belirlemenize yardımcı olabileceğinden faydalıdır. Ardından, Chrome Geliştirici Araçları'ndaki kapsam aracını kullanarak bir sayfanın JavaScript'inin hangi bölümlerinin sayfa yüklenirken kullanılmadığını tam olarak belirleyebilirsiniz.

Kod bölme, bir sayfanın başlangıçtaki JavaScript yüklerini azaltabilen faydalı bir tekniktir. Bu özellik, bir JavaScript paketini iki parçaya bölmenize olanak tanır:

  • Sayfa yüklenirken gereken JavaScript'tir ve bu nedenle başka bir zamanda yüklenemez.
  • Daha sonra yüklenebilen JavaScript'ler. Bu JavaScript'ler genellikle kullanıcı sayfadaki belirli bir etkileşimli öğeyle etkileşime geçtiğinde yüklenir.

Kod bölme işlemi, dynamic import() söz dizimi kullanılarak yapılabilir. Bu söz dizimi, başlangıç sırasında belirli bir JavaScript kaynağı isteyen <script> öğelerinin aksine, sayfa yaşam döngüsünün daha sonraki bir noktasında JavaScript kaynağı isteğinde bulunur.

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 });

Yukarıdaki JavaScript snippet'inde validate-form.mjs modülü yalnızca kullanıcı bir formun <input> alanlarından herhangi birini odak dışına aldığında indirilir, ayrıştırılır ve yürütülür. Bu durumda, formun doğrulama mantığını yönlendirmekten sorumlu JavaScript kaynağı, yalnızca gerçekten kullanılma olasılığı en yüksek olduğunda sayfayla etkileşime girer.

webpack, Parcel, Rollup ve esbuild gibi JavaScript paketleyiciler, kaynak kodunuzda dinamik bir import() çağrısıyla karşılaştıklarında JavaScript paketlerini daha küçük parçalara ayıracak şekilde yapılandırılabilir. Bu araçların çoğu bunu otomatik olarak yapar ancak özellikle esbuild'de bu optimizasyonu etkinleştirmeniz gerekir.

Kod bölme ile ilgili faydalı notlar

Kod bölme, ilk sayfa yükleme sırasında ana iş parçacığı çekişmesini azaltmak için etkili bir yöntem olsa da JavaScript kaynak kodunuzu kod bölme fırsatları için denetlemeye karar verirseniz birkaç şeyi aklınızda bulundurmanız gerekir.

Mümkünse paketleyici kullanın

Geliştiricilerin geliştirme sürecinde JavaScript modüllerini kullanması yaygın bir uygulamadır. Bu, kodun okunabilirliğini ve sürdürülebilirliğini iyileştiren mükemmel bir geliştirici deneyimi geliştirmesidir. Ancak JavaScript modüllerini üretime gönderirken ortaya çıkabilecek bazı ideal olmayan performans özellikleri vardır.

En önemlisi, kod bölme işlemi uygulamayı planladığınız modüller de dahil olmak üzere kaynak kodunuzu işlemek ve optimize etmek için bir paketleyici kullanmanız gerekir. Paketleyiciler, yalnızca JavaScript kaynak koduna optimizasyon uygulamakla kalmaz, aynı zamanda paket boyutu gibi performansla ilgili hususları sıkıştırma oranıyla dengelemede de oldukça etkilidir. Sıkıştırma etkinliği paket boyutuyla birlikte artar ancak paketleyiciler, komut dosyası değerlendirmesi nedeniyle uzun görevlere yol açacak kadar büyük olmamalarını da sağlamaya çalışır.

Paketleyiciler, ağ üzerinden çok sayıda paketsiz modül gönderme sorununu da önler. JavaScript modüllerini kullanan mimariler genellikle büyük ve karmaşık modül ağaçlarına sahiptir. Modül ağaçları paketten çıkarıldığında her modül ayrı bir HTTP isteğini temsil eder ve modülleri paketlemezseniz web uygulamanızdaki etkileşim gecikebilir. Büyük modül ağaçlarını mümkün olduğunca erken yüklemek için <link rel="modulepreload"> kaynak ipucunu kullanmak mümkün olsa da yükleme performansı açısından JavaScript paketleri hâlâ tercih edilir.

Derleme akışını yanlışlıkla devre dışı bırakmayın

Chromium'un V8 JavaScript motoru, üretim JavaScript kodunuzun mümkün olduğunca verimli bir şekilde yüklenmesini sağlamak için kullanıma hazır bir dizi optimizasyon sunar. Bu optimizasyonlardan biri akış derlemesi olarak bilinir. Bu optimizasyon, tarayıcıya aktarılan HTML'nin artımlı ayrıştırılması gibi, ağdan geldikçe aktarılan JavaScript parçalarını derler.

Chromium'da web uygulamanız için derleme akışının gerçekleşmesini sağlamanın birkaç yolu vardır:

  • JavaScript modüllerini kullanmamak için üretim kodunuzu dönüştürün. Paketleyiciler, derleme hedefine göre JavaScript kaynak kodunuzu dönüştürebilir ve hedef genellikle belirli bir ortama özgüdür. V8, modül kullanmayan tüm JavaScript kodlarına akış derlemesi uygular. Ayrıca, paketleyicinizi JavaScript modülü kodunuzu JavaScript modüllerini ve özelliklerini kullanmayan bir söz dizimine dönüştürecek şekilde yapılandırabilirsiniz.
  • JavaScript modüllerini üretime göndermek istiyorsanız .mjsuzantısını kullanın. Üretim JavaScript'inizin modül kullanıp kullanmadığına bakılmaksızın, modül kullanan JavaScript ile kullanmayan JavaScript arasında özel bir içerik türü yoktur. V8 söz konusu olduğunda, .js uzantısını kullanarak JavaScript modüllerini üretimde gönderdiğinizde derlemeyi yayınlamayı etkili bir şekilde devre dışı bırakırsınız. JavaScript modülleri için .mjs uzantısını kullanırsanız V8, modül tabanlı JavaScript kodu için akış derlemesinin bozulmamasını sağlayabilir.

Bu hususların kod bölme özelliğini kullanmanızı engellemesine izin vermeyin. Kod bölme, kullanıcılara gönderilen ilk JavaScript yüklerini azaltmanın etkili bir yoludur. Ancak bir paketleyici kullanarak ve V8'in akış derleme davranışını nasıl koruyabileceğinizi bilerek üretim JavaScript kodunuzun kullanıcılar için olabildiğince hızlı olmasını sağlayabilirsiniz.

Dinamik içe aktarma demosu

webpack

webpack, SplitChunksPlugin adlı bir eklentiyle birlikte gelir. Bu eklenti, paketleyicinin JavaScript dosyalarını nasıl böleceğini yapılandırmanıza olanak tanır. webpack, hem dinamik import() hem de statik import ifadelerini tanır. SplitChunksPlugin davranışını, yapılandırmasında chunks seçeneğini belirleyerek değiştirebilirsiniz:

  • chunks: async varsayılan değerdir ve dinamik import() çağrılarını ifade eder.
  • chunks: initial, statik import aramaları ifade eder.
  • chunks: all hem dinamik import() hem de statik içe aktarmaları kapsar. Bu sayede async ve initial içe aktarmaları arasında parçalar paylaşabilirsiniz.

Varsayılan olarak, webpack dinamik bir import() ifadesiyle karşılaştığında bu modül için ayrı bir parça oluşturur:

/* 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');
}

Yukarıdaki kod snippet'i için varsayılan webpack yapılandırması iki ayrı parça oluşturur:

  • main.js ve ./my-function.js modülünü içeren, webpack'in initial parçası olarak sınıflandırdığı main.js parçası.
  • Yalnızca form-validation.js (yapılandırılmışsa kaynak adında dosya karması içeren) içeren async parçası. Bu parça yalnızca condition doğru olduğunda indirilir.

Bu yapılandırma, form-validation.js parçanın yüklenmesini gerçekten ihtiyaç duyulana kadar ertelemenize olanak tanır. Bu, ilk sayfa yüklemesi sırasında komut dosyası değerlendirme süresini kısaltarak yükleme yanıt hızını artırabilir. Belirtilen bir koşul karşılandığında form-validation.js parçası için komut dosyası indirme ve değerlendirme işlemi gerçekleşir. Bu durumda, dinamik olarak içe aktarılan modül indirilir. Örneğin, bir polyfill'in yalnızca belirli bir tarayıcı için indirildiği veya önceki örnekte olduğu gibi, içe aktarılan modülün kullanıcı etkileşimi için gerekli olduğu bir durum olabilir.

Diğer yandan, SplitChunksPlugin yapılandırmasını chunks: initial olarak değiştirmek, kodun yalnızca ilk parçalarda bölünmesini sağlar. Bunlar, statik olarak içe aktarılan veya webpack'in entry özelliğinde listelenenler gibi parçalardır. Önceki örneğe baktığımızda, ortaya çıkan parça tek bir komut dosyası içinde form-validation.js ve main.js kombinasyonu olur. Bu da başlangıçtaki sayfa yükleme performansının daha kötü olmasına neden olabilir.

SplitChunksPlugin seçenekleri, daha büyük komut dosyalarını birden fazla küçük komut dosyasına ayıracak şekilde de yapılandırılabilir. Örneğin, maxSize seçeneği kullanılarak, maxSize tarafından belirtilen boyutu aşan parçaların ayrı dosyalara bölünmesi için webpack'e talimat verilebilir. Büyük komut dosyalarını daha küçük dosyalara bölmek, yükleme yanıt hızını artırabilir. Bazı durumlarda, CPU yoğun komut dosyası değerlendirme çalışması daha küçük görevlere bölünür. Bu görevlerin ana iş parçacığını daha uzun süre engelleme olasılığı daha düşüktür.

Ayrıca, daha büyük JavaScript dosyaları oluşturmak, komut dosyalarının önbelleği geçersiz kılma işleminden etkilenme olasılığının daha yüksek olduğu anlamına da gelir. Örneğin, hem çerçeve hem de birinci taraf uygulama kodu içeren çok büyük bir komut dosyası gönderirseniz yalnızca çerçeve güncellenir ancak paketlenmiş kaynakta başka hiçbir şey güncellenmezse paketin tamamı geçersiz kılınabilir.

Diğer yandan, daha küçük komut dosyaları, geri gelen ziyaretçilerin kaynakları önbellekten alma olasılığını artırarak tekrar ziyaretlerde sayfaların daha hızlı yüklenmesini sağlar. Ancak daha küçük dosyalar, daha büyük dosyalara kıyasla sıkıştırmadan daha az yararlanır ve önceden doldurulmamış bir tarayıcı önbelleğiyle sayfa yüklemelerinde ağ gidiş dönüş süresini artırabilir. Önbelleğe alma verimliliği, sıkıştırma etkinliği ve komut dosyası değerlendirme süresi arasında denge kurulmasına dikkat edilmelidir.

webpack demosu

webpack SplitChunksPlugin demo.

Bilginizi test etme

Kod bölme işlemi gerçekleştirilirken hangi tür import ifadesi kullanılır?

Dinamik import().
Doğru!
Statik import.
Tekrar deneyin.

Hangi tür import ifadesi JavaScript modülünün en üstünde yer almalı ve başka hiçbir yerde bulunmamalıdır?

Dinamik import().
Tekrar deneyin.
Statik import.
Doğru!

Webpack'te SplitChunksPlugin kullanılırken async parçası ile initial parçası arasındaki fark nedir?

async parçaları dinamik import() kullanılarak, initial parçaları ise statik import kullanılarak yüklenir.
Doğru!
async parçaları statik import kullanılarak, initial parçaları ise dinamik import() kullanılarak yüklenir.
Tekrar deneyin.

Sıradaki: Resimleri ve <iframe> öğelerini geç yükleme

JavaScript, oldukça maliyetli bir kaynak türü olsa da yüklenmesini erteleyebileceğiniz tek kaynak türü değildir. Resim ve <iframe> öğeleri kendi başlarına maliyetli kaynaklar olabilir. JavaScript'e benzer şekilde, bu kursun bir sonraki modülünde açıklanan tembel yükleme ile resimlerin ve <iframe> öğesinin yüklenmesini erteleyebilirsiniz.