PWA: Building and publishing a PWA in 15ish minutes using Angular 5 and Service Worker

Introduction

Here is an express start to your first PWA with service workers and a 100% score from lighthouse. I have taken it as a challenge to be able to present it to you so that you may be able to put your first pwa "hello world" app in 15ish minutes.


What is PWA?

Progressive web apps (PWAs) take traditional web sites/applications — with all of the advantages the web brings — and add a number of features that give them many of the user experience advantages of native apps. This set of docs tells you all you need to know. - Source: https://developer.mozilla.org/en-US/Apps/Progressive
If the above definition sounds interesting, then this is to get you kickstarted with PWA using Angular 5 and inspire you to build more PWAs. End of this post you should be able to build, host and publish a PWA and get motivated to read more about it. Ever since I read about PWAs It has intrested me like crazy. Before, you get excited about PWA, please look into the supported browsers and the growing trend.


Source: https://caniuse.com/#feat=serviceworkers

The good news is every browser is trying to equip itself with this service workers. That encourages us to build more PWAs.

Prerequisites

1. A laptop/computer that you may be able to do it yourself.
2. Have a valid GitHub Account. If you don't have one go to github.com
3. Have the latest nodejs and npm all set to run correctly. You may get the latest nodejs and npm from nodejs.org.
4. A good Text Editor. I am using vscode. You may wish to use anything that you are comfortable.
5. Git command line
6. Last but not the least, have a good Internet speed.


Outline

The clock starts now: Here is the outline of what we will be doing.
1. Install an Enterprise App Starter.
2. Generate a new project
3. Make changes to the generated code so that github is happy to host.
4. Build and Deploy
5. Test it with your phone. You are done!

Lets get started

Assuming that you are already having all the prerequisites.

Step 1: Install an Enterprise App Starter


Go to the command prompt/terminal and do the following. This installs the generator to your command line.

╭─ ~/Development/blog  
╰─$ mkdir first-pwa

╭─ ~/Development/blog  
╰─$ cd first-pwa 

╭─ ~/Development/blog/first-pwa  
╰─$ npm install -g generator-ngx-rocket
~/.npm-global/bin/ngx -> ~/.npm-global/lib/node_modules/generator-ngx-rocket/cli/bin/ngx
+ [email protected]
updated 7 packages in 16.134s

╭─ ~/Development/blog/first-pwa  
╰─$ ngx --help
          __   __
 _ _  __ _\ \./ / ____ ____ ____ _  _ ____ ___
| ' \/ _` |>   <  |--< [__] |___ |-:_ |===  |
|_||_\__, /_/°\_\ ENTERPRISE APP STARTER -~*=>
     |___/ v4.0.0

Usage: ngx [new|update|config|list|<script>] [options]

n, new [name]
  Creates a new app.
  -a, --addon                 Creates an add-on instead.
  --packageManager <yarn|npm> Uses specified package manager.
  --automate <json_file>      Automates prompt answers using JSON file.
  --tools                     Generates only the toolchain
  
u, update
  Updates an existing app or add-on.
  --tools                     Updates only the toolchain
  
c, config
  Configures add-ons to use for new apps.
  All available add-ons are used by default.

l, list
  Lists available add-ons.
  -n, --npm    Show installable add-ons on NPM
  
<script>
  Runs specified script from your package.json.
  Works just like npm run <script>

Step 2: Generate a new project


 Lets now generate the project and I assume that you are in a empty directory (first-pwa).

╭─ ~/Development/blog/first-pwa  
╰─$ ngx new
          __   __
 _ _  __ _\ \./ / ____ ____ ____ _  _ ____ ___
| ' \/ _` |>   <  |--< [__] |___ |-:_ |===  |
|_||_\__, /_/°\_\ ENTERPRISE APP STARTER -~*=>
     |___/ v4.0.0

? What's the name of your app? first pwa
? What kind of app do you want to create? Web app
? Do you want a progressive web app? (with manifest and service worker) Yes
? Which UI framework do you want? Angular Material (more website-oriented)
? Which kind of layout do you want? Side menu with split panels (more app-oriented)
? Do you want authentication? No
? Do you want lazy loading? No
   create package.json
   create .editorconfig
   create .htmlhintrc
   create .stylelintrc
 
   ...

   create src/app/core/shell/shell.component.scss
   create src/app/core/shell/shell.component.spec.ts

Running npm install, please wait...

> [email protected] install ~/Development/blog/first-pwa/node_modules/uws
> node-gyp rebuild > build_log.txt 2>&1 || exit 0


> [email protected] install ~/Development/blog/first-pwa/node_modules/node-sass
> node scripts/install.js

