Mempercepat pekerja layanan dengan pramuat navigasi

Pramuat navigasi memungkinkan Anda mengatasi waktu startup pekerja layanan dengan membuat permintaan secara paralel.

Jake Archibald
Jake Archibald

Dukungan Browser

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

Sumber

Ringkasan

Permasalahan

Saat Anda membuka situs yang menggunakan pekerja layanan untuk menangani peristiwa pengambilan, browser akan meminta respons dari pekerja layanan. Hal ini melibatkan booting pekerja layanan (jika belum berjalan), dan pengiriman peristiwa pengambilan.

Waktu booting bergantung pada perangkat dan kondisi. Biasanya sekitar 50 md. Di perangkat seluler, durasinya seperti 250 md. Dalam kasus yang ekstrem (perangkat lambat, CPU dalam keadaan tertekan), error ini bisa melebihi 500 md. Namun, karena pekerja layanan tetap aktif selama waktu yang ditentukan browser antar-peristiwa, Anda hanya akan mengalami penundaan ini sesekali, seperti saat pengguna membuka situs Anda dari tab baru, atau situs lain.

Waktu booting bukan masalah jika Anda merespons dari cache, karena manfaat melewati jaringan lebih besar daripada penundaan booting. Namun, jika Anda merespons menggunakan jaringan...

Booting SW
Permintaan navigasi

Permintaan jaringan tertunda oleh proses booting pekerja layanan.

Kami terus mengurangi waktu booting dengan menggunakan cache kode di V8, dengan melewati pekerja layanan yang tidak memiliki peristiwa pengambilan, dengan meluncurkan pekerja layanan secara spekulatif, dan pengoptimalan lainnya. Namun, waktu booting akan selalu lebih besar dari nol.

Facebook memberitahukan dampak masalah ini kepada kami, dan meminta cara untuk melakukan permintaan navigasi secara paralel:

Booting SW
Permintaan navigasi

Pramuat navigasi dapat membantu

Pramuat navigasi adalah fitur yang memungkinkan Anda mengatakan, "Saat pengguna membuat permintaan navigasi GET, mulai permintaan jaringan saat pekerja layanan melakukan booting".

Penundaan startup masih ada, tetapi tidak memblokir permintaan jaringan, sehingga pengguna mendapatkan konten lebih cepat.

Berikut adalah video cara kerjanya, di mana pekerja layanan diberi penundaan startup 500 md yang disengaja menggunakan sementara-loop:

Berikut demonya. Untuk mendapatkan manfaat pramuat navigasi, Anda memerlukan browser yang mendukungnya.

Aktifkan pramuat navigasi

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

Anda dapat memanggil navigationPreload.enable() kapan saja, atau menonaktifkannya dengan navigationPreload.disable(). Namun, karena peristiwa fetch Anda perlu memanfaatkannya, sebaiknya aktifkan dan nonaktifkan di peristiwa activate pekerja layanan.

Menggunakan respons pramuat

Sekarang browser akan melakukan pramuat untuk navigasi, tetapi Anda masih harus menggunakan responsnya:

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 adalah promise yang di-resolve dengan respons, jika:

  • Pramuat navigasi diaktifkan.
  • Permintaan berupa permintaan GET.
  • Permintaan ini adalah permintaan navigasi (yang dibuat browser saat memuat halaman, termasuk iframe).

Jika tidak, event.preloadResponse masih ada, tetapi akan di-resolve dengan undefined.

Jika halaman Anda memerlukan data dari jaringan, cara tercepat adalah memintanya dalam pekerja layanan dan membuat satu respons yang di-streaming yang berisi bagian dari cache dan bagian dari jaringan.

Misalnya kita ingin menampilkan artikel:

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

Di atas, mergeResponses adalah fungsi kecil yang menggabungkan aliran data dari setiap permintaan. Artinya, kita dapat menampilkan header yang di-cache saat konten jaringan melakukan streaming.

Ini lebih cepat daripada "app shell" model saat permintaan jaringan dibuat beserta permintaan halaman, dan konten dapat di-streaming tanpa peretasan besar.

Namun, permintaan untuk includeURL akan tertunda oleh waktu startup pekerja layanan. Kita dapat menggunakan pramuat navigasi untuk memperbaikinya juga, tetapi dalam hal ini kita tidak ingin melakukan pramuat halaman penuh, kita ingin melakukan pramuat menyertakan.

Untuk mendukung hal ini, header dikirim dengan setiap permintaan pramuat:

Service-Worker-Navigation-Preload: true

Server dapat menggunakannya untuk mengirim konten yang berbeda untuk permintaan pramuat navigasi daripada untuk permintaan navigasi biasa. Jangan lupa untuk menambahkan header Vary: Service-Worker-Navigation-Preload, sehingga cache mengetahui bahwa respons Anda berbeda.

Sekarang kita dapat menggunakan permintaan pramuat:

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

Mengubah header

Secara default, nilai header Service-Worker-Navigation-Preload adalah true, tetapi Anda dapat menyetelnya ke apa pun yang Anda inginkan:

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

Misalnya, Anda dapat menetapkannya ke ID postingan terakhir yang telah di-cache secara lokal, sehingga server hanya menampilkan data yang lebih baru.

Mendapatkan status

Anda dapat mencari status pramuat navigasi menggunakan getState:

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

Terima kasih banyak kepada Matt Falkenhagen dan Tsuyoshi Horo atas karya mereka dalam fitur ini, dan bantuan dalam artikel ini. Dan terima kasih banyak kepada semua yang terlibat dalam upaya standardisasi