Service Worker

Prathik Shetty

THE WEB IS DEAD. LONG LIVE THE INTERNET​

- Wired

HTML5 IS DEAD. LONG LIVE HTML5!

- CNET

Web Server

Web Server

Service Worker

Web Server

Service Worker

Cache

Service Worker

Push

A proxy that sits between web applications, and the browser and network

Works only on

Use Cases

Offline Mode

Push Notification

Background Sync

& More

  • Stub servers for testing
  • Manipulate responses
  • WebApp Update
  • Support for a new image format​
  • Easy Failover support
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js', {
      scope: '/app/'
    })
    .then(function(registration) {
      console.log('SW registered');
    }); 
    // .catch a registration error
} else {
    console.log('SW registered ');
}

index.html

self.addEventListener('install', function(event) {
  // On installation 
});

self.addEventListener('activate', function(event) {
  // on activation
});

sw.js

Installation

Activate

Error

Idle

Fetch/Push

Terminated

No SW

SW Lifecycle

Event Listeners

  • Install
  • Activate
  • Message

Functional Events

  • Fetch
  • Sync
  • Push
  • & more
self.addEventListener('fetch', function(event){
  event.respondWith(
    caches.match(event.request)
  );
});

sw.js

self.addEventListener('sync', function(event) {
  if (event.tag === 'my-sync-tag') {
    event.waitUntil(doSomething());
  }
});

sw.js

self.addEventListener('push', function(event) {
  event.waitUntil(
    self.registration.showNotification('Notification Yay!!!');
  );
});

sw.js

Examples

var cacheName = 'cache-v1';
var filesToCache = ['/main.js', '/index.html', '/offline.html', ...];

self.addEventListener('install', event => {
   event.waitUntil(
      caches
        .open(cacheName)
        .then(cache => {
          return cache.addAll(filesToCache);
        })
        .then(() => {
          return self.skipWaiting();
        })
   );  
});

sw.js

Pre-Cache on Install

var cacheName = 'cache-v1';
var filesToCache = ['/main.js', '/index.html', '/offline.html', ...];
var nonImportantFilesToCache = [...];

self.addEventListener('install', event => {
   event.waitUntil(
      caches
        .open(cacheName)
        .then(cache => {
          cache.addAll(nonImportantFilesToCache);
          return cache.addAll(filesToCache);
        })
        .then(() => {
          return self.skipWaiting();
        })
   );  
});

sw.js

Pre-Cache on Install v2

self.addEventListener('fetch', event => {
  event.respondWith( 
    caches
      .match(event.request)
      .then(response => {
         return response || fetch(event.request);
      });
  );
});

sw.js

Serve from cache on fetch 

self.addEventListener('activate',e => {
    e.waitUntil(
      caches
        .keys()
        .then(keyList => {
           return Promise.all(keyList.map(key => {
             if (oldCacheNames.includes(key)) {
               return caches.delete(key);
             }
           }));
        });
    );
    return self.clients.claim();
});

sw.js

Clear old cache on activate

self.addEventListener('fetch',event => {
  event.respondWith(
    return fetch(event.request)
      .then(response => {
        cache.put(event.request,response.clone());
        return response;
      });
  );
});

sw.js

Save to cache on fetch 

self.addEventListener('fetch',event => {
  event.respondWith(
    return fetch(event.request)
      .catch(function() {
        return caches.match(event.request);
      });
  );
});

sw.js

Fallback to cache


const maxItems = 35;

caches
  .open(cacheName)
  .then(function(cache) {
    cache
      .keys()
      .then(function(keys) {
        if (keys.length < maxItems) {
          cache.put(request, response);
        } else {
          cache
            .delete(keys[0])
            .then(function() {
              cache.put(request, response);
            });
        }
      });
  });

sw.js

Limiting Cache

self.addEventListener('notificationclose', function(event) {
  var notification = event.notification;
  var primaryKey = notification.data.primaryKey;
  console.log('Closed notification: ' + primaryKey);
});

sw.js

Closes Push Notification

self.addEventListener('notificationclick', function(event) {
  var notification = event.notification;  
  var primaryKey = notification.data.primaryKey;

  var action = event.action;
  if (action === 'action1') {
    clients.openWindow('https://decanter.com/action1.html?pk='+ primaryKey);
  } else {
    clients.openWindow('https://decanter.com/view.html?pk='+ primaryKey);
  }
});

sw.js

Clicks Push Notification

self.addEventListener('fetch', event => {
  event.respondWith( 
    caches
      .match(event.request)
      .then(response => {
        return response || fetch(event.request);
      })
      .catch(function() {
        if (event.request.headers.get('Accept').indexOf('text/html') !== -1) {
          return caches.match('/offline.html');
        }
      });
  );
});

sw.js

Offline

Support

Basic Support

Debugging

Freshness or Performance

Network First or Cache First

Service Workers replaces AppCache​

but it's a sledgehammer to crack a nut

Impact

73/53/100/92​

First Render

Repeat View

How to use in Prod

CTRL + C

CTRL + V

<amp-install-serviceworker>

Works great with AMP​

References

Service Worker

By Prathik S

Service Worker

  • 1,933