loading...
Cover image for How to create a PWA with JavaScript

How to create a PWA with JavaScript

devpato profile image Pato ・6 min read

If you don't know what a PWA is nor a service worker, take a look to my article here.

Level: Beginner
Requirements: Javascript, HTML, CSS, and having a good attitude.

Now, let's jump into creating a PWA from SCRATCH!

LIVE DEMO

https://my-first-pwa-744ee.firebaseapp.com/

Alt Text

Creating our PWA

1) Inside of your project's folder, you will create the following files:

2) Go to your index.html file and paste the following code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="manifest" href="manifest.json">
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#222" />
    <meta name="description" content="My First PWA">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="apple-mobile-web-app-title" content="My First PWA">
    <link rel="apple-touch-icon" href="/icons/icon-152x152.png">
    <title>My First PWA</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
    <link rel="stylesheet" type="text/css" href="./styles/styles.css" />
  </head>
  <body>
    <!-- More before code here -->
    <script src="./js/index.js"></script>
  </body>
</ lang="en">

I feel the meta tags are pretty self explanatory. If you want to dig more into them, just go to google.com :)

If you take a look to the code, you can see I'm importing the manifest.json, the styles.css and the index.js, so you don't have to worry about doing so, specially the manifest.json, you probably haven't seen this file before and its very important.

3) Inside of your index.html, where you have the body, we will create our responsive navbar.

FYI: This navbar was taken from here. The purpose of this tutorial is not to show you how to write css.

<div class="navbar" id="navbar">
  <a href="#home" class="active">Home</a>
  <a href="#news">News</a>
  <a href="#contact">Contact</a>
  <a href="#about">About</a>
   <a href="javascript:void(0);" alt="button to toggle menu" aria-label="button to toggle menu" class="icon" onclick="toggleMenu()">
    <i class="fa fa-bars"></i>
  </a>
</div>

4)Time to add some colors to our app inside our styles.css

body {
  margin: 0;
  font-family: Arial, Helvetica, sans-serif;
}

.navbar {
  overflow: hidden;
  background-color: #333;
}

.navbar a {
  float: left;
  display: block;
  color: #f2f2f2;
  text-align: center;
  padding: 14px 16px;
  text-decoration: none;
  font-size: 17px;
}

.navbar a:hover {
  background-color: #ddd;
  color: black;
}

.navbar a.active {
  background-color: #c3042f;
  color: #fff;
}

.navbar .icon {
  display: none;
}

@media screen and (max-width: 600px) {
  .navbar a:not(:first-child) {
    display: none;
  }
  .navbar a.icon {
    float: right;
    display: block;
  }
}

@media screen and (max-width: 600px) {
  .navbar.responsive {
    position: relative;
  }
  .navbar.responsive .icon {
    position: absolute;
    right: 0;
    top: 0;
  }
  .navbar.responsive a {
    float: none;
    display: block;
    text-align: left;
  }
}

5) index.js file paste the following code that will be doing the opening and closing of our responsive menu.

function toggleMenu() {
  const navbar = document.getElementById("navbar");
  if (navbar.className === "navbar") {
    navbar.className += " responsive";
  } else {
    navbar.className = "navbar";
  }
}

Service Worker Lifecyle

Register

6) Inside of your index.js you will register your service worker as follow

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("sw.js").then(() => {
    console.log("[ServiceWorker**] - Registered");
  });
}

Install

7) Inside of your service worker (sw.js) file, you will add the following code to install your service worker. Then we are going to cache some of our assets. The caching will take place during the install of your service worker.

Note: Besides caching, you can do way more things during the install.

// Service Worker
const cacheName = "my-first-pwa";
const filesToCache = [
  "/",
  "index.html",
  "./js/index.js",
  "./styles/styles.css"
];

self.addEventListener("install", e => {
  console.log("[ServiceWorker**] Install");
  e.waitUntil(
    caches.open(cacheName).then(cache => {
      console.log("[ServiceWorker**] Caching app shell");
      return cache.addAll(filesToCache);
    })
  );
});

If you want to check on the browser if you Service Worker got registered, go to Google Dev Tool-> Application -> Service Worker

Alt Text

Activate

8) Inside of your service worker (sw.js) file, you will add the following code. In this code snippet we wait for an activate event, and then run a waitUntil() block that clears up any old, unused caches before a new service worker is activated. Here we have a whitelist containing the names of the caches we want to keep. We return the keys of the caches in the CacheStorage object using keys(), then check each key to see if it is in the whitelist. If not, we delete it using CacheStorage.delete.

