I’m trying to make a Progressive Web App! Part 3.0: Service workers
The next, and maybe the most important, step is to register a service worker. This has been yet another pain, which is the reason why three weeks has passed since the latest installment in this series.
In short, the problems began with this error message in the console:
The script resource is behind a redirect, which is disallowed.
Service workers needs HTTPS in order to work, but even though I can browse to my local version of the app with https, the browsers doesn’t think that’s good enough. I guess that I have to install a certificate on the Laravel Homestead VM.
However, the browsers allows me to work with service workers when the URL is http://localhost/.
So in order to make some progress at all on this quest, I had to leave the Klickrubrik project.
A basic example
Using the Web Server for Chrome extension and a folder containing these files:
index.html
style.css
app.js
service-worker.js
…I could browse to http://127.0.0.1:8887
and finally start playing with service workers.
But what is it?
A service worker is a script that the browser can run before the site starts to load. It can interact with and manipulate the requests. As an example, it can replace all images requested by the site with a random dickpic. I don’t know why you would do something like that to your site, but that was the best example I could come up with for now 🙄
A service worker can also do things in the background. But most importantly right now, it can cache our assets and make our site load blazingly fast.
The code
The following code snippets comes from Googles introduction to service workers.
My examples are very simplified just to make them so easy to explain and understand as possible.
The entry point
Put this code together with your regular JavaScript.
if (‘serviceWorker’ in navigator) {
window.addEventListener(‘load’, function() {
navigator.serviceWorker.register(‘/service-worker.js’);
});
}
This snippet tells the browser to registrate our service worker script if the browser supports it. Note that the register
method returns a promise where you can handle the success or error callbacks.
The service worker
var CACHE_NAME = ‘klickrubrik-cache’;
var urlsToCache = [
‘/’,
‘/style.css’,
‘/app.js’
];self.addEventListener(‘install’, function(event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function(cache) {
return cache.addAll(urlsToCache);
})
);
});
This code will only run when the browser encounters it for the first time. It will create a cache for our web app called klickrubrik-cache.
The files we specifies will then be saved in that cache.
This means that when the browser requests style.css
and app.js
the next time, our service worker will intercept that request and serve the files from the cache instead. This is a huge win for performance!
But before we starts to celebrate, the code that makes it happen needs to be written first.
This is 100% stolen from the Google example:
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
// IMPORTANT: Clone the request. A request is a stream and
// can only be consumed once. Since we are consuming this
// once by cache and once by the browser for fetch, we need
// to clone the response.
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
The resources that is stored in the cache will now be avaliable, even if the user is offline or on a unstable connection. Great!
My next obstacle…
What if I push an update to app.js?
The user will still be served the old app.js
from the cache, so I need to bust the cache in some way.
Until next time, that’s what I’ll try to do. Wish me luck!