How to Build Progressive Web Apps with Lightning Web Components

Written by MichaelB | Published 2020/12/14
Tech Story Tags: progressive-web-apps | pwa | lightning-web-components | heroku | push-notifications | web-development | software-architecture | coding | web-monetization

TLDRvia the TL;DR App

Earlier this year, a post came out on the Salesforce Developers Blog, entitled “How to Build Progressive Web Apps with Offline Support using Lightning Web Components.” During the post's discussion about using Lightning Web Components (LWC) to build progressive web apps, it mentioned push notifications. My interest was piqued. How simple would it be to use LWC to build an app for push notifications? It turns out — really simple.

A Quick Review:  What Is a Progressive Web App (PWA)?

While a PWA can be used in a web browser like any standard web application, the PWA's power comes from users being able to “install” the PWA to their desktop or mobile device, just like a native app. What you end up with is a kind of pseudo-native app — built and run with standard web-app technologies, but enhanced to do things like caching for offline access and push notifications.
When a user installs PWA to their device, they no longer need to open a web browser to visit your application’s website. They can just open your “app” on their device, just like they would open a social media app or a banking app.

What Are Lightning Web Components (LWC)?

The LWC framework is a lightweight set of reusable components built with JavaScript and HTML. With its own templating system and scaffolding tool for quick initialization of NodeJS projects, building applications with LWC makes for fairly easy work. For developers who need additional support with front-end design, they can easily integrate styles and themes from the Salesforce Lightning Design System into their projects too. Push Notifications Powered by a PWA

Push Notifications

Push notifications help keep users engaged with your application. They provide a way to push meaningful information to your users, rather than waiting for them to pull that information from your application or website. The basic workflow for this interaction looks like this:
  1. Through the web application client, Alissa sends a request to your server that she wants to subscribe to receive push notifications.
  2. The server receives this subscription request and stores it — this involves storing an endpoint URL (unique to Alissa and this application) along with (possibly) information about what kind of content to send and when to send that content.
  3. When it comes time to send a push notification, the server sends a notification to Alissa’s unique endpoint URL.
  4. The web-application client receives the notification from the server and pops up a notification with the appropriate content on Alissa’s device.

Getting Our Hands Dirty — We’re Going to Build One

In this walk-through, we’re going to build a working LWC application that sends push notifications. We’ll be able to use the application in our browser and as a standalone application installed on our device. To do this, we’ll use create-lwc-app to scaffold an LWC application — that’s our client. We’ll also build a lightweight Express server to handle subscribe/unsubscribe requests and to push out notifications. We’ll deploy both the client and the server to Heroku.
What will our application do? We’ll keep it simple. We’ll let our user choose from one of three content sources for their push notification:
  1. Get the current geolocation of the International Space Station.
  2. Get a randomly selected quote about Computer Science.
  3. Get a suggestion for an activity to do when bored.
Also, we’ll let our user choose if they want to receive a push notification every 30, 60, or 180 seconds.
Lastly, we’ll provide users with buttons to subscribe or unsubscribe from these notifications.

Follow Along, Follow the Steps

All the code for this walk-through is available at this Github repository. Here are the steps we’re going to take:
  1. Set up and initialize our LWC application client.
  2. Add push notification functionality to our client’s service worker (more on service workers below — don’t worry!).
  3. Test out deploying our client to the web via Heroku.
  4. Build an Express server to handle subscriptions and notifications.
  5. Deploy our server to the web via Heroku.
  6. Build the UI in our client which allows users to select their notification content and duration.
  7. Wire up the “subscribe/unsubscribe” toggle button in the client’s app.js to handle push notifications and send subscription requests to the server.
  8. Deploy our completed client to Heroku.
  9. Test our shiny new PWA, both in the browser and installed on our device.
This walk-through assumes that you have an account with Heroku (it’s free!) for deploying your client and server. If you have an account with Github to store your own code as you go along, that would be helpful too (though not required).
Are you ready to do this? Let’s dive in.

Initial Setup for Our LWC Application

Our main project folder will, in the end, have a client subfolder and a server subfolder. Starting in your project folder, use yarn to initialize the folder and then add the create-lwc-app package. This package scaffolds an LWC project, which makes getting started with LWC super simple.
~/project$ yarn init 
~/project$ yarn add create-lwc-app
Next, we’ll use create-lwc-app to set up our client project in the client folder:
~/project$ yarn create-lwc-app client -t pwa --yes 
~/project$ cd client

