Optimising the performance of an Ionic PWA - Part 1
Angular 5 and the Angular CLI provide improved support for Progressive Web Applications (PWA).
Background
I am currently working on a PWA built using version 3 of the Ionic Framework:
Step 1 - Update the Angular CLI
To update the global and local (devDependencies) versions of the Angular CLI we'll use the Node Package Manager (npm):
npm install -g @angular/cli
npm install @angular/cli --save-dev
You can check the version by running the following command:
ng --version
You should see output like:
Angular CLI: 1.7.4
Node: 9.9.0
OS: darwin x64
Angular: 5.0.3
...
Step 2 - Adding a Service Worker
Angular's support for Service Workers is provided by the @angular/service-worker
module.
You can add Angular's Service Worker (NGSW) module to your Ionic project using npm:
npm install @angular/service-worker --save
core.module.ts
Next we need to update our Core Module. We'll start by importing the ServiceWorkerModule
from the @angular/service-worker
package:
...
import { ServiceWorkerModule } from '@angular/service-worker';
import { AngularFireModule } from 'angularfire2';
import { AngularFirestore, AngularFirestoreModule } from 'angularfire2/firestore';
import { AngularFireAuthModule } from 'angularfire2/auth';
import { ENV } from '@env';
...
Then we need to add the ServiceWorkerModule
to the Core Module's imports array:
@NgModule({
imports: [
CommonModule,
IonicModule,
AngularFireModule.initializeApp(ENV.firebase),
AngularFirestoreModule,
// ENV.production ? ServiceWorkerModule.register('/ngsw-worker.js') : []
ServiceWorkerModule.register('/ngsw-worker.js', { enabled: ENV.production })
],
...
})
export class CoreModule {
...
}
Note: There seems to be a problem when registering a service worker in a module where AngularFire2 is also imported. I had to update my main.ts
as follows:
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
import { ENV } from '@env';
if (ENV.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule).then(() => {
if ('serviceWorker' in navigator && ENV.production) {
navigator.serviceWorker.register('/ngsw-worker.js');
console.log('SW registered');
}
}).catch(error => console.log(error));
ngsw-config.json
Now we're ready to create our service worker configuration file:
{
"index": "/index.html",
"assetGroups": [{
"name": "Brew",
"installMode": "prefetch",
"resources": {
"files": [
"/index.html",
"/manifest.json"
],
"versionedFiles": [
"/build/*.js",
"/build/*.css"
],
"urls": []
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**"
],
"urls": [
"https://fonts.googleapis.com/**"
]
}
}]
}
package.json
We can update our npm scripts to include Angular Service Worker (NGSW) support:
"scripts": {
...
"ngsw-config": "node_modules/.bin/ngsw-config www src/ngsw-config.json",
"ngsw-copy": "cp node_modules/@angular/service-worker/ngsw-worker.js www/",
"build": "ionic-app-scripts build && npm run ngsw-config && npm run ngsw-copy"
...
},
Now let's try a production build:
npm run build --prod
ngsw.json
And then take a look at NGSW's control file:
{
"configVersion": 1,
"index": "/index.html",
"assetGroups": [
{
"name": "Brew",
"installMode": "prefetch",
"updateMode": "prefetch",
"urls": [
"/index.html",
"/manifest.json",
"/build/0.js",
"/build/1.js",
"/build/10.js",
"/build/2.js",
...
"/build/main.css",
"/build/main.js",
"/build/polyfills.js",
"/build/sw-toolbox.js",
"/build/vendor.js"
],
"patterns": []
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"urls": [
"/assets/fonts/noto-sans-bold.ttf"
"/assets/fonts/noto-sans-bold.woff",
"/assets/fonts/noto-sans-regular.ttf",
"/assets/fonts/noto-sans-regular.woff",
"/assets/fonts/noto-sans.scss",
...
"/assets/icons/android-chrome-192x192.png",
"/assets/icons/android-chrome-512x512.png",
"/assets/icons/apple-touch-icon.png",
"/assets/icons/favicon-16x16.png",
"/assets/icons/favicon-32x32.png",
"/assets/icons/favicon.ico",
"/assets/icons/icon-1200x1200.png",
"/assets/icons/icon-128x128.png",
"/assets/icons/icon-144x144.png",
"/assets/icons/icon-152x152.png",
"/assets/icons/icon-192x192.png",
"/assets/icons/icon-384x384.png",
"/assets/icons/icon-512x512.png",
"/assets/icons/icon-72x72.png",
"/assets/icons/icon-96x96.png",
"/assets/icons/mstile-150x150.png",
"/assets/icons/safari-pinned-tab.svg"
],
"patterns": []
}
],
"dataGroups": [],
"hashTable": {
"/build/0.js": "52a2e26203d073ee2e8704330b07f1cecf0d513e",
"/build/1.js": "ed5cd1ba257c919835e853389ba43035aed20a4d",
"/build/10.js": "71f52bb683be89d2f12caaf1d79b895d10d35a76",
"/build/2.js": "e03b904da18dde1fd704d739b2c1ba2acb5879bc",
...
}
}
Now we're ready to serve our app, for example:
firebase serve
And check (in Chrome DevTools) that our service worker has been registered:
What's Next
In the next post, I'll show you how to optimise your CSS delivery.
Resources:
- Maxim Salnikov: A new Angular Service Worker — creating automatic progressive web apps. Part 1: theory
- Maxim Salnikov: A new Angular Service Worker — creating automatic progressive web apps. Part 2: practice
- Sebastian Eschweiler: Angular 5 Service Worker
- Angular University: Angular Service Worker - Step-By-Step Guide for turning your Application into a PWA