We built the HTML and JavaScript -- and even CSS though you can't see it -- using Webpack. Webpack packages and bundles your Web assets to make them load faster and take up less room in cache. Go back to your editor and look at the webpack.config.js file. This file describes to Webpack how to build the bundles.

The client function at the beginning produces a Webpack configuration that builds the client-side bundles. It creates a bundle for every html and jsx pair that it finds. We currently only have index.

The configuration is broken into three sections. The Input section tells Webpack where to find the source jsx files, and what extensions to resolve. The Processing section tells it how to handle files of each extension: jsx or scss. It also sets up a the HTML, Workbox, and file copy plugins, which we will look at more closely. Finally, the Output section tells Webpack how to compose the bundles and where to put them.

This project uses Webpack to bundle the server-side code as well, which is handy for taking full advantage of modern JavaScript modules. That configuration appears after the call to client in module.exports.

HTML Plugin

Now let's focus on the HTML plugin. This takes the html file and the chunk that Webpack produced from the jsx. A chunk is a bundle of JavaScript and CSS reachable from a given entry point. We told Webpack to use the jsx file as the entry point, so any file that was imported by that file ended up in the chunk.

The html file is just a simple template for our PWA. It has a few interesting things in it, like a theme color, a link to the manifest, and some touch icons for iOS. These are all required (or at least strongly encouraged) for a PWA.

<meta name="theme-color" content="#3367D6"/>
<link rel="manifest" href="/manifest.json">
<link rel="apple-touch-icon" sizes="57x57"   href="/images/logo.png?width=57" >

Then we have a place to put the React application. This snippet has a fallback for browsers that have JavaScript disabled. This is also a requirement for a PWA.

<div id="application-host">
    JavaScript is required to run this application.

But notice what this file does not have. It doesn't have a <script> tag to bring in the application. That is what HtmlWebpackPlugin does. It just finds the bottom of the <body> tag, and inserts a <script> tag for the chunk that Webpack generates.

Webpack will include the hash in the name of the chunk so that JavaScript can be aggressively cached. That hash will change every time we modify the code. So if the browser gets a cached version of the file, it knows that the code has not changed. You can see the generated HTML file in the browser.

HTML file generated by the HTML Webpack Plugin containing the generated hash

Workbox Plugin

While the HTML plugin generates an HTML file for the foreground sandbox, the Workbox plugin generates a Service Worker file for the background sandbox. It serves precisely the same purpose. The chunk files that need to be cached will include revision hashes on each build. This plugin puts those file names into the manifest that we looked at before.

There are a couple of nuances that we should be aware of. If you look back at webpack.config.js, you will see that we told the plugin to output the file to the scripts folder. However, we cannot serve it from the scripts folder. A service worker will only intercept requests in its own folder and lower. So if we served it from http://localhost:8080/scripts/service-worker.js, then the service worker could not serve up the index.html file at the root.

For this reason, you will find a route in routes.js that serves this one file from the root folder. All other script files are served from scripts.

app.use('/scripts', express.static(path.join(__dirname, 'scripts')));

app.get('/service-worker.js', (req, res) => {
  res.sendFile(path.join(__dirname, './scripts/service-worker.js'));

The next nuance to be aware of is that Workbox is smart enough to know that the default file is probably called index.html. It will intercept a request for http://localhost:8080/ and serve it from the cache as if it was looking for http://localhost:8080/index.html. While that's cool and all, that also means that it will request index.html by name while populating the cache. So our application has to serve the file from either /, or /index.html.

You can see how we configured this in routes.js.

app.get(/^\/(index.html)?$/, (req, res) => {
  res.sendFile(path.join(__dirname, './index.html'));

With these details in place, Workbox can cache and serve all of the assets required to run the application. Now the browser just needs to know that it is dealing with a PWA, which it learns by loading a manifest.

Continue With