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

Gezinme ön yükleme özelliği, paralel olarak istek göndererek servis çalışanı başlatma süresini azaltmanıza olanak tanır.

Jake Archibald
Jake Archibald

Tarayıcı desteği

  • Chrome: 59.
  • Edge: 18.
  • Firefox: 99.
  • Safari: 15.4.

Kaynak

Özet

Sorun

Getirme etkinliklerini işlemek için hizmet çalışanı kullanan bir siteye gittiğinizde tarayıcı, hizmet çalışanından yanıt ister. Bu işlem, hizmet çalışanını (çalışıyor değilse) başlatmayı ve getirme etkinliğini dağıtmayı içerir.

Önyükleme süresi cihaza ve koşullara bağlıdır. Bu süre genellikle 50 ms civarındadır. Mobil cihazlarda bu süre 250 ms'ye yakındır. Aşırı durumlarda (yavaş cihazlar, CPU'da sorun) bu süre 500 ms'nin üzerinde olabilir. Ancak hizmet çalışanı, etkinlikler arasında tarayıcı tarafından belirlenen bir süre boyunca etkin durumda kaldığından bu gecikmeyi yalnızca ara sıra görürsünüz (ör. kullanıcı yeni bir sekmeden veya başka bir siteden sitenize gittiğinde).

Ağı atlamanın avantajı, önyükleme gecikmesinden daha fazla olduğundan, önyükleme süresi, önbellekten yanıt veriyorsanız sorun teşkil etmez. 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 işçilerini atlayarak, hizmet işçilerini tahmini olarak başlatarak ve diğer optimizasyonlarla başlatma süresini azaltmaya devam ediyoruz. Ancak önyükleme süresi her zaman sıfırdan büyük olur.

Facebook, bu sorunun etkilerini bize bildirdi ve gezinme isteklerini paralel olarak gerçekleştirmenin bir yolunu istedi:

SW önyüklemesi
Gezinme isteği

Navigasyon ön yükleme özelliğinden yararlanın

Gezinme önyüklemesi, "Kullanıcı bir GET gezinme isteği yaptığında, hizmet çalışanı başlatılırken ağ isteğini başlat" demenize olanak tanıyan bir özelliktir.

Başlangıç gecikmesi devam eder ancak ağ isteğini engellemediği için kullanıcı içeriği daha erken alır.

Bu işlemin çalışırken gösterildiği bir videoyu aşağıda bulabilirsiniz. Bu videoda, hizmet çalışanına while döngüsü kullanılarak kasıtlı olarak 500 ms başlangıç gecikmesi uygulanmaktadır:

Demoyu buradan izleyebilirsiniz. Gezinme ön yüklemesinin avantajlarından yararlanmak için bu özelliği destekleyen bir tarayıcıya ihtiyacınız vardır.

Gezinme ön 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()'ü istediğiniz zaman çağırabilir veya navigationPreload.disable() ile devre dışı bırakabilirsiniz. Ancak fetch etkinliğinizin bu özelliği kullanması gerektiğinden, en iyi yöntem bu özelliği hizmet çalışanınızın activate etkinliğinde etkinleştirmek ve devre dışı bırakmaktır.

Önceden yüklenmiş yanıtı kullanma

Artık tarayıcı, gezinme için önceden getirme işlemi gerçekleştirecek olsa da yanıtı kullanma 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, aşağıdaki durumlarda yanıtla çözülen bir sözdür:

  • Gezinme önceden yükleme etkin.
  • İstek bir GET isteğidir.
  • İstek, bir gezinme isteğidir (tarayıcılar, iframe'ler dahil sayfaları yüklerken oluşturur).

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

Sayfanızın ağdan veri alması gerekiyorsa en hızlı yol, servis çalışanında istek gönderip önbellekten ve ağdan alınan parçaları içeren tek bir akışlı yanıt oluşturmaktır.

Bir makale göstermek 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 mergeResponses, her isteğin akışlarını birleştiren küçük bir işlevdir. Bu sayede, ağ içeriği aktarılırken önbelleğe alınmış başlığı gösterebiliriz.

Ağ isteği sayfa isteğiyle birlikte yapıldığı ve içerik önemli düzeyde müdahale olmadan aktarılabileceği için bu yöntem "uygulama kabuğu" modelinden daha hızlıdır.

Ancak includeURL isteği, hizmet çalışanının başlatma süresi nedeniyle gecikecektir. Bu sorunu düzeltmek için gezinme ön yüklemesini de kullanabiliriz ancak bu durumda sayfanın tamamını değil, bir dahil etmeyi ön yüklemek istiyoruz.

Bunu desteklemek için her ön yükleme isteğiyle birlikte bir başlık gönderilir:

Service-Worker-Navigation-Preload: true

Sunucu, gezinme ön yükleme istekleri için normal bir gezinme isteğinde göndereceğinden farklı içerik göndermek amacıyla bunu kullanabilir. Önbellekleri 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

Service-Worker-Navigation-Preload başlığının varsayılan değeri true'dur ancak istediğiniz değere ayarlayabilirsiniz:

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

Örneğin, sunucunun yalnızca daha yeni veriler döndürmesi için bu değeri yerel olarak önbelleğe aldığınız son yayının kimliğine ayarlayabilirsiniz.

Durumu alma

getState'ü kullanarak navigasyon ön yüklemesinin durumunu arayabilirsiniz:

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

Bu özellik üzerinde yaptıkları çalışmalar ve bu makale için verdikleri destek için Matt Falkenhagen ve Tsuyoshi Horo'ya çok teşekkür ederiz. Standartlaştırma çalışmalarına katılan herkese çok teşekkür ederiz.