Cached binary found at ~/.npm/node-sass/4.7.2/linux-x64-57_binding.node

> [email protected] install ~/Development/blog/first-pwa/node_modules/puppeteer
> node install.js

Downloading Chromium r536395 - 96.2 Mb [====================] 100% 0.0s 
Chromium downloaded to ~/Development/blog/first-pwa/node_modules/puppeteer/.local-chromium/linux-536395

> [email protected] postinstall ~/Development/blog/first-pwa/node_modules/webpack/node_modules/uglifyjs-webpack-plugin
> node lib/post_install.js


> [email protected] postinstall ~/Development/blog/first-pwa/node_modules/node-sass
> node scripts/build.js

Binary found at ~/Development/blog/first-pwa/node_modules/node-sass/vendor/linux-x64-57/binding.node
Testing binary
Binary is fine
added 1537 packages in 50.038s

All done! Get started with these tasks:
- $ npm start: start dev server with live reload on http://localhost:4200
- $ npm run build: build web app for production
- $ npm test: run unit tests in watch mode for TDD
- $ npm run test:ci: lint code and run units tests with coverage
- $ npm run e2e: launch e2e tests
- $ npm run docs: show docs and coding guides 
  
 

Step 3: Code Change to enable hosting

Now open your favorite editor and do the following.

i) Remove /dist from .gitignore. We need to be able to publish our .gitignore to github pages.
ii) On index.html replace the base tag with below line.
<base href="./" />

This is because the web application when running on gh-pages does not always start with root context. Which means / is not going to work on gh-pages.
iii) Update the ServiceWorkerModule register on the app.module.ts.

From:
ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.production }),


To:
ServiceWorkerModule.register('./ngsw-worker.js', { enabled: environment.production }),


This is also to make sure the worker is working on gh-pages.

Step 4: Build and Deploy

Lets now do the build with following command.
╭─ ~/Development/blog/first-pwa  
╰─$ npm run build

> [email protected] build ~/Development/blog/first-pwa
> npm run env -s && ng build --prod

Date: 2018-03-17T19:30:45.683Z                                                          
Hash: a2be90b48555d5ce9393
Time: 39010ms
chunk {0} polyfills.6632764bf1554842312c.bundle.js (polyfills) 94.5 kB [initial] [rendered]
chunk {1} main.96d63fdea40fe06beccd.bundle.js (main) 782 kB [initial] [rendered]
chunk {2} styles.8214eb1655da67c76bfc.bundle.css (styles) 47.8 kB [initial] [rendered]
chunk {3} inline.318b50c57b4eba3d437b.bundle.js (inline) 796 bytes [entry] [rendered]


Once you have the build ready you may see the files as follows,
$ tree dist/ 
dist/
├── 3rdpartylicenses.txt
├── assets
│   └── ngx-rocket-logo.png
├── favicon.ico
├── index.html
├── inline.318b50c57b4eba3d437b.bundle.js
├── main.96d63fdea40fe06beccd.bundle.js
├── manifest.json
├── MaterialIcons-Regular.012cf6a10129e2275d79.woff
├── MaterialIcons-Regular.570eb83859dc23dd0eec.woff2
├── MaterialIcons-Regular.a37b0c01c0baf1888ca8.ttf
├── MaterialIcons-Regular.e79bfd88537def476913.eot
├── ngsw.json
├── ngsw-worker.js
├── polyfills.6632764bf1554842312c.bundle.js
├── robots.txt
└── styles.8214eb1655da67c76bfc.bundle.css

1 directory, 16 files


At this point you have everything ready for hosting the application. Lets do the deployment.

Go to https://github.com/new and create an empty repository.
Create a new repository as follows

Once you have created the repository you will see the below page.


Just make note of your repository URL from the page. HTTPS or SSH which ever you are comfortable with.

In my case it is as follows.
[email protected]:reflexdemon/first-pwa.git

Now, lets begin our deployment.

i) Initialize git repository on your local
$ git init
Initialized empty Git repository in ~/Development/blog/first-pwa/.git/

