Monday , July 26 2021

How to create a custom PWA with Workbox in create-react-app

Note: This is the third of a series of posts on PWA inside React. For a quick primer, see the two previous posts here and here.

In this follow-up post, I'll show you how to create a progressive Custom Web app (PWA) using the Google Workbox library without ejecting from the CRA shell (create-react-app).

Workbox is a library collection that simplifies the creation of offline functionality. Workbox is also considered the successor of sw-precache library, which CRA uses to generate a default SW.

We talked about the migration of the CRA from sw-precache in the work box (refer to this problem for details). Unfortunately, nothing seems to be yet.


  1. Configure the CRA build to use react-app-rewired. (react-app-rewired is a library to configure the default CRA build without ejection)
  2. Use react-app-rewired to customize the build to use Workbox to generate a worker
  3. Build a simple app for things to do
  4. Implement offline functionality for the todo app using Workbox.
    The offline functionality that we will target:
    a) Resources retrieved from the cache so that they can be published offline
    b) Allow POSTing of offline data

Presentation of Workbox in CRA

First, create a new CRA repository with the following command:

npx create-react-app react-app-rewire-workbox

This should set up a new folder with the relevant name. After setting this folder, insert cd into the folder and create a worker service file in the public folder. I'll call mine custom-service-worker.js.

Once you've done this, go ahead and remove the check NODE_ENV being set to PRODUCTION inside of registerServiceWorker.js

Finally, inside the custom-service-worker.js file, paste the following code:

This snippet of code is something I have collected directly from the Workbox website. You use the importScripts line to inject a global call variable workbox in your file. The script you are importing is served via a CDN. It is therefore necessary a simple check to check if the variable has been correctly loaded by the script or not.

So, now we have Workbox working for us in a development environment. Next, let's see how to implement react-app-rewired in CRA.

Implementation of react-app-rewired In CRA

Add the react-app-rewired package in the project folder using the following command:

Install npm --save-dev react-app-rewired

Now, if you read the documents, they mention that you have to create a config-overrides.js files in the root directory of your project. Let's see what this does first.

I will prepare a barebone file and I will explain what it means. There is a very detailed explanation of this in the documents, if you want to read it instead.

You can export an object from this file with three keys: webpack, jest, devServer. The respective functions allow to configure the configuration of the webpack production server, the jest configuration and finally the configuration of the webpack development server.

If you look at the devserver key in the config-overrides.js file, you will notice that we are recording configFunction.toString () instead of only configFunction . This is because if you try this last one, Node will print only [Function] to the console.

Open yours package.json file and replace the script command to start start reacted by app-rewired .

Build the Todo app

So far, we have managed to introduce Workbox into our development environment and we have also introduced it react-app-rewired in our CRA shell. We leave things as they are and build an example app and let it run in the development environment.

The Todo app will need a couple of moving parts, just so we can actually make use of the service workers.