# Install all the dependencies in the client project 
~/project/client$ yarn install

# Since we'll be using yarn rather than npm 
~/project/client$ rm package-lock.json

# Remove git data for create-lwc-app if using your own git repo 
~/project/client$ rm -rf .git
From this point on, all of the files we’ll be working with will be inside the ~/project/client subfolder (until we get to the section on building our server).

Make Some Minor Modifications to Simplify Our Project

The create-lwc-app scaffold provides some niceties that we’re not going to use as we go through this article. We’ll remove some packages just to keep things slim.
In package.json, remove the "husky" and "lint-staged" sections. Then, in the "scripts" section, remove the "prettier" and "test:*" lines. Husky gives us some nice-to-have, pre-commit code cleanup, but that will slow us down unnecessarily when we’re just trying to play and learn. Also, while we would ordinarily want to take advantage of how unit testing is baked right into create-lwc-app, we want to keep this article focused on building something quickly; so we’ll forego testing.
Remove the husky and prettier packages altogether:
~/project/client$ yarn remove husky prettier
Next, we’ll add some packages that we are going to need in our project:
~/project/client$ yarn add nodemon node-fetch @salesforce-ux/design-system
In package.json, change the "watch" script to make use of nodemon to rebuild and serve our project whenever our code changes:
"scripts": {
    ...
    "watch": "nodemon -e js,html,css --watch src --exec

\"yarn build && yarn serve\"" 
}
Our final package.json file should look like this.
Remove the Content Security Policy 
Typically, the LWC framework is served up with a strict Content Security Policy. The rationale is to protect against cross-site scripting vulnerabilities since LWC applications normally access Salesforce account data. For our purposes here, though, we want to turn off this restrictive policy, so that our client’s JavaScript code can send HTTP requests to our server when subscribing to push notifications.
To turn off this policy, we’ll edit scripts/server.js. In this file at line eight, you’ll notice app.use(helmet()). Helmet is a middleware package that sets security-related headers. We’ll simply configure Helmet not to use the default ContentSecurityPolicy, by editing that line to look like this instead:
/* ~/project/client/scripts/server.js 
*/

app.use(
    helmet({
        contentSecurityPolicy: false
    }) 
);
After the edit, our entire server.js file should look like this.

Push Notifications and the Service Worker

What Is a Service Worker?
A service worker is a small piece of code that the browser starts up. Then, the piece of code spins off to run as its own thread, separate from the browser. It gives browsers (and PWAs) the ability to cache data for access even if the user isn’t online, and to listen for push notifications even if their browser is closed. The service worker is fundamental to the building of feature-rich PWAs. Mozilla has put out an excellent resource — the Service Worker Cookbook — with lots of examples for how to use service workers effectively.
Our LWC Application Has a Service Worker Baked In!
You’ll recall that, when we called yarn create-lwc-app, we included a -t pwa flag. This flag results in the generation of scripts/webpack.config.js. This script is called whenever your client project is built, and it uses a method in workbox-webpack-plugin called GenerateSW. Ultimately, this builds a boilerplate service-worker script, which you’ll find in dist/sw.js if you run yarn build.
Additionally, the service worker is registered with the browser at src/index.html (at line 82).
The boilerplate service worker, however, is not set up to handle push notifications. We’ll do that here.
Enabling Our Service Worker to Handle Push Notifications
In our src subfolder, create a file called src/pushSW.js, with the following contents:
/* PATH: ~/project/client/src/pushSW.js 
*/

self.addEventListener('push', (event) => {
  const body = event.data ? event.data.text() : 'no payload';
  event.waitUntil(
    self.registration.showNotification('LWC Push Notifications PWA', { body })
  ) 
});
This tells our service worker to listen for a push event and then react by popping up a notification on the user’s device with the data from that event.
We’ll want to make sure our yarn build script properly copies src/pushSW.js to the dist folder. The dist folder contains all the files that will be served up as our client. To ensure pushSW.js is included, we need to modify lwc-services.config.js, adding the line below for exporting pushSW.js:
/* PATH: client/lwc-services.config.js 
*/ 