ii) Add files to local repository
╭─ ~/Development/blog/first-pwa  ‹master*› 
╰─$ git add .
╭─ ~/Development/blog/first-pwa  ‹master*› 
╰─$ git commit -m "first commit"
[master (root-commit) 84accee] first commit
 116 files changed, 21984 insertions(+)
 create mode 100644 .angular-cli.json
 create mode 100644 .editorconfig
 create mode 100644 .gitignore
 create mode 100644 .htmlhintrc
 create mode 100644 .stylelintrc
 create mode 100644 .yo-rc.json
 create mode 100644 README.md
 create mode 100644 browserslist
 create mode 100644 dist/3rdpartylicenses.txt
 create mode 100644 dist/MaterialIcons-Regular.012cf6a10129e2275d79.woff
 create mode 100644 dist/MaterialIcons-Regular.570eb83859dc23dd0eec.woff2
 create mode 100644 dist/MaterialIcons-Regular.a37b0c01c0baf1888ca8.ttf
 create mode 100644 dist/MaterialIcons-Regular.e79bfd88537def476913.eot
 create mode 100644 dist/assets/ngx-rocket-logo.png
 create mode 100644 dist/favicon.ico
 create mode 100644 dist/index.html
 create mode 100644 dist/inline.318b50c57b4eba3d437b.bundle.js
 create mode 100644 dist/main.96d63fdea40fe06beccd.bundle.js
 create mode 100644 dist/manifest.json
 create mode 100644 dist/ngsw-worker.js
 create mode 100644 dist/ngsw.json
 create mode 100644 dist/polyfills.6632764bf1554842312c.bundle.js
 create mode 100644 dist/robots.txt
 create mode 100644 dist/styles.8214eb1655da67c76bfc.bundle.css
 create mode 100644 docs/backend-proxy.md
 create mode 100644 docs/coding-guides/angular.md
 create mode 100644 docs/coding-guides/e2e-tests.md
 create mode 100644 docs/coding-guides/html.md
 create mode 100644 docs/coding-guides/sass.md
 create mode 100644 docs/coding-guides/typescript.md
 create mode 100644 docs/coding-guides/unit-tests.md
 create mode 100644 docs/corporate-proxy.md
 create mode 100644 docs/i18n.md
 create mode 100644 docs/readme.md
 create mode 100644 docs/routing.md
 create mode 100644 docs/updating.md
 create mode 100644 e2e/app.e2e-spec.ts
 create mode 100644 e2e/app.po.ts
 create mode 100644 e2e/tsconfig.e2e.json
 create mode 100644 karma.conf.js
 create mode 100644 package-lock.json
 create mode 100644 package.json
 create mode 100644 protractor.conf.js
 create mode 100644 proxy.conf.js
 create mode 100644 src/app/about/about-routing.module.ts
 create mode 100644 src/app/about/about.component.html
 create mode 100644 src/app/about/about.component.scss
 create mode 100644 src/app/about/about.component.spec.ts
 create mode 100644 src/app/about/about.component.ts
 create mode 100644 src/app/about/about.module.ts
 create mode 100644 src/app/app-routing.module.ts
 create mode 100644 src/app/app.component.html
 create mode 100644 src/app/app.component.scss
 create mode 100644 src/app/app.component.spec.ts
 create mode 100644 src/app/app.component.ts
 create mode 100644 src/app/app.module.ts
 create mode 100644 src/app/core/core.module.ts
 create mode 100644 src/app/core/http/api-prefix.interceptor.spec.ts
 create mode 100644 src/app/core/http/api-prefix.interceptor.ts
 create mode 100644 src/app/core/http/cache.interceptor.spec.ts
 create mode 100644 src/app/core/http/cache.interceptor.ts
 create mode 100644 src/app/core/http/error-handler.interceptor.spec.ts
 create mode 100644 src/app/core/http/error-handler.interceptor.ts
 create mode 100644 src/app/core/http/http-cache.service.spec.ts
 create mode 100644 src/app/core/http/http-cache.service.ts
 create mode 100644 src/app/core/http/http.service.spec.ts
 create mode 100644 src/app/core/http/http.service.ts
 create mode 100644 src/app/core/i18n.service.spec.ts
 create mode 100644 src/app/core/i18n.service.ts
 create mode 100644 src/app/core/index.ts
 create mode 100644 src/app/core/logger.service.spec.ts
 create mode 100644 src/app/core/logger.service.ts
 create mode 100644 src/app/core/route-reusable-strategy.ts
 create mode 100644 src/app/core/route.service.spec.ts
 create mode 100644 src/app/core/route.service.ts
 create mode 100644 src/app/core/shell/shell.component.html
 create mode 100644 src/app/core/shell/shell.component.scss
 create mode 100644 src/app/core/shell/shell.component.spec.ts
 create mode 100644 src/app/core/shell/shell.component.ts
 create mode 100644 src/app/home/home-routing.module.ts
 create mode 100644 src/app/home/home.component.html
 create mode 100644 src/app/home/home.component.scss
 create mode 100644 src/app/home/home.component.spec.ts
 create mode 100644 src/app/home/home.component.ts
 create mode 100644 src/app/home/home.module.ts
 create mode 100644 src/app/home/quote.service.spec.ts
 create mode 100644 src/app/home/quote.service.ts
 create mode 100644 src/app/material.module.ts
 create mode 100644 src/app/shared/index.ts
 create mode 100644 src/app/shared/loader/loader.component.html
 create mode 100644 src/app/shared/loader/loader.component.scss
 create mode 100644 src/app/shared/loader/loader.component.spec.ts
 create mode 100644 src/app/shared/loader/loader.component.ts
 create mode 100644 src/app/shared/shared.module.ts
 create mode 100644 src/apple-touch-icon.png
 create mode 100644 src/assets/ngx-rocket-logo.png
 create mode 100644 src/environments/environment.prod.ts
 create mode 100644 src/environments/environment.ts
 create mode 100644 src/favicon.ico
 create mode 100644 src/index.html
 create mode 100644 src/main.scss
 create mode 100644 src/main.ts
 create mode 100644 src/manifest.json
 create mode 100644 src/ngsw-config.json
 create mode 100644 src/polyfills.ts
 create mode 100755 src/robots.txt
 create mode 100644 src/test.ts
 create mode 100644 src/theme/theme-variables.scss
 create mode 100644 src/theme/theme.scss
 create mode 100644 src/translations/en-US.json
 create mode 100644 src/translations/fr-FR.json
 create mode 100644 src/tsconfig.app.json
 create mode 100644 src/tsconfig.spec.json
 create mode 100644 src/typings.d.ts
 create mode 100644 tsconfig.json
 create mode 100644 tslint.json

