DEV Community

Cover image for How To Create A PWA With JavaScript
Pato
Pato

Posted on • Updated on

How To Create A PWA With JavaScript

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

Level: Beginner
Requirements: Javascript, HTML, CSS, and 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">
Enter fullscreen mode Exit fullscreen mode

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 at 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 that, especially 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>
Enter fullscreen mode Exit fullscreen mode

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;
  }
}

Enter fullscreen mode Exit fullscreen mode

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";
  }
}
Enter fullscreen mode Exit fullscreen mode

Service Worker Lifecyle

Register

6) Inside of your index.js, you will register your service worker as followed:

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

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);
    })
  );
});

Enter fullscreen mode Exit fullscreen mode

If you want to check the browser to ensure your 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);
        }
      })
    );
  });
});
Enter fullscreen mode Exit fullscreen mode

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);
    })
  );
});
Enter fullscreen mode Exit fullscreen mode

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

Validating our cached files

If you want to see that 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 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"
}
Enter fullscreen mode Exit fullscreen mode

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

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

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/
Enter fullscreen mode Exit fullscreen mode

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 and 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>
Enter fullscreen mode Exit fullscreen mode

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 other tutorial tutorial.

The Lighthouse Report

Lighthouse is a tool that will help 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 your app a PWA but they are good to have and will make your school from Lighthouse higher.

Latest comments (4)

Collapse
 
mma15 profile image
Mubashir Ali Mir • Edited

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

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

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