Gezinme önceden yükleyerek Service Worker'ı hızlandırın

Gezinme ön yüklemesi, paralel istekler göndererek hizmet çalışanı başlatma süresinin üstesinden gelmenize olanak tanır.

Jake Archibald
Jake Archibald

Tarayıcı Desteği

  • 59
  • 18
  • 99
  • 15,4

Kaynak

Özet

Sorun

Getirme etkinliklerini işlemek için Service Worker'ı kullanan bir siteye gittiğinizde tarayıcı, hizmet çalışanından yanıt ister. Bu işlem, hizmet çalışanının başlatılmasını (çalışmıyorsa) ve getirme etkinliğinin gönderilmesini içerir.

Başlatma süresi cihaza ve koşullara bağlıdır. Genellikle yaklaşık 50 ms. Mobil cihazlarda bu süre 250 ms'ye yakındır. Ekstrem durumlarda (yavaş cihazlar, sorun yaşayan CPU) bu değer 500 ms'nin üzerinde olabilir. Ancak, Service Worker etkinlikler arasında tarayıcıda belirlenen süre boyunca uyanık kaldığı için bu gecikmeyi yalnızca ara sıra (ör. kullanıcı yeni bir sekmeden veya başka bir siteden sitenize gittiğinde) alırsınız.

Önbellekten yanıt veriyorsanız başlatma süresi sorun yaratmaz, çünkü ağı atlamanın yararı, başlatma gecikmesinden daha uzundur. Ancak ağı kullanarak yanıt veriyorsanız...

SW önyüklemesi
Gezinme isteği

Ağ isteği, hizmet çalışanının başlatılması nedeniyle gecikir.

V8'de kod önbelleğe alma özelliğini kullanarak, getirme etkinliği olmayan hizmet çalışanlarını atlayarak, service çalışanlarını tahmine dayalı olarak başlatarak ve diğer optimizasyonlarla yeniden başlatma süresini azaltmaya devam ediyoruz. Ancak önyükleme süresi her zaman sıfırdan büyük olur.

Facebook bu sorunun etkisini dikkatimize sundu ve buna paralel olarak navigasyon isteklerini yerine getirmenin bir yolunu istedi:

SW önyüklemesi
Gezinme isteği



"Evet, adil görünüyor" dedik.

"Navigasyon önceden yükleniyor" dersiniz

Gezinmeyi önceden yükleme özelliği, "Kullanıcı bir GET gezinme isteğinde bulunduğunda, hizmet çalışanı başlatılırken ağ isteğini başlat" demenize olanak tanıyan bir özelliktir.

Başlatma gecikmesi hâlâ söz konusudur, ancak ağ isteğini engellemez, bu nedenle kullanıcıya içerik daha kısa sürede ulaşır.

Aşağıda, bir zaman döngüsü kullanılarak hizmet çalışanına 500 ms'lik bir başlatma gecikmesinin verildiği uygulama videosunu bulabilirsiniz:

Demonun kendisi buradadır. Gezinmenin önceden yüklenmesinin avantajlarından yararlanmak için bunu destekleyen bir tarayıcıya ihtiyacınız vardır.

Gezinme önceden yüklemesini etkinleştirme

addEventListener('activate', event => {
  event.waitUntil(async function() {
    // Feature-detect
    if (self.registration.navigationPreload) {
      // Enable navigation preloads!
      await self.registration.navigationPreload.enable();
    }
  }());
});

navigationPreload.enable() numaralı telefonu istediğiniz zaman arayabilir veya navigationPreload.disable() ile bu özelliği devre dışı bırakabilirsiniz. Ancak fetch etkinliğinizin bu özellikten yararlanması gerektiğinden bu etkinliği Service Worker'ınızın activate etkinliğinde etkinleştirmeniz/devre dışı bırakmanız en iyi seçenektir.

Önceden yüklenmiş yanıtı kullanma

Artık tarayıcı, gezinmeler için önceden yüklemeler gerçekleştirir ancak yanıtı kullanmanız gerekir:

addEventListener('fetch', event => {
  event.respondWith(async function() {
    // Respond from the cache if we can
    const cachedResponse = await caches.match(event.request);
    if (cachedResponse) return cachedResponse;

    // Else, use the preloaded response, if it's there
    const response = await event.preloadResponse;
    if (response) return response;

    // Else try the network.
    return fetch(event.request);
  }());
});