self.addEventListener("activate", event => {
  caches.keys().then(keyList => {
    return Promise.all(
      keyList.map(key => {
        if (key !== cacheName) {
          console.log("[ServiceWorker] - Removing old cache", key);
          return caches.delete(key);
        }
      })
    );
  });
});

Cache falling back to the network

9) If you're making your app offline-first, this is how you'll handle the majority of requests. Other patterns will be exceptions based on the incoming request.

self.addEventListener("fetch", event => {
  event.respondWith(
    caches.match(event.request, { ignoreSearch: true }).then(response => {
      return response || fetch(event.request);
    })
  );
});

To learn about different fallback approaches for your scenarios visit this google docs

Validating our cached files

If you want to see if your files have been cached, all you have to do is go to the Google Dev tools->Application->Cache Storage

Alt Text

Manifest.json

This file is a file that tells the browser some basic information about your pwa and how is your app going to behave once it has been installed on the user's device.

Have you ever gone to a website and it asks you "Add to Home Screen"? well this is thanks to the manifest.json.

10) Inside of you manifest.json paste the following code:

{
  "short_name": "My PWA",
  "name": "My First PWA",
  "icons": [
    {
      "src": "./icons/icon.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "./icons/icon.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/index.html",
  "background_color": "#222",
  "display": "standalone",
  "scope": "/",
  "theme_color": "#c3042f"
}

To learn more about the manifest.json visit this docs.

Also don't forget that we added the manifest in the html ;)

11) In the root of your project, create a folder called "icons". Then add whatever icon you want with the name icon.png. If the icon has another name, you will have to update the manifest.json

Note: You can get some icons from FlatIcons https://www.flaticon.com/.

12) Verify the manifest.json is being detected. Once again, go to the Google Dev Tools->Application->Manifest

Alt Text

Now, once you run this app in your browser. It should ask you if you want to add the PWA to the home screen.

Robots.txt file

13) Inside of this Robots.txt file you can enter the following code, or you can visit this website to generate yours.

# robots.txt generated by smallseotools.com
User-agent: Googlebot
Disallow: 
User-agent: *
Disallow: 
Disallow: /cgi-bin/

This file is good for bots and SEO. If you want to learn more about them take a look at this article:

https://www.cloudflare.com/learning/bots/what-is-robots.txt/

Sitemap

Add the sitemap code inside of the sitemap.xml. You can generate one here: https://www.xml-sitemaps.com/. Or use mine, just change the URL.

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<!-- Generated by Web-Site-Map.com -->
<url><loc>https://my-first-pwa-744ee.firebaseapp.com/</loc><lastmod>2020-01-20T23:50:35+00:00</lastmod><changefreq>always</changefreq><priority>1.00</priority></url>
</urlset>

Deploy your PWA.

I'm not going to get into this again, but you can deploy the PWA wherever you want or you can do it via Firebase by following my tutorial.

The Lighthouse Report

Lighthouse is a tool that will help you get some metrics for our app. The Lighthouse report can be generated by adding the Google Chrome extension. Lighthouse will generate a report that will test your app for performance, accessibility , best practices, SEO and if it’s a PWA.

In order to run the report, go to your PWA website, and click on the Lighthouse Chrome extension and wait for the magic. You should see something like this.

Alt Text

Note The Robots.txt file and the sitemap.xml aren't needed to make you app a PWA, but they are good to have and will make your school from Lighthouse higher.

Discussion

pic
Editor guide
Collapse
mma15 profile image
Mubashir Ali Mir

Hi Pato!! Thank you for tutorial its really helpful. There was one thing I was wondering , how do we combine the sw.js you created for the Add to Home Screen PWA to the firebase push notifications one. Currently, both my service workers are overwriting each other so only one of them works.

Just to clarify, I am registering both service workers in main.js, one after each other.

Collapse
devpato profile image
Pato Author

You can only have one service worker (besides the firebase-messaging-sw.js). In this code I have that functionality combined together github.com/devpato/creating-a-pwa-...

Collapse
mma15 profile image
Mubashir Ali Mir

Thank you for the quick response! So just to confirm, with this, the sw.js is able to function properly and firebase is still able to talk to the background app without us registering it on our side?

Thread Thread
devpato profile image
Pato Author

you will have the firebase-messaging-sw.js and the sw.js file like I did on the repo. Make sure the firebase-messaging-sw.js has that name. That's how firebase code will look for that file