Using Workbox 2 and Angular to create a Progressive Web App. Part 1: App shell

This is a set of 5 articles dedicated to creating PWAs using the newest framework and library mentioned in the header. We start from PWA essentials and move to more sophisticated features. The code for all 5 parts is ready and pushed to the repo I mention below, master branch)
Part 1: App shell. This resource
Part 2: Runtime caching. Soon
Part 3: Broadcast channel. Soon
Part 4: Background sync. Soon
Part 5: Push notifications. Soon

The web apps we call “progressive” today will be the regular ones in the nearest future. Create React App generates PWA by default, Angular CLI will do the same soon, other frameworks have corresponding templates and generators.

What if we want to add PWA features to our app in framework-agnostic way? There are some great helpers for us! I believe, the current de-facto standard is sw-precache by Google. Its successor, presented at Google I/O 2017, is named Workbox. This article is about my experiments with its version 2.0, released recently.

Workbox will help to create progressive web apps using any front-end framework (or no framework). For my case this will be Angular 5 (beta 5) with UI built using Angular Material (beta 10).

Let’s go for the classics and create a Twitter-like demo app: some feeds, posting a message, push notifications. Since this is a PWA Twitter, we’ll call it PWAtter :) Here is the hosted demo.

According the definition, Progressive Web App is the app using the latest web technologies (by using the modern browsers’ APIs). For PWAtter we’ll take the following subset of features:

  • Web manifest
  • Application shell and runtime caching to optimize networking
  • Gentle app shell update flow
  • Offline fallback for the message posting (with “replay when online” feature)
  • Push notifications

We skip the story about web manifest, as for our case it’s not about the coding. I just generated the assets using realfavicongenerator.net. We jump straight to the…

Application shell

In simple words, this is the minimal set of the files required for your application to start and display some meaningful parts of UI. For example, header with application title, menu, loading indicator. In the best case, all this should be server-rendered, but let’s keep this for another article — it’s not a strict requirement for app shell concept. What could we do with these files? Right! Put them all into the cache (a special one, operated via Cache API, do not mess it with HTTP cache), and serve from there using Service Worker API, instead of going for the network. This way we have our app up and running in offline mode + avoid some network requests in online mode.

Some things to care about:

  • Exact list of files: we don’t want to manage this manually. The set could be different from build to build, also filenames could contain hashes, if we go for cache-busting (I mean HTTP cache here).
  • Versioning: we don’t want to show app shell v1 forever.
  • Cache management: it’s our duty to do a spring cleaning after new version of app shell landed. Cache in not endless and has some quotas.

All listed above could be solved by just one command of Workbox CLI.

Angular app and testing offline mode

Let’s jump to our Angular app for the moment. I prepared everything for our demos on the PWAtter repo (we need step1 branch). Clone it, and install npm packages.

To be able to build the app, we heed Angular CLI to be installed:

npm install @angular/cli --global

Now we can go for:

npm run build-prod

This will generate Angular app in dist folder (this command is nothing but a shortcut to Angular CLI’s production build with disabled filenames hashing — for the visually cleaner output).

Let’s serve it using any static webserver and open in Chrome (I strongly recommend you to check if you use the latest version of the browser)

I use Web Server for Chrome extension.

The simplest option to webserve some static files

Obviously, after we switch to Offline mode (using Chrome DevTools):

we’ll see our good old friend — offline dyno.

Do you know about this Chrome’s built-in game? Hit Space in your browser when see a page with dynosaur!

Workbox setting up

Let’s say farewell to this animal! It’s time to install Workbox:

npm install workbox-cli --global

Now we can run:

workbox generate:sw

This nice CLI will ask some questions (to get this dialog, please delete workbox-cli-config.js file first) about file types and location and generate a service worker. In my example I called it sw-default.js :

workbox generate:sw
? What is the root of your web app? dist
? Which file types would you like to cache? (Press <space> to select, <a> to toggle all, <i> to inverse selection)txt, png, ico, htm
l, js, json, css
? What should the path of your new service worker file be (i.e. './build/sw.js')? dist/sw-default.js
? Last Question - Would you like to save these settings to a config file? Yes

This is very convenient to save the settings in workbox-cli-config.js, as this tool offers, so this file will appear again.

Let’s go to the dist folder. Three new files appeared:

  • sw-default.js — auto-generated service worker
  • workbox-sw.prod.v2.0.0.js — Workbox’ high-level library (+its map-file)

What’s inside the first file?

  • Importing main library
  • The list of all files we specified during setup and their hashes (revisions)
  • Instantiating of WorkboxSW object and calling precache() method

Looking ahead, I can say that as a result we also got all the routing rules set up for us by this code, so it’s not only precaching itself.

Registering service worker

Now we are ready to ask browser to use this auto-generated sw-default.js as a service worker for our app. There are two ways possible:

  • Having registration code in index.html
  • Register our service worker after Angular app bootstrapped

The best practice here is: the later — the better, for service worker not to compete with the main thread for bandwidth and processor time, saving precious milliseconds of first-time load for our customers. So we go for the second option.

If you still want to register service worker without using events exposed by your framework-of-choice, do not forget to put the registration code to window.onload event (to follow the best practice I mentioned).

For our case, the code in main.ts will look like:

For sure, it could be much more laconic, but I decided to output all the feedback I have, for the testing purposes.

Now run

npm run build-full-workbox-default

(this is the conjunction of Angular build and Workbox generate commands in my package.json). And hit refresh in your browser. Do not forget to switch off offline-mode :)

And now… magic moment! Turn on offline mode again, reload the page — boom! The app is still there.

Well, it looks a bit broken. For the demo purpose I decided to use external font and icon font, and of course Workbox CLI couldn’t locate and place these assets during the build. We will fix this in the next chapter, but for now let’s check what happened.

Open DevTools -> Application — this will be our main section. You will see the service worker registered to serve our origin (combination of protocol+hostname+port) and its scope (root in our case):

Also, we can check the contents of Cache (you may need to right-click on Cache Storage and choose Refresh Caches to see the actual content):

From the Workbox precache() docs:

Revisioned assets can be cached intelligently during the install (i.e. old files are cleared from the cache, new files are added to the cache and unchanged files are left as is).
In addition to maintaining the cache, this method will also set up the necessary routes to serve the precached assets using a cache-first strategy.

We could fine-tune the build behaviour by specifying globIgnores in settings file:

Awesome! It means that we have to do nothing (except one-time npm script setup action) to generate a fresh app shell on each build, to serve the assets from cache, to maintain cache lifecycle in an optimal way. Yes and no.

Yes — for the simplest PWA cases. Where we need only an app shell and all the assets are available during the build time. Then we can fully rely on this 100% auto-generated service worker, which intended to be the main one.

No — for the rest of cases :) Even in our simplest app we already need something more (caching resources in a runtime), and service workers could be very different for different projects. So we need a way to create our own main service worker, and extend it by Workbox features. In contrary to sw-precache, it’s possible now! Actually, using workbox-cli is the one of three ways to build PWAs using workbox — the top-level one, simplest, but not flexible at all.

On the next chapter we

  • Go away from workbox-cli in favour of workbox-sw and workbox-build
  • Create our custom service worker and extend it by precache from Workbox
  • Implement runtime caching with the different strategies to optimize networking for font assets and API calls

Stay tuned! I’ll be happy to answer your questions on Twitter @webmaxru. You may want to check my speaking appearances for this year.

I present mainly about PWA and Angular. Catch me there to have a chat about these.