Creating a Progressive Web App in Ionic 4

February 3, 2019, 9:00 pm Categories:

Categories

Progressive Web Apps - a quick recap

Progressive Web Apps, or PWAs for short, are mobile applications designed to leverage modern web features to deliver Native App-like experiences to the end user while running in the browser.

At its simplest then a Progressive Web App is simply a browser based mobile app (unlike Ionic/Cordova hybrid Apps which are designed to run natively on handheld devices).

Typically a PWA will take advantage of modern Web API's and features such as push notifications, offline caching and installability (they can be 'pinned' to a user's home screen on their device).

When running on older browsers that don't support the latest Web API's/features a Progressive Web App should gracefully degrade and allow the user to reasonably access page content (albeit without the 'bells and whistles' that come with features present in modern browsers).

What must a Progressive Web App be able to do?

Functionally a Progressive Web App must be:

  • Responsive - adapts to the screen size of the device that it is viewed on (mobile, tablet or web)
  • Discoverable - can be indexed by search engines
  • Installable - can be installed to the home user's device
  • Linkable - can be shared, without requiring complex installations, via URL's
  • Network independent - is expected to function in low network connectivity or offline
  • Progressive - works on all browsers
  • Engaging - can engage the user, even when 'switched off' by use of features such as Push Notifications

What are the key features/technologies?

Progressive Web Apps make use of the following architectural patterns, technologies and Web APIs to provide native-like experiences:

  • App Shell model - the minimal HTML, CSS and JavaScript required to power the user interface which is able be cached and does not require reloading by the network on subsequent user visits
  • Web App Manifest - provides information about an application, such as author, icon, description version etc, in the form of a JSON file which allows that application to be pinned to the home screen of a user's device
  • Service Workers - scripts that act as proxy servers that sit between the web application and the browser/network and help to control content caching, background syncing, offline access and push notifications...amongst other features

Unfortunately browser support is always an issue wherever technologies are concerned as demonstrated by the dismal statistics for the Web App Manifest:

Web App Manifest
Browser Version
IE Not Supported
Edge Not Supported
Firefox Not Supported
Google Chrome 38+
Safari Not Supported
Opera 32+
iOS Not Supported
Android UC Browser Not Supported
Android Chrome Browser 62+

Browser support for the Service Worker API support fares slightly better however:

Service Workers
Browser Version
IE Not Supported
Edge 17+
Firefox 57
Google Chrome 49
Safari Technology Preview only
Opera 32+
iOS Not Supported
Android UC Browser 11.5 (partial support)
Android Chrome Browser 62+

As Service Workers play a huge role in driving the functionality of Progressive Web Apps the level of support offered by modern browsers, although increasing, is pretty poor.

This means that only the most up to date browsers will be able to support Progressive Web Apps (I.e. caching/offline access and push notifications) while older browsers will simply get a website that renders in their browser but without some (or all) of the 'bells and whistles' offered by the latest supporting browsers.

For the majority of projects and their intended audience this might not be an issue at all but legacy support is always worth bearing in mind when embarking on any cross-platform development - particularly where such support is limited.

Changes with Ionic 4

In the previous version of Ionic PWA support was baked in and able to be activated with little effort. Since the release of Ionic 4 things are a little different thanks to its framework-agnostic approach which leaves developers able to develop their applications - at the time of writing this article anyway - from the following framework options:

  • Angular (default front-end framework for Ionic)
  • React
  • Vue

Support for further frameworks have been mentioned (such as EmberJS) as well as the option to eschew the use of a framework altogether and simply develop with native TypeScript/JavaScript.

What this bring-your-own-framework approach (or use none at all) means is that the framework of choice is responsible for most of the work with Ionic simply sitting on top of and interacting with that as a secondary layer if you will.

When it comes to building PWAs with Ionic 4 we'll need to install and make use of the @angular/pwa node package - as we'll be using Angular as the default front-end framework for application development.

