BLOG

How to make your Parcel.js app progressive

In this article I will provide information how I configured my Parcel.js bundled app to be PWA-enabled

There is not so much information about Parcel.js PWA support. So currently seems like it does not support PWA out-of-the-box. It was quite good challenge and finally I could make working setup. Below is a step-by-step guide.


Initial project structure

In article I use a simple React.js application, basically a generated code from create-react-app and with replaced webpack (contained in react-scripts) by Parcel.js. But steps are not directly related to any framework and can be used in any project.

./
├── LICENSE
├── package.json
├── README.md
└── src
       ├── App.css
       ├── App.js
       ├── index.css
       ├── index.html
       ├── index.js
       ├── logo.svg
       └── public
              └── ... static assets like images here

And package.json content will have only react dependencies and parcel run scripts:

// package.json
{
  "name": "parceljs-pwa",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "https://github.com/jtumano/parceljs-pwa.git",
  "author": "Jan Tumanov",
  "license": "MIT",
  "scripts": {
    "start": "parcel ./src/index.html",
    "build": "parcel --experimental-scope-hoisting ./src/index.html"
  },
  "dependencies": {
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  },
  "devDependencies": {
    "parcel-bundler": "^1.12.4"
  }
}

Adding manifest and icons

First step is to add manifest and icons.

NB! It should not be named manifest.json because parcel will try to process and minify it which will not work in the end. Correct name should be manifest.webmanifest!

As my structure have source files in src directory then I'll add file named manifest.webmanifest there with following content:

// src/manifest.webmanifest
{
  "short_name": "Parcel PWA example",
  "name": "Parcel PWA example",
  "icons": [
    {
      "src": "public/favicon.ico",
      "type": "image/x-icon",
      "sizes": "64x64 32x32 24x24 16x16"
    },
    {
      "src": "public/logo192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "public/logo512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}

Icons should be located in '/src/public' directory.


Add apple-touch-icon

For apple devices you should provide a link to icon which will be shown in desktop. Simply add this line to <head> section in index.html:

<link rel="apple-touch-icon" href="/public/logo192.png" />

Now head should look like:

<!-- src/index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="/public/favicon.ico" />
    <link rel="apple-touch-icon" href="/public/logo192.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <link rel="manifest" href="/manifest.webmanifest" />
    <title>Parcel PWA example</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script src="./index.js"></script>
  </body>
</html>

Add service worker

This is the toughest part. I had to search a lot and finally found that I have to use workbox directly according to Google recommendations (https://developers.google.com/web/tools/workbox/modules/workbox-cli)

Some steps to make service worker work:

  • add workbox-cli dependency:
yarn add -D workbox-cli
  • create workbox-config.js file in project root with following content:
// workbox-config.js
module.exports = {
  globDirectory: "dist",
  globPatterns: [
    "**/*.{html,js,css,png,svg,jpg,gif,json,woff,woff2,eot,ico,webmanifest,map}"
  ],
  swDest: "dist/service-worker.js",
  clientsClaim: true,
  skipWaiting: true
};
  • add postbuild npm hook for service-worker.js file generation in package.json:
{
...
  "scripts": {
    "build": "parcel --experimental-scope-hoisting ./src/index.html",
    "postbuild": "workbox generateSW"
  },
...
}
  • register your service-worker. To simplify I used a piece of code from create-react-app generated serviceWorker.js file directly in index.html but feel free to modify it and experiment:
<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  <script src="./index.js"></script>
  <script>
    const sw = "service-worker.js"; // it is needed because parcel will not recognize this as a file and not precess in its manner

    navigator.serviceWorker
      .register(sw)
      .then(registration => {
        registration.onupdatefound = () => {
          const installingWorker = registration.installing;
          if (installingWorker == null) {
            return;
          }
          installingWorker.onstatechange = () => {
            if (installingWorker.state === "installed") {
              if (navigator.serviceWorker.controller) {
                console.log(
                  "New content is available and will be used when all " +
                    "tabs for this page are closed. See https://bit.ly/CRA-PWA."
                );
              } else {
                console.log("Content is cached for offline use.");
              }
            }
          };
        };
      })
      .catch(error => {
        console.error("Error during service worker registration:", error);
      });
  </script>
</body>

After these operations you should have PWA-enabled application.

Now you can try to publish your generated dist and check. I prefer free static site hosting https://www.netlify.com/ Try to load page, disconnect internet (in browser or physically) and refresh page.

Complete source code of example can be found here: https://github.com/jtumano/parceljs-pwa

Also I'd recommend to check workbox documentation, where can find a lot of service worker configurations especially for your needs: https://developers.google.com/web/tools/workbox/modules/workbox-cli