38

Build Progressive Web Apps with React — Part 1

 5 years ago
source link: https://www.tuicool.com/articles/hit/zEfIFnU
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Build Progressive Web Apps with React — Part 1

How to transform a React application into a progressive web application with great performance — deep tutorial

222myu6.png!webAruIzuY.png!web

Progressive Web Apps (PWA) are something that almost everyone in mobile app development is talking about. And though PWAs have been around for a while now, they have been gaining more and more attention every day.

Originally introduced by Google in 2015, PWA is an app that can contain features such as offline capability, push notifications, greater performance and speed, and local caching of assets.

With the help of PWA technology, even web developers can build great mobile apps using their own web stack. You don’t need to learn Java, Objective-C, Swift, or React Native. You can build PWAs with Angular, Vue, or React!

In this post, you will get to see how to turn a simple Web App built with React into a Progressive Web App! You will learn how to run the app completely offline, how to cache assets, how to use Workbox to create service workers, and finally how to install the PWA into your smartphone!

Prerequisites

Before getting started, let’s make sure that we have gathered all the “ingredients” required to cook this app.

First, make sure that you have the latest version of Node installed. Then, if you don’t already have a code editor installed, then go get one. I personally recommend the VS Code.

Instead of wasting time and text space on building a whole new web app, I am going to use one that I have already created. This app is going to be a simple Todo App built using React with Firebase and Redux. I have already written a post describing how to build this app.

If you are impatient and just want to get started with the PWA stuff, you clone the full web app using Git as shown below:

$ git clone https://github.com/rajatk16/firedux-todo.git
$ cd firedux-todo
$ npm install

This set of commands will download the project folder, and install the node modules.

It goes without saying that since we are building a React Progressive Web App, we need to have the React CLI installed on the system as well.

It’s also recommended to use Bit for the application’s components so that you won’t have to rewrite them in your next app. Instead, just share them with Bit and make them reusable from any other app you have no or in the future.

Service Workers

Service workers are what make Progressive Web Apps so amazing. Basically, a service worker is a script that is run by the browser in the background. It does not interact with the actual app and even the usual user won’t know anything about it.

Let’s take a look at the code in the index.js file of our app.

aQRbier.png!webryumMju.png!web

The final line of code says serviceWorker.register(); . The older version of the Create-React-App CLI used to do it for us. But with version 2.0, the service worker is unregister(); ed by default. So make sure that you make the change to the code in your future PWAs.

React uses a “cache-first” strategy so that all the assets generated by the app get saved. Take a look at the register function of serviceworker.js file as shown below:

JBFFBjU.png!webfEjErei.png!web

Here, the code says that the service worker will only register if the app is in production mode. You can change the code so that the service worker will worker will register in development mode, but that is usually not a good idea as it will cause problems with caching.

So what we can do is build a production version of the app and serve it as shown below:

$ yarn global add serve
$ yarn build
$ serve -s build

This will run the app on localhost:5000 in the browser. If you open the Developer tools and go to the console tab, you should get a couple of messages from Workbox about the pre-cache status of your app’s assets.

VZ3Avqz.png!web3Q7fIvq.png!web

Create-React-App’s default service worker will only cache the static assets, so if you turn off your internet and refresh the browser, the app’s UI will load because it is a static asset. But the task, which is being fetched from Firebase will not load.

fMnAnyi.png!webqMBZFbf.png!web

Updating the Service Worker

It is more difficult to debug a PWA than a regular app because of how they get cached the browser.

In Chrome’s DevTools, you will see a tab named Application. Inside you can see all the info related to the service worker of our app.

6nY7nuZ.png!webiMNba27.png!web

There are two ways by which we can update the service worker. One is by clicking on the “Update on Reload” option at the top, the other is by manually updating the service worker using the update link. You can also unregister the service worker and reload the browser to update the service worker.

Normally if you make any changes to your app, you would need to rebuild the production version of the app and re-launch it on the browser in order to see the changes.

But things get a little more complicated in case of Progressive Web Apps. That is because the service worker has cached the previous version’s assets, and we need to update the service worker, close all browser tabs that have the app opened in them, and open the app again to see the changes.

Try to change the name of the app from the generic “React App” that we see on the tab to something like “Firedux Todo”. To do this, you will first need to go to public/index.html , change title tag’s content to Firedux Todo , and rebuild the production version of the app as shown below:

$ yarn build
$ serve -s build

Then reopen the app in the browser. If the app’s title has not changed, then try to update the service worker and see if it works.

Creating Custom Service Workers

Create-React-App’s default service worker is good for caching static assets, but we want to do much more than that. For that, we need to disregard the default service worker and create one of our own.

The simplest way to do that is to go to the public folder in our app’s directory and create a new service worker file. I am going to name it customWorker.js . For now, let’s add a simple console log to check if it works.

console.log("Hello World");

The React App is still going to choose its default service worker. To fix this, open the src/serviceworker.js and at line 34 you will find the following code:

