© Carlos Rojas 2020
C. RojasBuilding Progressive Web Applications with Vue.js https://doi.org/10.1007/978-1-4842-5334-2_3

3. Service Workers

Carlos Rojas1 
(1)
Medellin, Colombia
 

There are common scenarios in which web applications are limited by not having an active window tab in the Web browser. However, if we take our app to the mobile world, this limitation does not exists, because even though a mobile app is closed, we can interact with the user by using, for example, push notifications. Service workers are the best option for adding special capabilities to web apps, which make them feel natural when they are installed from a mobile device (without users having to “enter” an app store or Google Play).

The Web is evolving. We can now create mechanisms that give users similar experiences in both apps and browsers, which led to the development of PWAs and service workers.

What Is a Service Worker?

A service worker can be thought of as a script (JS file) that executes in the background, regardless of whether a web site is open. There are some exciting things about service workers:
  • Service workers cannot access the document object model (DOM ) directly. Service workers communicate with the pages that they control through the PostMessage interface, which manipulates the DOM if needed.

  • With service workers, we can control how network requests are handled.

  • Service workers store information through IndexedDB, which I mentioned earlier. IndexedDB is a transactional database system found in web browsers. What do I need to use a service worker?

Service workers are great, but there are two things we must provide to integrate their benefits in our app:
  1. 1.

    Web browser support. You can see the actual support here: https://caniuse.com/#feat=serviceworkers

     
  2. 2.

    HTTPS in the server (except in localhost)

     

Understanding the Life Cycle

Service workers have a life cycle independent from web apps (Figure 3-1). Understanding the service worker’s life cycle allows us to offer an excellent experience to our users.
../images/483082_1_En_3_Chapter/483082_1_En_3_Fig1_HTML.png
Figure 3-1

The service worker life cycle

Let’s take a look at the states:
self.addEventListener('install', event => {
      // installing stage code
});
self.addEventListener('activate', event => {
      // activated stage code
});
self.addEventListener('fetch', event => {
      // fetch stage code
});
self.addEventListener('message', function(messageEvent) {
      // message stage code
})
  • Installing: This is the first event that occurs. It is given just one time per service worker. If the called promise in this event fails, the web browser does discard it and won’t let that the service worker take control of the client.

  • Activated: After the service worker controls the client and is ready to handle events, the activated state is initiated. The service worker is now active.

  • Terminated and Fetch/Message : After the service worker is in control of the pages, it can be in one of two states: terminated (to save memory) or fetch/message (to handle network requests).

Adding a Service Worker to an App

Now let’s add a service worker to our app:
  1. 1.

    Create a JS file, sw.js, and locate it in the web project root (normally at the same level of the main entry). In Vue projects, we need to move this file to the public/ folder, because when we make a build, our tool takes this folder as our web app root.

     
  2. 2.

    Register the service worker by adding the following:

     
if ('serviceWorker' in navigator) {
   window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js').then(function(registration) {
      // Successful
      console.log('SW registered.');
    }, function(err) {
      // Error
      console.log('Something bad happened :(', err);
    });
  });
}

Core Technologies

Service workers allow adding new functionality thanks to some core technologies. We look at them next.

Promises

Promises are an upgrade in the web platform to help in asynchronous scenarios. A promise is an object that represents a value in an asynchronous operation. A promise can take one of the following states:
  • pending: Initial state

  • fulfilled: The operation completed successfully

  • rejected: The operation failed

We can create a basic promise using this syntax:
const myPromise = new Promise((resolve, reject) => {
          // fulfilled
          resolve(someValue);
          // rejected
          reject("failure reason");
});
And you can use this promise using then() and catch():
myPromise.then(
       (response) => {
       console.log(“success!”, response);
}
).catch(
       (error) => {
console.log(“error!”, error);
}
);

We use promises in each application programming interface (API) that we integrate into our script, which is why we must have a good understanding of this concept.

Fetch API

The fetch API is a new web interface that seeks to simplify XMLHttpRequest and provides more features that help the resource search through the network.

Its use looks like this:
var myImage = document.querySelector('img');
fetch('someimage.jpg').then(function(response) {
  return response.blob();
}).then(function(myBlob) {
  var objectURL = URL.createObjectURL(myBlob);
  myImage.src = objectURL;
});

As you can see, it works like a promise and allows for easy response management. In addition, thanks to its request and response objects, we have good control over the requests we intercept from the network.

Cache

Saving files locally is one of the most important advantages offered by PWAs. Let's think about that for a moment.

Mobile phone users often find themselves with very bad connections, such as when they are on a subway or when they are in a basement. Users just want to have a good experience with our services, which is why we need the cache API and the cache storage API to store important files locally, and respond with the app shell on all occasions. In this way, users don’t have to wait for the PWA UI to load when they have an unstable connection.

Cache storage is the new storage layer; do not confuse it with the browser cache. To interact with cache storage we use the cache object:
caches.open(cacheName).then(function(cache) {
  // Cache code here
});
We find cache storage using Chrome DevTools in the Application tab and the Cache section (Figure 3-2).
../images/483082_1_En_3_Chapter/483082_1_En_3_Fig2_HTML.jpg
Figure 3-2

Google Chrome DevTools cache storage section

The previous code snippet shows the basic usage of cache storage API. The cache object allows us to carry out important operations. Here are a few:
  • cache.addAll(): Adds an element array to cache storage.

  • cache.delete(): Deletes an element from cache storage.

  • cache.match(): Gets the first element that matches the sent parameter. If it is unable to find one, it sends an undefined.

  • cache.put(): Allows for the addition of a new element to the cache.

Let’s implement cache storage in our service worker. To do this, we create a version and some assets we want to store. In our new file sw.js, we need a variable to identify the service worker version and activate updates in the future:
const currentCache = 'cache-v1.0';
Also, we need an array with the files we are going to store in the cache:
const files = [
    "favicon.ico",
    "icons/icon-128x128.png",
    "icons/icon-144x144.png",
    "icons/icon-152x152.png",
    "icons/icon-192x192.png",
    "icons/icon-384x384.png",
    "icons/icon-512x512.png",
    "icons/icon-72x72.png",
    "icons/icon-96x96.png",
    "index.html",
    "manifest.json",
  ];
Moreover, we have to listen for install and activate events in the service worker life cycle. We then add our files to cache storage when we detect install, and update our files when currentCache has another version from an activate event:
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(currentCache).then(cache => {
      return cache.addAll(files);
    })
  );
});
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => Promise.all(
      cacheNames.filter(cacheName => {
        return cacheName !== currentCache
      }).map(cacheName => caches.delete(cacheName))
    ))
  );
});
You can go there from the repo (https://github.com/carlosrojaso/appress-book-pwa) with
$git checkout v1.0.6

Debugging Our Service Worker

To see if everything works fine, we need to run
$npm run build
And then run
$serve -s dist
Next, open Chrome DevTools and locate the Application tab. Select Service Workers. You should see information about your service worker onscreen (Figure 3-3).
../images/483082_1_En_3_Chapter/483082_1_En_3_Fig3_HTML.jpg
Figure 3-3

Google Chrome DevTools service worker status activated and running

At this point, we are saving some files in cache storage (Figure 3-4), but we don’t have offline support because we are getting the files from the network. In Chapter 4, we learn how to get files from cache storage if we are offline.
../images/483082_1_En_3_Chapter/483082_1_En_3_Fig4_HTML.jpg
Figure 3-4

Google Chrome DevTools cache storage section

Summary

A service worker is a JS file that executes in the background, regardless of whether a web site is open. Thanks to service workers we can add support to a new world of Web APIS like CacheStorage and can use offline features to improve the user interaction with our Apps.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.198.49