A Simple Guide to Taking a Web Page Offline, using Service Workers

A service worker is a script that runs in the background when your application/website is running. It acts as an intermediary between the browser and your app.

It has the ability to intercept network requests, modify them and redirect them; it is quite powerful. The service worker’s main function is to serve a cached/stored version of your application when the network is down. This makes them suitable for making your website work offline.

A very simplified explanation as to how service workers work, is this,

  1. When your site loads in the browser, the service worker finds all the files you specifically tell it to store, downloads them and then stores them in a space in the browser known as the cache. You can store anything from images, videos, html files, icons etc. These files should be files needed by your website or web page to function. 
    But there’s a catch, if any one of the files you specified fails to download, the installation process fails, then your service worker won’t be activated. On the other hand, if all the files are successfully downloaded, the service worker can then be activated. This phase is called the installation phase.
  2. Then, when a visitor visits your web app for the first time, the service-worker from your site goes through the installation phase on their computer. On subsequent visits to your website, if same visitor loses internet connection, the service worker picks up on this and reaches into the browser storage for the files previously downloaded in the installation phase and displays your website. This is called the fetching phase

This API is quite exciting and powerful. If you feel like you could use this and you are not a developer, it is best to consult one and discuss the merits/demerits and how it could work for your web application. On the other hand, if you are a developer, I have some code samples we can go through as to how to go about implementing the offline experience.

How to Implement a Service Worker

The first step to implementing the offline experience is to register the service worker. But before we can do this, in order for the service worker to work locally i.e not on a production server, we need to create a local development server for the application. Which means you can’t just visit “index.html” in your browser and expect the service worker to work

We could use the python framework flask or the Javascript framework, node.js to create a quick server. I choose to use flask. To follow, you need to download python for your operating system, and then install flask by doing

pip install flask

You also need to clone the code repository I’ll be demonstrating with, by doing

git clone https://github.com/AdeyinkaAdegbenro/Javascript_Calculator.git

Currently, our directory structure looks somewhat like this;

JavaScript_Calculator

→index.html

→readme.MD

→style.css

→script.js

We are going to change this.

  • Create a file named server.py and type in the code below. Below, we create two routes. The first /index, which makes sure that when you navigate to /index in the browser, it renders index.html, the second /sw.js, makes sure that when your code calls /sw.js, it serves the content of sw.js from the static folder
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/index')
def home():
return render_template('index.html')
@app.route('/sw.js', methods=['GET'])
def sw():
return app.send_static_file('sw.js')
app.run(debug=True)
  • Create a folder named templates. Move index.html from the root folder to the templates folder. We are moving index.html because flask serves its html files from the templates folder by default.
  • Create a folder named static. Move both script.js and style.css to the static folder. We are also moving these two files because flask serves its static files from the static folder.
  • Since we have moved the positions of our static files, we have to update their paths in the script tags in index.html. Open templates/index.html, and on lines 3 and 12 change style.css to /static/style.css and script.js to /static/script.js respectively. At the end of the day we have something like this:
our new templates/index.html

Our directory structure should now look like below;

JavaScript_Calculator

→server.py

→readme.MD

templates

→→index.html

static

→ →style.css

→ →script.js

N.B → → represents files in a folder while bold letter represent folders

Now run python server.py or python3 server.py in your console as the case maybe, then visit http://localhost:5000/index in your browser.

We are now ready for the next step.

Registering the Service Worker

Create a file named sw.js and then place it in the static directory. Then go to static/script.js and enter the following at the bottom