With that covered let's crack on with creating our PWA with Ionic 4.

Creating an Ionic project

Open your system/third-party command-line software and create a new Ionic project - imaginatively named ionic-pwa - with the following commands:

ionic start ionic-pwa blank --type=angular

Once the project has been created, change into the root directory and install the @angular/pwa node package with the following command:

ng add @angular/pwa --project app

Here we're simply using the underlying Angular CLI tool to install the package to a project named app (this is the value associated with the defaultProject key within the angular.json file situated at the root of the ionic-pwa project directory).

In theory installing the @angular/pwa node package should proceed smoothly but I found myself hitting the following error:


Cannot find module 'parse5/lib/extensions/location-info/tokenizer-mixin'
Error: Cannot find module 'parse5/lib/extensions/location-info/tokenizer-mixin'
    at Function.Module._resolveFilename (module.js:538:15)
    at Function.Module._load (module.js:468:25)
    at Module.require (module.js:587:17)
    at require (internal/module.js:11:18)
    at Object. (my-username/ionic-pwa/ionic-pwa/node_modules/parse5-sax-parser/lib/index.js:5:36)
    at Module._compile (module.js:643:30)
    at Object.Module._extensions..js (module.js:654:10)
    at Module.load (module.js:556:32)
    at tryModuleLoad (module.js:499:12)
    at Function.Module._load (module.js:491:3)

Yikes! Not good!

Fortunately this can be resolved by opening the tsconfig.json file at the root of the ionic-pwa project directory and commenting out the following line:


"moduleResolution": "node",

Save this amendment and re-run the installation of the @angular/pwa node package. All should now proceed smoothly on that front!

Once the package installation has been successfully completed go back to the project's tsconfig.json file and undo the commented line so that this file is returned to its original state like so:


{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "module": "es2015",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ]
  }
}

This file "reset" needs to be implemented as we won't be able to transpile the application if the commented line was left untouched. Definitely not good from a development perspective!

With the @angular/pwa node package now installed our project has a number of changes and additions that have been made.

Let's take a look at what these are...

Application root module

Within the application root module - ionic-pwa/src/app/app.module.ts - a ServiceWorkerModule has been imported from the @angular/pwa node package and initialised within the @NgModule imports array:


import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

This registers the service worker file - ngsw-worker.js - for use in production builds so that the application can take advantage of pre-fetching, caching and push events. This file will be published to the application's www directory when a production build is generated.

Launch icons

Within the src/assets directory we have an icons subdirectory with the following launch icon png files that have been generated for the application:

  • icon-72x72
  • icon-96x96
  • icon-128x128
  • icon-144x144
  • icon-152x152
  • icon-192x192
  • icon-384x384
  • icon-512x512

As these icons use the default Angular logo you will, of course, need to replace these with your own branding (but for the purposes of this tutorial we'll simply continue as is).

Web Manifest

Within the project's ionic-pwa/src sub-directory we have the application manifest.webmanifest

This is responsible for providing information about the application, supplying the launch icons as well as determining the application theming colour/background colour.

Application index.html file

Changes to the project's src/index.html file see the inclusion of the manifest.webmanifest file, a theme-colour meta tag and a <noscript> warning for user's who may have disabled JavaScript in their browsers:


<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8"/>
  <title>Ionic App</title>

  <base href="/"/>

  <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
  <meta name="format-detection" content="telephone=no"/>
  <meta name="msapplication-tap-highlight" content="no"/>

  <link rel="icon" type="image/png" href="assets/icon/favicon.png"/>

  <!-- add to homescreen for ios -->
  <meta name="apple-mobile-web-app-capable" content="yes"/>
  <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
  <link rel="manifest" href="manifest.webmanifest">
  <meta name="theme-color" content="#1976d2">
</head>

<body>
  <app-root></app-root>
  <noscript>Please enable JavaScript to continue using this application.</noscript>
</body>

</html>

That covers the changes so let's now create a production build (we'll simply use the default Ionic project as it is) and deploy that to Firebase Hosting.

