w3c / ServiceWorker

Service Workers
https://w3c.github.io/ServiceWorker/
Other
3.63k stars 314 forks source link

Service Worker handling of first page load subresources #1282

Open yoavweiss opened 6 years ago

yoavweiss commented 6 years ago

This was throughly discussed before (at https://github.com/w3c/ServiceWorker/issues/73 and https://github.com/w3c/ServiceWorker/issues/938 at the very least), but I believe there's merit to raising the issue again.

Service Workers have many use-cases nowadays. Offline browsing is one of them, but others would significantly benefit from being able to manage requests and responses of subresources in the very first page load on which the Service Worker is installed.

Use cases

Lazy loading of resources

Web content today contains many resources which may or may not be needed to render the very first view. This includes images and javascript resources. There are many JS based solutions to lazy load certain resources, but it’s hard to make them bullet proof in the face of HTML based resources as well as JS based ones both from first and third party code.

Service Workers provide an elegant way to defer the loading of resources until a later point in time.

Third party SPOF prevention

Many developers integrate third party services to their sites. That means that when those third parties have a Bad Day™, the sites in question run a risk of having their first render, DCL or load event delayed until the OS gives up on said third party and initiates a reset of the connection.

Service Workers provide a handy technique for dealing with that, and terminate those third party requests if they are taking too long.

Decompression

Service Workers also potentially enable the use of new and exciting compression schemes by allowing developers to perform proprietary decompression of those schemes in Service Workers.

While that’s possible, the first load of the page (which arguably requires the most help on the performance front), won’t be able to benefit from such optimizations.

Potential solution

The above use-cases all require that the SW will be installed before any subresource requests are being sent out for the document, but not necessarily before the document itself - the document request and response do not have to be processed by the Service Worker.

SW code push + early hints installation

The solution outlined here involves two different mechanisms: Server Push in order to deliver the SW resource to the browser and Early Hints + <link rel=serviceworker> in order to trigger the renderer to install that Service Worker ASAP.

The combination of these two technologies would enable servers to send down both the instructions to install the ServiceWorker as well as its resource to the browser, while the HTML contents are being calculated on the server. Once the browser received the 103 response with the SW installation instructions, it will start fetching the service worker, and will hopefully find its contents already in its network stack. Then it can start speculatively installing the service worker (even though the document may not be committed just yet), and hopefully finish the installation before the HTML processing starts and subresources are discovered.

In case where the race is lost and HTML is processed before the SW has finished installation, the requests will go out without triggering the SW's fetch event. Developers can use an fast-installing, "bootstrapping" SW in these cases to minimize the race.

Thoughts?

Enalmada commented 5 years ago

Since google fonts don't support font-display I created a fetch handler that modifies the response to add it. Unfortunately first time visitors don't get the benefit of this. It would be nice to have a way to install service worker before any other requests happen to allow for handling first page load resources.

anthumchris commented 4 years ago

Potential current workaround: Install the SW alone first, then reload the page. The SW then serves content for a SW-enabled page to intercept every request.

See usage at fetch-progress.anthum.com (open in Incognito mode to force SW install)

index.html

<p>Installing Service Worker, please wait...</p>

<script>
  navigator.serviceWorker.register('sw.js')
  .then(reg => {
    if (reg.installing) {
      const sw = reg.installing || reg.waiting;
      sw.onstatechange = function() {
        if (sw.state === 'installed') {
          // SW installed.  Reload for SW intercept serving SW-enabled page.
          window.location.reload();
        }
      };
    } else if (reg.active) {
      // something's not right or SW is bypassed.  previously-installed SW should have redirected this request to different page
      handleError(new Error('Service Worker is installed and not redirecting.'))
    }
  })
  .catch(handleError)

  function handleError(error) {}
</script>

sw.js

self.addEventListener('fetch', event => {
  const url = event.request.url;
  const scope = self.registration.scope;

  // detect "index.html" or URI ending in "/"
  if (url === scope || url === scope+'index.html') {
    // serve index.html with service-worker-enabled page
    const newUrl = scope+'index-sw-enabled.html';
    event.respondWith(fetch(newUrl))
  } else {
    // process other files here
  }
});

index-sw-enabled.html

This page loads in browser as URI index.html after SW installs. Put intended app/page content here.