if ('serviceWorker' in navigator) {   
// we are checking here to see if the browser supports the service worker api
window.addEventListener('load', function() {
navigator.serviceWorker.register('../sw.js').then(function(registration) {
// Registration was successful
console.log('Service Worker registration was successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}

The line navigator.serviceWorker.register('../sw.js').then(function(registration...
 is the main code that does the registration of our service worker script.

The register method takes the url of our sw.js script. Remember, in our Flask file server.py, we created a route that tells Flask to find the file for url /sw.js in /static/sw.js; we have to tell ourscript.js file how to find our sw.js file. We do this by prefixing the url /sw.js with a .., which is basically telling the browser to change the directory a step back (from the static folder to the root folder) and call sw.js. Now visit http://localhost:5000 in your browser and open your console. You should see this:

Service worker successfully registered

Also, chrome has made it very easy to debug service workers by providing the application tab in the console. Visit the console by doing ctrl shift i. If you visit the application tab, you see this:

The application tab

This comes in handy while trying to monitor/debug our service worker script. If you click the little blue sw.js in your browser, it is going to take you to the empty source of our sw.js file. It is empty because we have not added any code to it.

We are going to type the following in our sw.js file:

var CACHE_NAME = 'offline-calculator';
var urlsToCache = [
'/',
'/index',
'/static/style.css',
'/static/script.js'
];
self.addEventListener('install', function(event) {
// install files needed offline
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});

The code above handles the installation phase of the service worker script. What we did is this, we are asking the script to install the urls needed for the calculator to work. They include ‘/’, ‘/static/style.css’, ‘/static/script.js’. Urls ‘/’ and ‘/index’ are the routes that serve our index.html file. You can confirm this by checking server.py. So ultimately, all we are doing is saving files returned by each route/path in the browser cache. offline-calculator is the name of the cache where we are asking the script to store our files in the browser. So think of it as storing files the browser would normally request from the server in the browser. This way, when there’s a network issue, the browser can serve your web app’s files from its own storage without the internet. Sort of like downloading movies to your sd card so you don’t have to stream them every time.

Make sure your server is running usingpython server.py. Then navigate to http://localhost:5000/. Also, make sure your console is open. In your console, visit the Application tab. You should see the offline-calculator cache in the cache storage section:

The Application Tab

When you click the offline-calculator line, you should see the files that have been cached:

Our site cache

Now that we have confirmed that our files have been cached in the browser, it’s time to go offline. Oh wait, we can’t. Reason — if we go offline now, the script does not know how to retrieve our files to show the user. You have to teach it how. That’s where the fetch handler comes in.

Let’s implement the fetch handler, shall we?

self.addEventListener('fetch', function(event) {
// every request from our site, passes through the fetch handler
// I have proof
console.log('I am a request with url:', event.request.clone().url)
event.respondWith(
// check all the caches in the browser and find
// out whether our request is in any of them
caches.match(event.request)
.then(function(response) {
if (response) {
// if we are here, that means there's a match
//return the response stored in browser
return response;
}
// no match in cache, use the network instead
return fetch(event.request);
}
)
);
});

What we have done here is set a fetch event listener. The listener well, listens for any form of web request and then checks if the request is stored in our browser cache. If it is, the response is gotten from the file stored in the cache. If however, the request was not found, then we just dofetch(event.request). Which is equivalent to how the browser will get the request normally.

For a better understanding of how the fetch and cache API works, study these resources, cache and fetch.

It is time to reload our page. Reload it once to update the script. And then, shut down the flask server, and also put your computer in airplane mode. Now after reloading the page again, I get this

We have our page loading offline alright. Except our calculator does not seem to be working. From my console, I can see the fetch event for blablabla.com resulted in a network error response. It seems I forgot to add jquery to the list of urls to save in the browser. Since JQuery is used extensively in our calculator code, it won’t work without it. I’m going to go ahead and add the Jquery cdn to the list urlstoCache in our code and save the file.

var urlsToCache = [
'/',
'/static/style.css',
'/static/script.js',
'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js'
];

Now we go back online and reload our page (don’t forget to start your server up again), and then go offline again (shut your server down too). After reloading my page, I get this

Tada!

Since we added the jquery url to our list of urls to cache, our calculator can now work offline. Also, the cache has been updated with the jquery url

Jquery in Cache storage

We now have a working calculator offline. You can find the before and after code on github.

While this tutorial might look easy, this is not all there is to know about service workers. There’s more, but I wanted to introduce you to the most fun part first, so you won’t get bored. Here are other topics and resources (in no particular order) you should look into, if you want to dive deeper into service workers and fully understand how they work

  1. https://developers.google.com/web/fundamentals/primers/service-workers/
  2. serviceworke.rs — The service worker cookbook
  3. The W3C Specifications https://www.w3.org/TR/service-workers-1/
  4. https://developer.mozilla.org/en-US/docs/Web/API/Cache
  5. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
  6. Promises — Used extensively in service workers https://developers.google.com/web/fundamentals/primers/promises
  7. Async Functions — Also used extensively in service workers https://developers.google.com/web/fundamentals/primers/async-functions
  8. Google is also your best friend https://www.google.com.ng/search?q=service+workers

Ciao.