Whenever you wanted to speed up the loading of your web app or to make your app run offline or to qualify as a PWA, you could use a service worker. A typical service worker will cache all the assets that are needed to render the page. This means that the service worker will be able to serve the page without having to load the assets from the server.
What is a Service Worker?
A service worker is a script which runs your browser, separated from a web page, and which allows the door to access functions which do not require a web page or a user interaction. Today, capabilities like as push and background sync already exist. Other things such as periodic sync or geofencing could be supported in future by service workers.
Let's begin!
In this blog, we will create a service worker to cache all the static files and serve them when it is requested. It also shows an offline page when the requested resource is not in the cache and there is no internet connection.
Core Events of a Service Worker:
There are three main events that a service worker can handle:
install
activate
fetch
Install (event):
The install event is called when the service worker is installed. When this event is called the service worker is registered and it starts cacheing the mentioned assets. A typical install event will look something like this
self.addEventListener("install", (event) => {
// do something with the event
}
Activate (event):
The activate event is called when the service worker is activated. The activate event will be called when the service worker is installed and the cache is ready to be used. It also manages the cache destruction when a new version of the service worker is installed or assets are changed.
Fetch (event):
The fetch event is called when a request is made to the service worker. The fetch event looks for a cached version of the requested resource and if it is found, it will return the cached version. Else it will return the response from the server. We will create the sw in such a way that it will cache all the static files and serve them when it is requested only when there is no internet connection.
Let's actually start coding!
Let's start by creating a folder called sw
in the root directory of the project. Add your index.html page in the folder and write some HTML.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
<h1>This is the home page.</h1>
</body>
</html>
And then we create a JS file named serviceworker.js
In the file, we tell service worker the static assets we need it to cache.
const CACHE = "content-v1"; // name of the current cache
const OFFLINE = "/offline.html"; // URL to offline HTML document
const AUTO_CACHE = [
// URLs of assets to immediately cache
OFFLINE,
"/",
"/serviceworker.js"
];
We can add the files needed to be cached in the AUTO_CACHE
and it will cache it in the activate
event. But to keep it simple, we add the things we need.
If you have noticed, we did not create a offline.html
file but asked the service worker to cache it. So, we will create the offline.html
. This page will be shown to the user if the service worker cannot serve the assets from cache while there is no internet connectivity.
offline.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>You're Offline!</title>
</head>
<body>
<h1>You're offline. Please check your internet connection!</h1>
</body>
</html>
Now that our files are ready, let handle the install event of the service worker. In the serviceworker.js
add this
self.addEventListener("install", (event) => {
event.waitUntil(
caches
.open(CACHE)
.then((cache) => cache.addAll(AUTO_CACHE))
.then(self.skipWaiting())
);
});
On the install event it iterates AUTO_CACHE and add cache for each entry. This is where the cacheing happens.
Now that we have handled the install event, we will code for the activate
event.
In the serviceworker.js
add the following.
self.addEventListener("activate", (event) => {
event.waitUntil(
caches
.keys()
.then((cacheNames) => {
return cacheNames.filter((cacheName) => CACHE !== cacheName);
})
.then((unusedCaches) => {
console.log("DESTROYING CACHE", unusedCaches.join(","));
return Promise.all(
unusedCaches.map((unusedCache) => {
return caches.delete(unusedCache);
})
);
})
.then(() => self.clients.claim())
);
});
Here, after the activate
event is fired, it will check for the cache version and will delete unused or inapplicable caches
fetch
event:
The whole working of the service worker is the fetch event. It is the event that is fired when a user requests a resource.
self.addEventListener("fetch", (event) => {
if (
!event.request.url.startsWith(self.location.origin) ||
event.request.method !== "GET"
) {
// External request, or POST, ignore
return void event.respondWith(fetch(event.request));
}
event.respondWith(
// Always try to download from server first
fetch(event.request)
.then((response) => {
// When a download is successful cache the result
caches.open(CACHE).then((cache) => {
cache.put(event.request, response);
});
// And of course display it
return response.clone();
})
.catch((_err) => {
// A failure probably means network access issues
// See if we have a cached version
return caches.match(event.request).then((cachedResponse) => {
if (cachedResponse) {
// We did have a cached version, display it
return cachedResponse;
}
// We did not have a cached version, display offline page
return caches.open(CACHE).then((cache) => {
const offlineRequest = new Request(OFFLINE);
return cache.match(offlineRequest);
});
});
})
);
});
The first thing it does is check if the request is from the server or from the client. If it is from the client, it will return the response. And it also makes sure that the request is a GET request. It will not handle POST requests or request to external urls. After it gets the response, fetch(event.request)
, the fetch
function will try to download the resource from the server. It always try to download from the server first regardless of what is cached. If there is no network connectivity, it will try to find the requested assets in the cache. If it finds it, it will return the cached version. If it does not find it, it will return the offline page.
Now, all we have to do is make the browser to find and install the service worker. Since, the home page is index.html
we will make the install call there.
In the index.html
add the following before the end of the </body>
.
if('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('serviceworker.js')
.then((reg) => console.log('Success: ', reg.scope))
.catch((err) => console.log('Failure: ', err));
})
}
First, the if
statement checks if the browser supports service workers. If it does, it will register the service worker.
The register
function takes a path to the service worker file. In this case, it is serviceworker.js
This function returns a promise. It will resolve when the service worker is registered and reject if it fails. reg.scope
returns the scope of the service worker. This is the url that the service worker will control.
That's all! If we try to access the page when the device is offline, it will still load the page! And if it requests a resource which is not cached, it will show the offline page!
The code for this example can be found at Github