module.exports = {
  resources: [
    { from: 'src/resources/', to: 'dist/resources/' },
    { from: 'src/index.html', to: 'dist/' },
    { from: 'src/manifest.json', to: 'dist/' },
    { from: 'src/pushSW.js', to: 'dist/pushSW.js' }
  ] 
};
Lastly, we want to make sure that our pushSW.js code also gets loaded with our service worker. To do this, we want to modify scripts/webpack.config.js, telling GenerateSW to import our pushSW.js code as part of the sw.js file that it generates. We do this like so:
/* PATH: client/scripts/webpack.config.js 
*/
 
const { GenerateSW } = require('workbox-webpack-plugin');
module.exports = {
  plugins: [
    new GenerateSW({
      swDest: 'sw.js',
      importScripts: ['pushSW.js']
    })
  ] 
};

This tells 
This tells GenerateSW, while it’s generating sw.js, to bundle in our code from pushSW.js. Up above, since we wrote pushSW.js and made sure that it is copied to the dist folder, this call to GenerateSW will successfully incorporate our push notification functionality into our service worker.
Now, our LWC application is all set up as a full-fledged PWA that can handle push notifications. Let’s make sure we can deploy it to the web, and then the real fun begins!

Setup Deployment of the Client to Heroku

Next, we’re going to set up a Heroku app so that we can serve up our client on the web. Once you have logged in to Heroku, go to Create new app:
Choose a name for your app. (By the way, app names need to be unique across the herokuapp.com domain, so the example app name shown in this article may not be available to you.)
Click on “Create app.” That’s all there is to setting up your Heroku app. The rest of the work will be at the command line, using the Heroku CLI and git.
Set Up git remote for Heroku Client App
Back at the command line, we’re going to set up a new git remote, and we’re going to call it heroku-client:
~/project$ git remote add heroku-client https://git.heroku.com/
[REPLACE WITH HEROKU APP NAME].git
You may have noticed that we’re doing something a little unconventional here. Ordinarily, if your git repository has a single project you want to deploy to Heroku, then you can just follow the “Deploy using Heroku Git” instructions on your Heroku app’s deployment page. In our setup, however, we have a single git repository which contains both a client project which needs to be deployed (git push) as one Heroku app and a separate server project which needs to be deployed as a different Heroku app. So, we will be creating different git remotes (one for client, one for server), and we’ll use git subtree to push our client application to one remote (called heroku-client), and our server application to the other (called heroku-server).
(If that’s confusing for you, you can absolutely choose to separate the client project and the server project into two separate git repositories. From there, just deploy the standard Heroku Git way.)
We need to add a Procfile to our client folder. This lets Heroku know what command to run in order to spin up the application to serve up the client. The Procfile is one line and can be created like this:
~/project/client$ echo 'web: yarn serve' > Procfile
Let’s add and commit our files:
~/project/client$ cd .. 
~/project$ git add . 
~/project$ git commit -m "Prepared client for initial Heroku deploy"
Make sure you have installed the Heroku CLI and are logged in:
~/project$ heroku login
Now, to push only our client subfolder to the heroku-client remote, we use the following command (rather than the standard git push command):
~/project$ git subtree push --prefix client heroku-client master
Test Our Client Deployment
After your code is pushed to Heroku, you’ll notice on the command line that Heroku goes through a build process and then calls yarn serve to serve up the client application on the web.
Let’s check our browser to see what we have:
Excellent. Our initial LWC application is live!
Let’s look a little closer to see if the service worker with push notifications is properly registered. In your browser (we’ll be using Google Chrome for our example), open your developer tools and find the “Application” tab. In the left sidebar of the developer tools, click on “Service Workers.”
You should see the sw.js service worker active and running. You can test the push-notification functionality by clicking on the “Push” button. You should have received a notification from your browser with the content “test?” embedded. If you didn’t see a notification, you may want to check your browser and your site settings to ensure that you’ve allowed notifications from this site.
That's the end of the first part of our series. In the next part, now that the client has been set up for basic push notifications, we’re going to build our server, which will handle subscriptions and pushing notifications, build the UI, deploy it all to Heroku, and see the app and push notifications in action.
Also published here.

Written by MichaelB | I run Dev Spotlight - we write tech content for tech companies. Email at [email protected].
Published by HackerNoon on 2020/12/14