event.preloadResponse, şu durumlarda yanıt olarak verilen bir vaattir:

  • Gezinme ön yüklemesi etkin.
  • İstek, bir GET isteği.
  • İstek, bir gezinme isteğidir (tarayıcıların, iframe'ler dahil olmak üzere sayfaları yüklerken oluşturduğu).

Aksi takdirde event.preloadResponse hâlâ mevcuttur ancak undefined ile çözümlenir.

Sayfanız için ağdan veri alınması gerekiyorsa en hızlı yöntem bu verileri hizmet çalışanından istemek ve önbellekten parçalar ile ağdan parçalar içeren tek bir akışlı yanıt oluşturmaktır.

Bir makale görüntülemek istediğimizi varsayalım:

addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  const includeURL = new URL(url);
  includeURL.pathname += 'include';

  if (isArticleURL(url)) {
    event.respondWith(async function() {
      // We're going to build a single request from multiple parts.
      const parts = [
        // The top of the page.
        caches.match('/article-top.include'),
        // The primary content
        fetch(includeURL)
          // A fallback if the network fails.
          .catch(() => caches.match('/article-offline.include')),
        // The bottom of the page
        caches.match('/article-bottom.include')
      ];

      // Merge them all together.
      const {done, response} = await mergeResponses(parts);

      // Wait until the stream is complete.
      event.waitUntil(done);

      // Return the merged response.
      return response;
    }());
  }
});

Yukarıdaki örnekte, her bir isteğin akışlarını birleştiren mergeResponses küçük bir işlevdir. Bu, ağ içeriğinin akışı sırasında önbelleğe alınmış başlığı görüntüleyebileceğimiz anlamına gelir.

Bu yöntem, "uygulama kabuğu" modelinden daha hızlıdır. Çünkü ağ isteği, sayfa isteğiyle birlikte yapılır ve içerik, büyük saldırılar olmadan yayınlanabilir.

Ancak includeURL isteği, hizmet çalışanının başlatma süresine göre ertelenir. Bunu düzeltmek için de gezinme ön yüklemesini kullanabiliriz, ancak bu durumda tam sayfayı önceden yüklemek istemeyiz ve bir dahil etme işlemini önceden yüklemek istiyoruz.

Bunu desteklemek için, her önceden yükleme isteğinde bir başlık gönderilir:

Service-Worker-Navigation-Preload: true

Sunucu, gezinme önceden yükleme istekleri için normal gezinme isteğinden farklı içerik göndermek üzere bunu kullanabilir. Önbelleklerin yanıtlarınızın farklı olduğunu bilmesi için Vary: Service-Worker-Navigation-Preload üstbilgisi eklemeyi unutmayın.

Artık ön yükleme isteğini kullanabiliriz:

// Try to use the preload
const networkContent = Promise.resolve(event.preloadResponse)
  // Else do a normal fetch
  .then(r => r || fetch(includeURL))
  // A fallback if the network fails.
  .catch(() => caches.match('/article-offline.include'));

const parts = [
  caches.match('/article-top.include'),
  networkContent,
  caches.match('/article-bottom')
];

Üstbilgiyi değiştirme

Varsayılan olarak, Service-Worker-Navigation-Preload üst bilgisinin değeri true şeklindedir, ancak istediğiniz gibi ayarlayabilirsiniz:

navigator.serviceWorker.ready.then(registration => {
  return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
  console.log('Done!');
});

Örneğin, bunu yerel olarak önbelleğe aldığınız son yayının kimliğine ayarlayabilirsiniz. Böylece, sunucu yalnızca daha yeni verileri döndürür.

Durum bilgisi alınıyor

getState kullanarak navigasyonun önceden yüklenmesinin durumunu arayabilirsiniz:

navigator.serviceWorker.ready.then(registration => {
  return registration.navigationPreload.getState();
}).then(state => {
  console.log(state.enabled); // boolean
  console.log(state.headerValue); // string
});

Bu özellikle ilgili çalışmaları ve bu makaleyle ilgili yardımları için Matt Falkenhagen ve Tsuyoshi Horo'ya çok teşekkür ederiz. Standartlaştırma çalışmalarına dahil olan herkese çok teşekkür ederiz.

Yeni birlikte çalışabilirlik serisinin kapsamında