The Philly Code Camp ⚙️ Service Worker ⚙️ - Making the Schedule Work 📶 Offline! 📱 [A Service Worker Tutorial]

philly-cc-iphone-x-view
Philly Code Camp Service Worker

Earlier this week I launched the Philly Code Camp Progressive Web App. I explained how the application works from a high level but today I want to go over how the service worker is structured.

To be a progressive web app (PWA) a website must be secure, have a valid web manifest file and most importantly register a service worker. Using HTTPS and having a valid web manifest file a rather simple features that can be added in an hour to in most cases.

But a good service worker, that's a whole different thing.

Recently I have been launching a cadre of new simple progressive web apps just to show how simple it is to make a PWA. But none of these apps, so far, have really used a serviceWorker to its full capabilities.

The Philly Code Camp service worker starts showing how you can use a service worker to build a rich front end user experience with nothing but web technologies.

Check out the sibling article on how the PWA Add to Homescreen/Install experience works

 const version = "1.01", preCache = "PRECACHE-" + version, dynamicCache = "DYNAMIC-" + version, cacheList = [ "/", "img/phillydotnet.png", "js/app/app.js", "js/app/sessions.js", "js/libs/localforage.min.js", "js/libs/mustache.min.js", "js/libs/utils.js", "css/libs/bootstrap.min.css", "css/libs/fontawesome-all.css", "css/app/site.css", "css/webfonts/fa-solid-900.woff2", "api/philly-cc-schedule.json", "html/app-shell.html", "templates/session-list-item.html", "templates/session.html" ]; 

The next two variable declarations are the names of our caches, a pre-cache and a dynamic cache. Both have their core name but I also append the version value. This makes managing caches much simpler as you will see when I review the activate event.

The last constant is an array of the URLs that should be pre-cached when the service worker is first registered. For the Philly Code Camp site this is all of the asset dependencies like JavaScript, CSS and images.

I cacheed the home page and three template HTML files. I'll explain those files later in the article.

Service Worker Life Cycle Events

The next section are what I call the life cycle events, install and activate.

The install event triggers when a service worker is first registered. The service worker life cycle is a very complex topic into broad for this article. I have about two hours worth of content on this topic in my progressive web app course.

 self.addEventListener("install", event => { self.skipWaiting(); caches.open(preCache) .then(function (cache) { cache.addAll(cacheList); });

});

There are typically two things you do in a service worker install event handler, call skipWaiting if you want the service worker to immediately become active and precache application assets.

I do both in this installment handler. You should be careful when using skipWaiting because you might break your progressive web app experience if the service worker update is drastically different from the previous version.

To precache the assets and make them available immediately you simply need to open a reference to your precache cache and then pass the array defined in the constant section to the at all method. This method will take care of fetching those assets and placing those in the precache.

The second phase of a service worker registration is when it actually becomes active. A new service worker does not immediately become active because it may potentially break the user experience.

Therefore the activate event will fire when the service worker does become life.

 self.addEventListener("activate", event => { event.waitUntil( caches.keys().then(cacheNames => { cacheNames.forEach(value => { if (value.indexOf(version) < 0) { caches.delete(value); } }); console.log("service worker activated"); return; }) ); }); 

Again there are many things you can do in this event, but the most common task is to clean up old caches. This is where that version value comes into play.

In the service worker I loop through all of the named caches and check to see if they contain the current version number. If they don't I delete the previous caches.

This is a way to ensure that you don't have old's, stale responses cached. It also means that you don't run out of room to cache assets for your application.

Cache invalidation is a very complex topic. I wrote an article for Perth planet this past December going over some concepts here. I encourage you to read it if you want to know more about the topic.

The Fetch Event Handler

The service worker fetch event handler fires anytime there is a request for a network addressable resources, or a file from the server.

