Vue JS and Progressive Web Apps

Vue JS and Progressive Web Apps

Progressive web app (PWAs) is a web app that does somethings that a native app does. It can work offline and you can install it from a browser with one click.

To build a PWA, we have to register service workers for handling installation and adding offline capabilities to make a regular web app a PWA. Service works also lets PWAs use some native capabilities like notifications, caching for when the device is offline, and app updates.

To start building the app, we start by running the Vue CLI. We run:

npx @vue/cli create vuepwa

to create the Vue.js project. When the wizard shows up, we choose ‘Manually select features’. Then we choose to include Vue Router, PWA support, and Babel in our project.

Next, we add our own libraries for styling and making HTTP requests. We use BootstrapVue for styling, Axios for making requests, VueFilterDateFormat for formatting dates, and Vee-Validate for form validation.

To install all the libraries, we run:

npm i axios bootstrap-vue vee-validate vue-filter-date-format
npm run serve

Folder structure, but we know this and it’s not a Vue article

Manifest

In order to create a Progressive Web App, the browser must first know that it is a PWA. This is down by creating a Web App Manifest. A Web App Manifest is a JSON file that defines some information about the application. It is very similar to how an Android’s manifest file looks like and contains info such as name, author, and more.

Once again, this file is used to describe the app with the help of some meta data as shown below:

{
"name": "DC-Covers",
"short_name": "Covers",
"start_url": "index.html",
"display": "standalone",
"theme_color": "#0476F2",
"background_color": "#fff",
"icons": [
{
"src": "", // usually kept in the static/icons folder
"sizes": "", // enter the size of the image
"type": "" // enter the type of the image eg: image/png,
},
]
}

Service Workers

One of the more exciting aspects of progressive web apps is that they can work offline. Using service workers, it is possible to show data that was retrieved in previous sessions of the app (using IndexedDB) or, alternatively, to show the application shell and inform the user that they are not connected to the Internet (the approach we’ve taken in this demo). Once the user reconnects, we can then retrieve the latest data from the server.

All of this is possible through service workers, which are event-driven scripts (written in JavaScript) that have access to domain-wide events, including network fetches. With them, we can cache all static resources, which could drastically reduce network requests and improve performance considerably, too.

Application Shell

The application shell is the minimum HTML, CSS and JavaScript required to power a user interface. A native mobile application includes the application shell as part of its distributable, whereas websites ordinarily request this over the network. Progressive web applications bridge this gap by placing the application shell’s resources and assets in the browser’s cache. In our Sky High application, we can see that our application shell consists of the top header bar, the fonts and any CSS required to render these elegantly.

To get started with service workers, we first need to create our service worker’s JavaScript file, sw.js, placed in the root directory.

SW.JS

// Use a cacheName for cache versioning
var cacheName = 'v1:static';

// During the installation phase, you'll usually want to cache static assets.
self.addEventListener('install', function(e) {
    // Once the service worker is installed, go ahead and fetch the resources to make this work offline.
    e.waitUntil(
        caches.open(cacheName).then(function(cache) {
            return cache.addAll([
                './',
                './css/style.css',
                './js/build/script.min.js',
                './js/build/vendor.min.js',
                './css/fonts/roboto.woff',
                './offline.html'
            ]).then(function() {
                self.skipWaiting();
            });
        })
    );
});

// when the browser fetches a URL…
self.addEventListener('fetch', function(event) {
    // … either respond with the cached object or go ahead and fetch the actual URL
    event.respondWith(
        caches.match(event.request).then(function(response) {
            if (response) {
                // retrieve from cache
                return response;
            }
            // fetch as normal
            return fetch(event.request);
        })
    );
});

Let’s look more closely at our service worker. First, we are setting a cacheName variable. This is used to determine whether any changes have been made to our cached assets. For this example, we will be using a static name, meaning that our assets will not change or require updating.

self.addEventListener('install', function(e) {
    // declare which assets to cache
}

The install event fires during the installation phase of the service worker and will fire only once if the service worker is already installed. Therefore, refreshing the page will not trigger the installation phase again. During the installation phase, we are able to declare which assets will be cached. In our example above, we are caching one CSS file, two JavaScript files, our fonts file, our offline HTML template and, of course, the application root. self.skipWaiting() forces the waiting service worker to become active.

So far, we have declared our service worker, but before we see it kick into effect, we need to reference it in our JavaScript. In our application, we register it in main.js

// Register the service worker if available.
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js').then(function(reg) {
        console.log('Successfully registered service worker', reg);
    }).catch(function(err) {
        console.warn('Error whilst registering service worker', err);
    });
}

window.addEventListener('online', function(e) {
    // Resync data with server.
    console.log("You are online");
    Page.hideOfflineWarning();
    Arrivals.loadData();
}, false);

window.addEventListener('offline', function(e) {
    // Queue up events for server.
    console.log("You are offline");
    Page.showOfflineWarning();
}, false);

// Check if the user is connected.
if (navigator.onLine) {
    Arrivals.loadData();
} else {
    // Show offline message
    Page.showOfflineWarning();
}

// Set Knockout view model bindings.
ko.applyBindings(Page.vm);

Copy

We’ve also included two event listeners to check whether the session’s state has changed from online to offline or vice versa. The event handlers then call the different functions to retrieve the data through Arrivals.loadData() and to enable or disable the offline message through Page.showOfflineWarning and Page.hideOfflineWarning, respectively. Our application also checks whether the user is currently online, using navigator.onLine, and either retrieves the data or shows the offline warning accordingly. And in the last line of main.js, we apply the Knockout bindings to our View Model Page.vm.

If we load our application for the first time (with Chrome Developer Tools), we will see nothing new. However, upon reloading, we will see that a number of network resource have been retrieved from the service worker. This is our application shell.

Leave a Reply

Close Menu