• Structure of a PWA
  • JavaScript Workers

    • Definition
    • Using
  • Service Workers
  • Definition
  • Using

In the last episode, we learned what a PWA is. Let's code some today. Today, we will see an important part of a PWA: Service workers.

Structure

A PWA has basically two layers:

Application Shell: All the CSS/HTML and images used in your application. We can use some cache on this part to make it faster. This part of your app is static, so it can be easily cached.

Content: What will be rendered inside your app. This part is dynamic, so we need to take care of what we will cache here.

JavaScript Workers

The PWA has service workers. As we saw in the last episode, the service workers are the main point of having nice features like push notifications, offline experience, and the ability to intercept network requests managing cache requests. In the future other features will be added.

Before the creation of Service Workers to manage offline experiences, we had App Cache. Service Workers came into place to avoid a number of issues with Application Cache.

A Service Worker is a JavaScript Worker, this is a way JavaScript can use background threads. Each worker can perform tasks without interfering with the user interface. We can communicate with these workers by passing messages. There a few ways we can do something similar, using XMLHttpRequest, setTimeout() or setInterval(), those are non-blocking operation, they are not concurrent operations.

Before playing with Service Workers, let's see what JavaScript Workers are. This will help us understand what Service Workers are.

Let's try to create our workers. To start our workers, we need a separate file for our worker. Then, we just need to instantiate the Worker.

var worker = new Worker("worker.js");

We will create a HTML page and create two workers, they will calculate the Fibonacci of a number.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Using Workers</title>
</head>
<body>

  <header class="header">
    <h1>Using workers</h1>
  </header>

  <main class="main">
  </main>

  <script src="app.js" async></script>
</body>

Let's see our app.js, here we will create two workers, get the message from the workers and print the result in a console log.

To receive the result from the worker, we add a listener "message" on it, and the callback function renders the result.

worker.addEventListener("message", function(){})

To send messages to our worker:

worker.postMessage(VALUE);

So, from outside our worker and inside, if we want to send message, we will be using the postMessage function. The value will not be shared, they will be copied.

Our two workers:

var FIB_VALUE = 37;

var worker = new Worker("worker.js");
var worker2 = new Worker("worker.js");

worker.addEventListener("message", function(e) {
  console.log("Result Worker 1: ", e.data);
});

worker2.addEventListener("message", function(e) {
  console.log("Result Worker 2: ", e.data);
});

worker.postMessage(FIB_VALUE);
worker2.postMessage(FIB_VALUE);

In our worker file, we will also listen for a message and do our work. Inside of that, we defined the fib function. Once we get the result, we use postMessage to pass the result to our worker.

self.addEventListener("message", function(e) {
  var value = e.data;

  function fib(value) {
    if (value <= 1) {
      return value;
    } else {
      return fib(value - 1) + fib(value - 2);
    }
  }

  var result = fib(value);
  self.postMessage(result);
});

To run this, you will need to install the webserver from Chrome. This is necessary because Chrome doesn't allow us to run workers from files without being on localhost. So, we will run over a HTTP server.

We already have it installed. We need to start it, and point to the folder our files are located.

Starting our page, we can see the result on the console. Awesome!

Now, if you need some intense calculation or some heavy task, you know what to do in JavaScript. You can find this code in our repository on GitHub.

Service Workers

We know what JavaScript workers are, how to start them, and how to communicate with them using messages. However, what are Service Workers ?

Well, Service Workers are specialized JavaScript workers. Service workers can intercept connections and cache data.

You are already using Service workers, and you didn't think about it.

Open this link in your chrome: chrome://serviceworker-internals/. It shows all the saved service workers. Lots of websites are using service workers.

For the same reason we run the javascript worker via a server, we need to run our service worker through a server.

Our first example we will use some code from the Google CodeLab.

Let's start by creating our HTML, in our HTML we will add an async script that will have our Service Worker.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Hello World PWA</title>
  </head>
  <body>
    <header class="header">
      <h1>PWA Title</h1>
    </header>

    <main class="main">
    </main>

    <script src="app.js" async></script>
  </body>
</html>
vim scripts/app.js

Here we will check if ServiceWorker is supported by the navigator. If so, we will register the Service Worker. We register the ServiceWorker by passing the file where the Service Worker is located. We also have a callback function, in this case we are just doing a console log saying the service worker is registered.

(function() {
  if ("serviceWorker" in navigator) {
    navigator.serviceWorker.register("service-worker.js").then(function() {
      console.log("Service Worker Registered");
    });
  }
})();

Now, let's check our server. Checking our server, we can see in the console log the Service Worker was registered. If we check the tab in the DevTools -> Application and go to Service Workers, we can see our Service worker is there. Nice!

Now let's handle the events for install and activate. These will be two events we will add in our event listener.

vim service-worker.js

In our file, we will have a cache name and array of files to be cached, we will add the static files soon. In both listeners we are using waitUntil to wait until all the caches are processed. This gives the Service Worker time to update everything.

var cacheName = "first-pwa";
var filesToCache = [];

self.addEventListener("install", function(e) {
  console.log("[ServiceWorker] Install");
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      console.log("[ServiceWorker] Caching app shell");
      return cache.addAll(filesToCache);
    })
  );
});

self.addEventListener("activate", function(e) {
  console.log("[ServiceWorker] Activate");
  e.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(
        keyList.map(function(key) {
          if (key !== cacheName && key !== dataCacheName) {
            console.log("[ServiceWorker] Removing old cache", key);
            return caches.delete(key);
          }
        })
      );
    })
  );
});

In our installation, we are adding all the files into our cache. In the activation, we check if the key and the data cache name is different. This is not the best way to do that, this is just an example to see how we can use cache. In the next episode, we will use a library that will help us a lot with this.

Checking our PWA, we can see in the console that our service worker was installed and activated.

We have activated it and installed but we are not caching anything. Let's add a static file and cache it.

We will add an image into our HTML.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Hello World PWA</title>
  </head>
  <body>
    <header class="header">
      <h1>PWA Title</h1>
    </header>

    <main class="main">
      <img src="images/daily_drip_logo.png" width="700" height="200"/>
    </main>

    <script src="app.js" async></script>
  </body>
</html>

We added the DailyDrip logo as an image. Now what we need to do is add this image into our list of cache files in our Service Worker.

var dataCacheName = "first-pwa";
var cacheName = "first-pwa";
var filesToCache = ["images/daily_drip_logo.png"];

And this is the only thing we need to do to cache our static files! If we had more images, javascript or css files(our app shell), they all will be here.

Now, if we check our website, we can see on Application -> Cache -> Cache Storage and our cache named first-pwa will be there with the image we cached.

If we refresh the image, it will load from the cache. Isn’t this awesome? The way we are doing cache could be improved. Tomorrow we will see a library to make cache really easy, and we will create our App Shell.

I deployed it, so you can see it on the web.

Summary

Today we saw the basics of JavaScript Workers and Service Workers.

Resources