iii) Update your remote and push them.
╭─ ~/Development/blog/first-pwa  ‹master› 
╰─$ git remote add origin [email protected]:reflexdemon/first-pwa.git
╭─ ~/Development/blog/first-pwa  ‹master› 
╰─$ git push -u origin master
Counting objects: 131, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (128/128), done.
Writing objects: 100% (131/131), 670.72 KiB | 0 bytes/s, done.
Total 131 (delta 11), reused 0 (delta 0)
remote: Resolving deltas: 100% (11/11), done.
To [email protected]:reflexdemon/first-pwa.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

Now, building, packing are shipping are done. Hosting is the only thing pending.

Please use the below commands and you should be able to host your application.


╭─ ~/Development/blog/first-pwa  ‹master› 
╰─$ git subtree push --prefix dist/ origin gh-pages
git push using:  origin gh-pages
Counting objects: 19, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (18/18), done.
Writing objects: 100% (19/19), 453.30 KiB | 0 bytes/s, done.
Total 19 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), done.
To [email protected]:reflexdemon/first-pwa.git
 * [new branch]      0445901608cce0afa1ea60a9fac1d3ecf908d721 -> gh-pages

Here you go with your first pwa.

https://reflexdemon.github.io/first-pwa/


Your URL might be something like, https://.github.io/first-pwa/

The clock stops here.

Lighthouse

Lighthouse is an open-source, automated tool for improving the quality of web pages. You can run it against any web page, public or requiring authentication. It has audits for performance, accessibility, progressive web apps, and more.

Please visit here to be able to run your lighthouse report.

 
I have published my lighthouse report here. https://reflexdemon.github.io/first-pwa/reflexdemon.github.io_2018-03-17_16-24-05.report.html

Conclision

Hope you did it inside my challenge of 15ish min to  deploy your first PWA. At this point I am happy that you learn something into the new buzz word PWA.
After completing this post, If you are still intrested to read more about PWAs, here is the path I would recommend you to begin your PWA journey.

Please visit the links in the order provided below,
1. https://developer.mozilla.org/en-US/Apps/Progressive
2. https://developers.google.com/web/progressive-web-apps/
3. https://angularfirebase.com/lessons/hnpwa-angular-5-progressive-web-app-service-worker-tutorial/#Angular-5-Service-Worker-Everything-you-Wanted-to-Know
4. https://angular.io/guide/service-worker-intro
5.  https://blog.vpv.io/2018/03/pwa-building-and-publishing-pwa-in.html



Comments

  1. When adding PWA capability to a regular Angular project, the command:
    $ ng add @angular/pwa
    automatically adds PWA features to our Angular application, such as a manifest.json file, icons and the ngsw-worker.js service worker.

    However, in the Ngx Rocket app generated app, there is no ngsw-worker.js in the src directory, and it only gets build by running npm run build --env=prod.

    Why is the file not part of the project, and what if you wanted to add another function to the service worker for example to look for differences in http responses?

    Thanks in advance.

    ReplyDelete

Post a Comment

Popular posts from this blog

Java SSL/TLS Testing Tool: Cipher Suite

Yarn and Angular-CLI