Creating a production build

In order for our project to truly make use of the @angular/pwa node package we're going to need to publish this to a production build with the following CLI command:


ionic build --prod

Now this should run smoothly without any incident but unfortunately the production build failed having thrown up the following error (as of early February 2019):


Error: ERROR in common.00974306b5124fe008c9.js from Terser
TypeError: Cannot read property 'minify' of undefined
    at minify (/Volumes/my-username/ionic-pwa/node_modules/terser-webpack-plugin/dist/minify.js:175:23)
    at module.exports (/Volumes/my-username/ionic-pwa/node_modules/terser-webpack-plugin/dist/worker.js:13:40)
    at handle (/Volumes/my-username/ionic-pwa/node_modules/worker-farm/lib/child/index.js:44:8)
    at process. (/Volumes/my-username/ionic-pwa/node_modules/worker-farm/lib/child/index.js:51:3)
    at emitTwo (events.js:126:13)
    at process.emit (events.js:214:7)
    at emit (internal/child_process.js:772:12)
    at _combinedTickCallback (internal/process/next_tick.js:141:11)
    at process._tickCallback (internal/process/next_tick.js:180:9)

Don't you just hate it when the software you use throws up little gems like this?

If you find yourself encountering this delightful event (and until this particular bug is fixed) here's how to resolve that:

  • Delete the Terser package from the project's node_modules directory
  • Open the project's package-lock.json file, locate the terser configuration and change the values for the version number and resolved keys to 3.14.0 (see code snippet below for example) and save the changes
  • Return to the command line and run npm install

Just for reference the amended section of the project's package-lock.json file should look like the following:


