Staff Developer Advocate at 

Patricio Vargas (Pato)

  • Google Developer Expert on Web Technologies
  • Microsoft MVP
  • Auth0 Ambassador
  • AWS Community Builder
  • Postman Supernova
  • Twilio Champion
  • Media Developer Expert at Cloudinary
  • Google Women Techmaker

@devpato

What are you learning today?

@devpato

What is a Service Worker?

@devpato

What is a Service Worker?

A Service worker is a proxy script between your web app and the outside. Service Workers execute separately from the main browser thread.

Web App

Service Worker

Cache

Network

@devpato

Web Applications

@devpato

Can I use?

What are the superpowers of a
Service Worker?

@devpato

What are the superpowers of a Service Worker?

  • It can cache network requests.
  • It can handle how network requests are done on your website.
  • It can make use of the Background Sync API.
  • It can cache things from your website.
  • It can receive push notifications when the app is not active.
  • It can stay inactive when it's not in use.
  • It can be used to make your app work offline.
  • It can display an offline page in case of connection failure.
  • It can capture offline metrics.
  • It can perform load balancing in the client side.

@devpato

Background Sync
API

@devpato

Student Grade Action
Pato A           💾 ❌ ✏️
Chris D F           💾 ❌ ✏️

Background Sync API

Service Worker & Background Sync API

Offline Cache

Backend

Why I fell in love with the Service Workers?

@devpato

Thanks to Service Workers

  • The company is now in over 120 countries.
  • 1/5 Americans have products from our client.
  • Reduced the number of server
    calls by 32%
  • Increased the number of users.
  • Increased user experience.
  • By 2019 company was making over
    $8.8 billion in sales.
  • PWA first time load time ~3 secs.

@devpato

#BasedOnATrueStory

@devpato

Working with Service Workers

@devpato

Service Worker Lifecycles

index.js

sw.js

@devpato

Registering the Service Worker

if ("serviceWorker" in navigator) {
    window.addEventListener("load", ()=>{
        navigator.serviceWorker.register("sw.js").then(swRegistered => {
            console.log("[ServiceWorker**] - Registered");
        });
    });
}

index.js

@devpato

Installing the Service Worker

const cacheName = "my-pwa-shell-v1.0";
const filesToCache = [
    "index.html",
    "./js/index.js",
    "./styles/styles.css",
    "manifest.json",
    "./assets/icons/icon.png",
    "./assets/nike1.jpeg",
];


self.addEventListener("install", e => {
    console.log("[ServiceWorker] - Install");
    e.waitUntil((async () => {
		const cache = await caches.open(cacheName);
		console.log("[ServiceWorker] - Caching app shell");
		await cache.addAll(filesToCache);
    })());
});

sw.js

@devpato

Activating Service Worker

self.addEventListener("activate", e => {
    e.waitUntil((async () => {
        const cacheList = await caches.keys();
        await Promise.all(
            cacheList.map(currentCache => {
            	// currentCache = "cacheVersion-1.0" , newCache = "cacheVersion-2.0"
                if (currentCache !== newCache) { 
                    return caches.delete(currentCache);
                }
            })
        );
    })());
});

sw.js

DEMO

@devpato

Workbox

@devpato

What is Workbox?

Workbox is a library that incorporates a set of best practices and eliminates the boilerplate that every developer writes when working with service workers.” - Google Devs.

  • Precaching
  • Runtime caching
  • Caching Strategies
  • Request Routing
  • Background Sync
  • Helpful Debugging

@devpato

PRE-CACHE

VS

RUNTIME CACHE

@devpato

Pre-Cache

Your resources are put in the cache before they are requested.  This is an extreme approach.

E.g Your web app start url, offline fallback, and, key js and files

 

Pre-caching happens at the service worker install event at the cache first strategy

 

@devpato

Runtime Cache

Runtime cache adds resources to the cache when they are requested.  Runtime caching works with different caching strategies and the resources are cached independently.

 

E.g a new cache named "images".

 

 

 

@devpato

Fetching
(Intercepting Network Call)

self.addEventListener('fetch', e => {
    e.respondWith((async () => {
        const resource = await caches.match(e.request);
        
        return resource || fetch(e.request);
    })());
});

sw.js

@devpato

Cache, falling back to the Network

2

fallback

Web App

Network

Service Worker

1

The Asset

3

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      return response || fetch(event.request);
    }),
  );
});
import {registerRoute} from 'workbox-routing';
import {CacheFirst} from 'workbox-strategies';

registerRoute(({request}) => request.destination === 'style', new CacheFirst());

With Workbox.js

Without Workbox.js

Cache, falling back to the network

@devpato

Network, falling back to the cache

3

fallback

2

Web App

Cache

Network

Service Worker

1

The Asset

self.addEventListener('fetch', function (event) {
  event.respondWith(
    fetch(event.request).catch(function () {
      return caches.match(event.request);
    }),
  );
});
import {registerRoute} from 'workbox-routing';
import {NetworkFirst} from 'workbox-strategies';

registerRoute(
  ({url}) => url.pathname.startsWith('/social-timeline/'),
  new NetworkFirst()
);

With Workbox.js

Without Workbox.js

Network, falling back to the cache

@devpato

Network Only

2

Web App

Network

Service Worker

1

The Asset

self.addEventListener('fetch', function (event) {
  event.respondWith(fetch(event.request));
  // or simply don't call event.respondWith, which
  // will result in default browser behavior
});
import {registerRoute} from 'workbox-routing';
import {NetworkOnly} from 'workbox-strategies';

registerRoute(({url}) => url.pathname.startsWith('/admin/'), new NetworkOnly());

With Workbox.js

Without Workbox.js

Network Only

@devpato

Cache Only

2

Web App

Cache

Service Worker

1

The Asset

self.addEventListener('fetch', function (event) {
  // If a match isn't found in the cache, the response
  // will look like a connection error
  event.respondWith(caches.match(event.request));
});
import {registerRoute} from 'workbox-routing';
import {CacheOnly} from 'workbox-strategies';

registerRoute(({url}) => url.pathname.startsWith('/app/v2/'), new CacheOnly());

With Workbox.js

Without Workbox.js

Cache Only

@devpato

DEMO

@devpato

Service Workers Best Practices

@devpato

Service Workers Best Practices

  • Make sure to set the right scope for your service worker

  • Delay registering a service worker until after a web app's load event fire

  • Don't worry about a service worker caching itself

  • Avoid caching bad responses by properly handling them in fetch

  • Evaluate caching strategy examples and never copy-paste them directly

  • You might not need to build a service worker from scratch

@devpato

Service Workers Best Practices

  • Make sure you know all the network request
  • Don't Try to Recreate All Your Business Logic in the Service Worker

  • Always Define an offline response

  • Be careful when using hot-reload

@devpato

Resources

@devpato

Resources

@devpato

Service Workers

By Patricio Vargas

Service Workers

  • 119