It will involve:

  1. A basic user interface level (I'm going to completely ignore the style for this.)
  2. A JSON-Server we can request data from

I will not go into detail about the setting, because it's all pretty simple. I will include a link to a git repository with a working version of this app at the end of this article, so you can take a look at this.

Here is the Todo component I wrote.

The component makes a recovery request on a JSON-Server I set up, and get a response composed of a series of todos. The component therefore makes the latter. As I said, extremely simple.

To set the JSON-Server execute the following command:

installation of npm --save json-server

Create a file called db.json with the following structure

Finally, run the following command in the terminal:

json-server --watch db.json --port 8000

This runs a local server on port 8000 and looks at the db.json file for any changes. In case something changes, the server will restart itself. It's a very simple way to mock a server to test your app.

Finally, update yours App.js file to reflect the new Todo component and remove the default JSX from that file.

Turn on the app (inside an incognito window) and take a look at what it looks like now. You should see a list of all things and an input box below them with a button to send. As I said, a very simple user interface.

Once you've set this up, let's discover a way to make this stuff work offline using Workbox.

Note: When testing the functionality of a service operator in a development environment, always make sure to do so in a new incognito window each time. It makes the test and debugging much less headaches because your data is not stored between sessions.

Implement caching with the work box

Now, if you go ahead and open the Chrome toolbar, you should see something that looks like the following on the Application tab.

Google Chrome Developer Toolbar

Check the offline checkbox and try reloading your web page. Probably it will fail with an error in saying that no network connection has been detected. If you look at the network card, you will see a series of failed network requests.

The most obvious that will fail is the request to ours JSON-Server to take the list everyday. Let's solve it first. Open the custom-service-worker.js file and add the following code

workbox.routing.registerRoute (
& # 39; Http: // localhost: 8000 / Todos & # 39 ;,
workbox.strategies.networkFirst ()

This is setting up a caching strategy of networkFirst for any requests made tohttp: // localhost: 8000 / Todos endpoint. The image below gives you a clear explanation of what the networkFirst the strategy implies You always check the network first, and only in case of lack of network you go to the cache to recover the resource. This is a typical strategy that you could use when you run a query on an & # 39; API that is able to provide new data.

First strategy network

Now the app is not yet loaded because there are still two important pieces missing. That is to say, we are not yet caching

  1. The JS bundle that is served by our local development server.
  2. The index.html file

Add the following code to custom-service-worker.js

workbox.routing.registerRoute (
        workbox.strategies.networkFirst (),
workbox.routing.registerRoute (
        'Http: // localhost: 3000 & # 39; ,
        workbox.strategies.networkFirst ()

If you notice, the first path in the code snippet above is a RegEx object. This is a simple and clean way to address multiple paths with the same strategy. However, if you're looking for a resource that does not follow the same policy of origin, be sure to specify the entire path.

This, of course, is not the ideal way of doing things. Ideally, we want static assets like JS bundles, style sheets and HTML files to be pre-cached as part of the Webpack creation process. We will get there, but it is important to understand that there is no ongoing black magic. This is all simply simple caching.

Go ahead and activate the page again and open your console. You should see a bunch of logs from Workbox on routing. Go offline and refresh the page. You should see everything load as normal. If you open the workbox logs in the console, you will see Workbox printing if the network request failed or succeeded and the workbox response to that error (see the following screen):

Workbox access the Dev Chrome tools window

Implementation of deferred posting of data with Workbox

Well, at the next update: how can we send POST data to the server without a network connection?

First, let's set up a way to restore POST data online and make sure it works. Update yours addTodo function inside the Todo component so that it looks like the following:

Everything we did was added to a callback manager a setState so we can be notified when the status is updated. At this point, we made a request POST al JSON-Server to update db.json with the new todo.

Try sending a new Todo, open db.json and you should see the new todo added to your series of objects.

Now, try doing the same thing offline, and you should get a network error for obvious reasons. You'll probably get a statement from the registry that says: Unable to recover.

To solve this, we will use something called backgroundSync, the specification for which you can read here. The way it should work is that every time you make a request to a server for a specific resource (in our case a POST request), if no network is detected, Workbox will store this request in indexedDB and will continue to query the request of a set period of time. When a network connection is detected, the request will be reproduced. If no network connection is established within the predefined time period, the request is discarded.

The backgroundSync API uses something called SyncManager under the hood. You can read about it in the MDN documents here. Unfortunately, as you can see, SyncManager is not on the standard track and Chrome is the only browser that has a fully implemented specification. This means that Chrome is the only browser in which it is guaranteed to work reliably.

We have to add some code to custom-service-worker.js to get backgroundSync information that works for us. Add the following code to the file:

We are using a background synchronization plugin provided by Workbox. The first parameter you provide to the constructor is the name of the queue you want Workbox to create when storing failed requests. The second parameter is an options object, where we are defining the maximum time to attempt to reproduce requests within.

Finally, we register a new route with the POST method and set the strategy we want to use for caching. This is very similar to what we have already done with the exception of defining the type of request that is made and also having a plugin defined for our strategy.

Now try running the same scenario as sending a todo without any network connection and see what happens in the log. You will get a log similar to the following screenshot.

Workbox adds the failed request to a queue

You can look at the request that was added by looking for indexedDB under the application tab in the Chrome DevTools window. Open the subdirectories listed under the IndexedDB drop-down menu, and you should see the stored request, waiting to be played.

Turn off the offline option in the DevTools window and you should see a new Workbox popup almost immediately. It will appear like the following:

Workbox log indicating that the failed request has been reproduced and forwarded

The image above implies that Workbox reproduces the failed request when it receives a synchronization request and gives you the confirmation that your request was successful. If you look db.json now, you will notice that the new todo has been added to the file.

Well, here we are. We have a way to reproduce failed requests through a service operator now.

What we need to do is integrate a Webpack plug-in so that Workbox can store static resources as part of the build process. This will eliminate the need to explicitly provide a path to cache static resources within our Service Worker file.

Preloading of static resources

This will be the final step. In this section, we will make changes to the CRA build process to force it to generate the Service Worker file using Workbox instead of sw-precache.

First, install the following packages: workbox-webpack-plugin is pathway.

Open the package.json file and edit the build script to run react-app-rewired instead of react-scripts the same way we did for the startup script.

Finally, open the config-overrides.js file and modify it to resemble the following:

There are a couple of things we are doing in this file.

First, let's check if it's a production build. If it is, we create a Workbox configuration object and provide it with the path of our custom SW and also the path of the output SW we want.

We also provide a call option importWorkboxFrom and set it up disabled.

This is an option that specifies that we do not want to import Workbox anywhere as we request it directly from a CDN in our SW script.

Finally, we have a function that is called removeSWPrecachePlugin . All this happens in loop on the plugins listed in the Webpack configuration, find the correct one and return the index so that we can remove it.

Now, go ahead and run the app build and open the generated SW file in the build directory. In my case, this SW file has the name custom-service-worker.js

You will notice a new one importScripts calls at the top of the file, which seems to require a precache manifest file. This file is stored in the build folder and, if you open it, you should see a list of all the static resources that are cached by Workbox.


Thus, we have achieved the following objectives:

  1. Configure the CRA build to use react-app-rewired
  2. Use react-app-rewired to customize the build to use Workbox to generate a Service Worker. We have realized using workbox-webpack-plugin. The compilation process now automatically stores all static resources in the cache.
  3. Build a simple app for things to do
  4. Implement offline functionality for the todo app using Workbox.
    The offline functionality that we will target:
    a) Resources retrieved from the cache so that they can be published offline
    b) Allow POSTing of offline data

Here is the link to the repository that has a working version of the app. You can clone it and have fun.

Follow me on Twitter Here. Follow me on GitHub here

Source link

Leave a Reply

Your email address will not be published.