"terser": {
      "version": "3.14.0",
      "resolved": "https://registry.npmjs.org/terser/-/terser-3.14.0.tgz",
      "dev": true,
      "requires": {
        "commander": "2.17.1",
        "source-map": "0.6.1",
        "source-map-support": "0.5.9"
      },

With these changes you should now be able to create a production build for the ionic-pwa project.

One last potential error

If you try running a production build and hit the following error:


node_modules/@ionic-native/core/decorators/common.d.ts(1,23): error TS2688: Cannot find type definition file for 'cordova'.

Simply install the Cordova type definitions for your project with the following command:


npm install @types/cordova --save

With this simple install - should you need to follow this step - you should now find the ionic build --prod command succeeds and your newly generated www directory (which contains our PWA production-ready build files) is ready for hosting.

Deploying to Firebase Hosting

Firebase hosting provides the following benefits for Ionic developers looking to host their PWAs:

  • SSL certificate enabled by default (no complicated set-up process required)
  • 1GB storage (with free developer plan)
  • 10GB data transfer monthly allowance (with free developer plan)
  • Custom domain
  • Simply deployment process

It's important to note at this point that you can use Firebase Hosting WITHOUT having to use any of the other services provided by this BaaS - although you WILL need to create a new project (or select an existing one) to enable Firebase Hosting.

To do this go to the web browser, log into the Firebase console, create a new/select an existing project and, on the project overview screen, select Firebase Hosting:

Firebase project overview screen

From the default view for the Firebase Hosting dashboard simply click on the Get Started button:

Firebase hosting service dashboard

We then follow the Set up hosting wizard which guides us through the steps necessary to configure and enable Firebase Hosting for the selected project:

Firebase set up hosting wizard

If you haven't already done so outside of this tutorial follow the Set up hosting wizard instructions and globally install the Firebase CLI to your desktop machine with the following command:

npm install -g firebase-tools

If you should encounter permission errors on a Unix based system you might need to prefix the above command with sudo.

The final screen of the Set up hosting wizard simply provides the CLI commands that we'll need to use to publish our Ionic PWA production build to the Firebase Hosting service:

Final screen of the Firebase set up hosting wizard

Return back to the command line and, with the Firebase CLI now installed, log into your firebase account like so:

firebase login

From within the root of the ionic-pwa project directory issue the following command to initiate a Firebase project:

firebase init

This will launch an interactive wizard which allows you to initialise a Firebase project within the ionic-pwa directory via the following prompts:

  • The Firebase CLI features to set up for the project - select Hosting (as shown in the below screen capture)
  • The default Firebase project for the directory - select the project you enabled the Firebase Hosting service for earlier in this section (as shown in the second screen capture)

Firebase CLI wizard for generating project hosting

Firebase CLI wizard for selecting project for hosting to be assigned to

Once the project has been selected the Firebase CLI will then ask the following questions (the answers you need to provide are included in bold):

  • What do you want to use as your public directory? www
  • Configure as a single-page app (rewrite all urls to /index.html)? Yes
  • File www/index.html already exists. Overwrite? No

=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? www
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
? File www/index.html already exists. Overwrite? No
i  Skipping write of www/index.html

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete!

With the firebase init command completed the following JSON files have been generated in the root of the ionic-pwa project directory:

  • firebase.json (provides information on which Ionic project directories the Firebase Hosting service will use/ignore)
  • .firebaserc (lists the project that the Firebase Hosting service has been initialised for)

With these in place the Firebase CLI now "knows" which service to utilise, the remote project to communicate with and what data needs to be transferred.

As we have already generated our PWA production build we can simply upload this to Firebase Hosting with the following command:


firebase deploy

Which triggers output akin to the following:


=== Deploying to 'fir-comm-d000a'...

i  deploying hosting
i  hosting: preparing www directory for upload...
✔  hosting: 43 files uploaded successfully

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/fir-comm-d000a/overview
Hosting URL: https://fir-comm-d000a.firebaseapp.com

Notice that the firebase deploy command has also provided the hosting url for your PWA?

Copy this somewhere safe for later reference.

The PWA production build code has now been deployed to the Firebase Hosting service for the selected Firebase Project like so (as you can see from the following there have been a number of deployments for further code fixes and enhancements - as is often the case when testing developed code remotely):

List of current and previous deployments to Firebase Hosting

Now you can install the PWA on your mobile device by opening the device browser and navigating to the Firebase Hosting URL that was returned by the firebase deploy command!

Auditing an Ionic PWA

With our PWA production build securely hosted on Firebase we can test how the application performs using the Google Chrome Lighthouse plugin.

Within the Google Chrome browser simply navigate to the Firebase Hosting URL for the PWA, open the Developer Tools, click onto the Audits tab and make the following selections:

Selecting the Lighthouse PWA audit in Google Chrome Developer tools

Click on the Run audit button which will then run each selected audit accordingly:

Lighthouse Audit running in Google Chrome Developer Tools

Once completed your PWA audit scores will then be displayed by ranking for the following criteria:

  • Progressive Web App
  • Performance
  • Accessibility
  • Best Practices

Any errors, warnings or issues encountered during the audit will also be displayed within this report:

Lighthouse PWA score report

In summary

As you have hopefully seen from the above article creating a production-ready PWA with Ionic 4 is relatively simple thanks to the inclusion and use of the @angular/pwa node package.

In addition hosting a production ready PWA can be handled by any hosting service with an SSL certificate enabled for the domain - this is why Firebase Hosting is such a great choice (amongst other features that it offers).

As you've no doubt realised from this tutorial publishing a PWA is MUCH simpler than publishing a mobile app (as there are no additional hoops to jump through such as code signing, App Store submission etc).

If you've enjoyed what you've read and/or found this helpful please feel free to share your comments, thoughts and suggestions in the comments area below.

I explore different aspects of working with the Ionic 4 framework in my e-book featured below and if you're interested in learning more about further articles and e-books that I'm writing please sign up to my FREE mailing list.

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top