The event receives an event object which has a request property. You can use this request object to see if there is a cached response available.

 self.addEventListener("fetch", event => { event.respondWith( caches.match(event.request) .then(function (response) { if (response) { return response; } return fetch(event.request) .then(response => { if (response.ok) { //I have no clue why the chrome extensions requests are passed through the SW //but I don't like the error messages in the console ;) if (event.request.url.indexOf("chrome-extension") === -1) { let copy = response.clone(); //if it was not in the cache it must be added to the dynamic cache caches.open(dynamicCache) .then(cache => { cache.put(event.request, copy); }); } return response; } }).catch(err => { if (err.message === "Failed to fetch") { if (event.request.url.indexOf("session") > -1) { return renderSession(event); } } }); }) ); }); 

To do this call the caches object match method, passing the request object. This method will loop through all the named caches and see if there is a response cached for this specific request.

If there is a response it will return it.

You should check to see if the response object exist, and if so return that to the client.

For the Philly Code Camp progressive web app I chose to use a cache first, then network falling back response pattern. This means I'm looking at the cache first and if there is nothing in the cache then I make a request to the server. If were off-line I will check to see if I can render it locally or not.

If there is not a response a new fetch request is made. The request object is passed to the fetch method and the network request is initiated.

When this returns you should have a response object. If the request was good, which means it has a status of 200 which is also represented by the response.okay property, then you can process it.

The first thing I want to do is make sure that I cacheed this response so I don't need to hit the network the next time. To do this you need to clone the response object. You cannot use a response object more than once.

I then cache the cloned response object in the dynamic cache. To do this you open up a reference to the dynamic cache and then pass the request object and the response copy to the put method.

Now there is a cached response for that request.

As a side note I learn to also add a check to see if the request is part of a chrome extension request. If it is I don't cache the response. I'm not sure why chrome tries to do this, because extensions should be running in a separate process. But if you don't you will see funky exceptions happening.

Handling Offline

The real cool thing about this particular service worker is how I handle off-line mode.

For the application home page nothing really changes because everything is pre-cached. If you noticed one of the assets that I made sure was precache was the entire code camp schedule.

The schedule is a Jason object which makes it easy to manipulate locally. I demonstrated this by showing you how I do search and faceted filtering. But because the entire schedule is local I can also dynamically render individual session pages.

But to be able to do that I needed to also precache the app shell and the session detail template.

If the device is off-line in a request to the network is made an exception is thrown. This is why there is a catch event handler in the service worker. But I only have logic to dynamically render a session detail page. So I check the request URL to see if a request was made to a session. If so I then trigger the logic to render the session inside the service worker.

If you watch the video on how the application built script works I essentially extracted that logic and placed it in the service worker.

 function renderSession(event) { let slug = getSlug(event.request.url), appShell = "", sessionTemplate = ""; return getAppShell() .then(html => { appShell = html; }) .then(() => { return getSessionTemplate() .then(html => { sessionTemplate = html; }); }).then(() => { let sessionShell = appShell.replace("<%template%>", sessionTemplate); return getSessionBySlug(slug) .then((session) => { let sessionPage = Mustache.render(sessionShell, session); //make custom response let response = new Response(sessionPage, { headers: { 'content-type': 'text/html' } }), copy = response.clone(); caches.open(dynamicCache) .then(cache => { cache.put(event.request, copy); }); return response; }); }); }; 

The renderSession function does several of things: it loads the app shell and session templates and then request the session detail. Once it has retrieved all three of these files it then uses mustache to render the HTML that composes the entire page.

Once the pages rendered it is then cached and returned to the client. I use the same logic as if it came from the network, clone the response and cache that version.

The big difference here is I create a custom response object. This is one of the cool things about the fetch API, you can create custom request and response objects.

By caching the rendered response the next time this session is requested it will come from cache and not need to hit the network or the dynamically rendered. Even if the device goes back online the cached response from this process will be available.

Wrapping Things Up

All this quick explainer of the Philly Code Camp service worker has been very helpful and inspirational.

Service workers are extremely broad topic to cover. And even though I touched on a few intermediate and advanced concepts in the service worker it by no means demonstrates the full breadth of service workers.

My Progressive Web App from Beginner to Expert course has at least a dozen hours of video training on service worker development. A more will be posted in the coming weeks.

Handling the service worker life cycle and the proper caching strategies for your application are can be very complex and require a very educated developer to execute properly.

You should be able to use the Philly Code Camp service worker as a decent starting point for many applications. Just know that your application's personality may dictate a slightly different path.

If you want to learn how to do service worker development I highly encourage you to register for my progressive web application course. You can sign up right now for just $29 and save hundred and 71 off the regular list price.

The Philly Code Camp PWA Source Code is on GitHub.

Service workers are great super power that makes the web a great place to build rich front-end experiences. A well-done service worker can be the key separates your website from the competition. It can also make your business more profitable and productive because your applications execute cleaner and faster.

Share This Article With Your Friends!

We use cookies to give you the best experience possible. By continuing, we'll assume you're cool with our cookie policy.

Install Love2Dev for quick, easy access from your homescreen or start menu.

Googles Ads Facebook Pixel Bing Pixel LinkedIn Pixel