window.addEventListener('load', () => {
  const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;

Replace the service-worker.js with the name of our custom service worker.

Run yarn build and serve -s build again to create a new production build and serve it. Reopen the app on chrome and check the console to see if you get the Hello World message printed.

If you get any errors, try to unregister the service worker and reload the browser.

j6zqEjE.png!webq6Bfeaz.png!web

This is a very manual way of using custom service workers in our app. The drawback of this method is that our app can no longer use Workbox. Workbox provides us a with a lot of cool features so it would be better to be able to keep Workbox and create custom service workers. So lets undo all the changes that we made in this section and start over.

Now add a new developer dependency called react-app-rewired which will help us rewrite the webpack config and take be able to use the Workbox.

$ yarn add -D react-app-rewired

Then open the package.json file and rewrite the scripts as shown below:

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test",
  "eject": "react-app-rewired eject"
}

Then create a new file named config.js in the root of the project directory. We will import the workbox-webpack-plugin and map over all the plugins till we get to the GenerateSW plugin, which we will then replace with InjectManifest plugin from Workbox.

const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
module.exports = function override(config, env) {
  config.plugins = config.plugins.map(plugin => {
    if(plugin.constructor.name === 'GenerateSW') {
      return new WorkboxWebpackPlugin.InjectManifest({
        swSrc: './src/sw.js',
        swDest: 'service-worker.js'
      })
    }
    return plugin
  })
  return config
}

We then need to create the sw.js file in the src folder. Inside, we will define the actual custom service worker. I will simply add a console log and call skipWaiting and clientsClaim , ensuring that the service worker gets installed on all the open clients.

console.log("Hello World")
workbox.skipWaiting()
workbox.clientsClaim()

Do not add the last two lines if you are going to write a complex service worker, which you probably are.

Rebuild the production version of the app and serve it on the browser. You should then get the Hello World message printed in the console.

Pre-Cache the Static Assets

Our custom service worker now gets loaded onto the app, but its not really doing anything. In this section, we will write the code that will precache the static assets of the app.

The service worker will use a list of files from the _precacheManifest , which is generated automatically by the InjectManifest webpack plugin and cache those files at the correct route. So in the sw.js file, remove the console.log function and write the following at the end.

workbox.precaching.precacheAndRoute(self.__precacheManifest || [])

Rebuild the production version of the app and serve it on the browser. Then in the DevTool’s console tab, you will see that the pre-cache is responding to all the static assets of the app.

To verify if the precaching is working, go offline and reload the browser. The app should still reload. The only difference here is that none of the Todos will get loaded because it is not a static asset.

Listening for Install and Activate Events in a Service Worker

The code inside a service worker is purely event based. Two such events are the install and activate events. These events are executed code when the PWA gets installed on a device for the first time.

Inside the src/sw.js file, add an EventListener for the install and activate event as shown below:

self.addEventListener('install', event => {
  console.log('install')
})
self.addEventListener('activate', event => {
  console.log('activate')
})

After rebuilding and reserving the app, you will get to see the console print out install and activate in the browser’s console. If you don’t see these messages in the console, thats because the service worker is already installed and activated. You can try unregistering the service worker and reload the browser.

We can do more than just console out messages inside these callbacks. We can clean up old caches and interact with the local storage or indexedDB. Lets start with a setTimeout wrapped inside a Promise .

self.addEventListener('install', event => {
  const asyncInstall = new Promise(resolve => {
    console.log("Waiting to resolve...")
    setTimeout(resolve, 5000)
  })
  event.waitUntil(asyncInstall)
})

It is important that whatever Promise we pass to the waitUntil actually resolves or rejects. If we didn’t resolve in the timeout here, it would wait forever to install the service worker. Then it would never activate. The service worker would never work.

Caching Third Party Resources

Our custom service worker is currently caching only static assets that are included in the React App. So, if you have resources from a third party like Bootstrap, they won’t be included in the cache as they are not available at webpack compile time.

To fix this, we need to change the service worker to add caching for those by assets by using workbox’s registerRoute method, and a regular expression to match the entire URL of the asset. We will pick the staleWhileRevalidate cache strategy.

Inside src/sw.js file, we can define and register a new route for the service worker. The first argument to registerRoute is either a string that matches the resource that we are trying to cache or a RegExp that matches it. We will say that anything that starts with https and ends with min.css or min.js . The second argument to register a route tells workbox what strategy to use when caching that resource.

workbox.routing.registerRoute(
  new RegExp('https:.*min\.(css|js)')
  workbox.strategies.staleWhileRevalidate()
)

If we check the cache on the application tab, we will get to see that the assets are put into the runtime cache instead of the pre-cache . To fix this, we can give a name to the cache by adding the cacheName option as shown below:

workbox.routing.registerRoute(
  new RegExp('https:.*min\.(css|js)')
  workbox.strategies.staleWhileRevalidate({
    cacheName: 'cache'
  })
)

Conclusion

In this post, we saw how to create a Progressive Web App using React’s Create-React-App CLI. We started by talking about React’s default register worker and how it works and what it can do. Then we saw how to update any changes to our app by updating the service worker.

Then, we saw different ways of creating a service worker, and how by using the react-app-rewired library we can still use the Workbox in our custom service worker. We then saw the code necessary to tell our custom service worker to cache the static and third party assets of our app.

In the next post, we will see how to:

  • Add Custom Icons to the PWA
  • Change the name and short name of the PWA
  • Add the PWA to the Home Screen of a smartphone device and the Chrome App Home Screen
  • Access the Camera of a smartphone device
  • And more…

Thanks for reading this long post! I hope this post helped you understand React Progressive Web Apps a little better. If you liked this post, then please do give me a few :clap: and please feel free to comment below. Cheers!

You can also check out my posts about how to build Progressive Web Apps with Angular:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK