Harder, Better, Faster, Stronger

Supercharging Your Site with Service Workers

Agenda

What is a Service Worker, Anyway

Some “Cool Stuff”

This is powerful!

A Good Analogy

Photo by MaggieLovesOrbit On Insta on Unsplash

A Bad Service Worker

How Do I Make a Service Worker

Registering a Service Worker

/* Feature check */
if (navigator.serviceWorker) {
navigator.serviceWorker.register('/sw.js')
.then(function(registration) {
/* It worked */
})
.catch(function(error) {
/* It didn't */
});
}

Promises, Promises

Life Cycle of a Service Worker

  1. Parsed
  2. Installing
  3. Installed / Waiting
  4. Activating
  5. Active
  6. Redundant

Parsed

Installing

Installed / Waiting

Activating

Active

Yay! You did it!

Your Service Worker is now in control of your site!

Redundant

What Can My Service Worker Do Now

Fetch Handling

/* In /sw.js at your site root */
self.addEventListener('fetch', function(event) {
var request = event.request;
event.respondWith( fetch(request) );
}); //end of listener

Fetch Handling Revised

If the user agent supports service workers, it also supports ES2015 syntax.

Safe to use arrow functions, const and let, etc.

self.addEventListener('fetch', event => {
const request = event.request
event.respondWith( fetch(request) )
})

Ok, How Do I Make That Useful

Cache First, Then Network

Here's a basic strategy:

  1. Check the cache
  2. If it's in the cache, return the cached copy
  3. If not, request the resource from the network

Fetch Handling Revised, Part 2

self.addEventListener('fetch', event => {
const request = event.request
event.respondWith(
caches.match(request) // Promise for a Response
.then(response => {
if (response) { // Annoying!
return response
}
else {
return fetch( request )
}
}) //End of then
) //End of respondWith
})

Pre-caching

Setting Up

Define your caches…

// In your sw.js file
const VER = 'v1';
const ASSET_CACHE = 'assetCache_' + VER;
// Add other caches later
const CACHE_LIST = [ ASSET_CACHE ]

const FILES_TO_CACHE = [ '/css/style.css', '/js/core.js' ] // etc.

Install Event

Tell the service worker that it's not done installing until everything is cached

self.addEventListener('install', event => {
event.waitUntil( /* A Promise is fulfilled */ )
})

Install Event, Part 2

self.addEventListener('install', event => {
event.waitUntil(
caches.open(ASSET_CACHE)
.then(myCache => {
return myCache.addAll( FILES_TO_CACHE )
}) //end of open Promise
.then(() => self.skipWaiting()) // This is cool!
) //end of waitUntil
})

Cache Invalidation and Service Worker Updating

Clean Up Your Old Caches

Activate Event

self.addEventListener('activate', event => {
event.waitUntil(
caches.keys() // Returns a Promise
.then(cacheNames => {
return Promise.all(
cacheNames.map(name => {
if ( !CACHE_LIST.includes(name)) {
return caches.delete(name) // A Promise
}
}) //end of map()
) //end of Promise.all
}) // End of keys Promise
.then(() => clients.claim()) // This is cool!
) //end of waitUntil
})

Caching Strategies Revisited

Fine tune fetch event handling to create a different strategy for images.

  1. If it's in the cache, return it
    • Also, grab & cache a fresh copy from the network
  2. If it's not in the cache, get it from the network
    • And cache it

Fetch Handling Revised, Part 4

// This is inside the fetch event listener
if (request.headers.get('Accept').includes('image')) {
fetchEvent.respondWith(
caches.match(request)
.then(response => {
if (response) { //It's in the cache!
fetchEvent.waitUntil(
fetch(request) // Grab a fresh copy
.then(fResponse => {
return
stash(IMG_CACHE, request, fResponse)
})
)
return response
}

Fetch Handling Revised, Part 5

Continued…

      else {
return fetch(request)
.then(fResp => {
fetchEvent.waitUntil(
stash(IMG_CACHE, request, fResp.clone())
)
return fResp
})
}
})
)
}

Abstraction

// This is a utility function for a common task
// Returns a Promise
function stash(cacheName, request, response) {
caches.open(cacheName)
.then(cache => {
return cache.put(request, response)
})
}

Page Content

Page Caching Strategy

  1. Fetch the page from the network
    • Put a copy in the cache
  2. If fetch fails, check the cache
  3. If the page doesn't exist in the cache ???

An Offline Experience

Fetch Handling Revised, Part 6

// This is inside the event listener
if (request.headers.get('Accept').includes('text/html')) {
event.respondWith(
fetch(request)
.then(resp => { // Got it, so cache it
event.waitUntil(stash(PAGE_CACHE, request, resp.clone()))
return resp
})
.catch(() => { // Network failed, fall back to cache
caches.match(request)
.then(resp => {
return resp || caches.match('/offline/')
})
})
)
}

Let's See It In Action

https://www.tccd.edu

Other Considerations

A Quick PSA About PWAs

Persistent Web Applications require:

Resources

Special Thanks to OmniUpdate

Get a free website accessibility scan with results from OmniUpdate! try.omniupdate.com/scan-5