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

Gezinme önceden yüklemesi, istekleri paralel olarak göndererek hizmet çalışanı başlatma süresinin üstesinden gelmenizi sağlar.

Jake Archibald
Jake Archibald

Tarayıcı Desteği

  • Chrome: 59..
  • Kenar: 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ın başlatılmasını (zaten çalışmıyorsa) ve getirme etkinliğinin gönderilmesini içerir.

Açılma süresi cihaza ve koşullara göre değişir. Genellikle 50 ms civarındadır. Mobilde bu değer 250 ms'ye yakın. Olağanüstü durumlarda (yavaş cihazlar, sorunlu CPU) 500 ms'nin üzerinde olabilir. Ancak Service Worker, etkinlikler arasında tarayıcının belirlediği bir süre boyunca uyanık kaldığından bu gecikmeyi yalnızca ara sıra (ör. kullanıcı sitenize yeni bir sekmeden veya başka bir siteden geldiğinde) yaşarsınız.

Ağı atlamanın avantajı, başlatma gecikmesinden daha fazla olduğundan, önbellekten yanıt veriyorsanız başlatma süresi sorun teşkil etmez. Ancak ağ üzerinden yanıt veriyorsanız...

SW önyüklemesi
Navigasyon 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, hizmet çalışanlarını tahmine dayalı şekilde başlatarak ve diğer optimizasyonları uygulayarak başlatma süresini azaltmaya devam ediyoruz. Ancak başlatma süresi her zaman sıfırdan büyük olur.

Facebook bu sorunun etkisini bize hatırlattı ve buna paralel olarak navigasyon isteklerini yerine getirmek için bir yöntem istedi:

SW önyüklemesi
Navigasyon isteği

Navigasyon kurtarmaya önceden yüklenir

Navigasyon önceden yüklemesi, "Kullanıcı bir GET gezinme isteği gönderdiğinde ağ isteğini Service Worker başlatılırken başlat" demenize olanak sağlayan bir özelliktir.

Başlatma gecikmesi devam eder ancak ağ isteğini engellemez, böylece kullanıcı içeriği daha erken alır.

Burada, hizmet çalışanına bir süre döngüsü kullanarak 500 ms başlatma gecikmesinin verildiği bir çalışma videosunu görebilirsiniz:

Demoyu burada bulabilirsiniz. Gezinmeyi önceden yüklemenin avantajlarından yararlanmak için bunu destekleyen bir tarayıcıya ihtiyacınız vardır.

Gezinmeyi önceden yüklemeyi etkinleştir

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

navigationPreload.enable() hizmetini istediğiniz zaman arayabilir veya navigationPreload.disable() ile devre dışı bırakabilirsiniz. Ancak fetch etkinliğinizin bunu kullanması gerektiğinden hizmet çalışanınızın activate etkinliğinde etkinliği etkinleştirip devre dışı bırakmanız en iyisidir.

Önceden yüklenmiş yanıtı kullanma

Artık tarayıcı gezinmeler için önceden yüklemeler gerçekleştirecektir, ancak yine de 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, aşağıdaki durumlarda yanıt ile sonuçlanan bir taahhüttür:

  • Navigasyonu önceden yükleme etkinleştirildi.
  • İstek bir GET isteği.
  • İstek bir gezinme isteğidir (tarayıcılar, iframe'ler de dahil olmak üzere sayfaları yüklerken oluşturur).

Aksi takdirde event.preloadResponse hâlâ orada kalır ancak undefined ile çözümlenir.

Sayfanız için ağdan veri almanız gerekiyorsa en hızlı yöntem, bunu hizmet çalışanında istemek ve önbellekten bölümler ile ağdaki bölümleri 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ıda mergeResponses her isteğin akışlarını birleştiren küçük bir işlevdir. Bu, ağ içeriği akışı sırasında önbelleğe alınmış üstbilgiyi görüntüleyebileceğimiz anlamına gelir.

Bu, "uygulama kabuğundan" daha hızlıdır modeli, sayfa isteğiyle birlikte yapılır ve içerik önemli saldırılar olmadan akış gerçekleştirebilir.

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

Bunu desteklemek için her önceden 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ği için olduğundan 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.

Şimdi önceden 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 üstbilgisinin değeri varsayılan olarak true olsa da bunu istediğiniz gibi ayarlayabilirsiniz:

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

Örneğin, bu değeri yerel olarak önbelleğe aldığınız son kaydın kimliğine ayarlayabilirsiniz. Böylece, sunucunun yalnızca yeni veriler döndürmesi sağlanır.

Durum öğreniliyor

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ından dolayı Matt Falkenhagen ve Tsuyoshi Horo'ya teşekkür ederiz. Bu makale, onlara yardımcı oldu. Standartlaştırma çabasında yer alan herkese çok teşekkür ederim.