How to build a PWA in pure Javascript, a compilation of insights and pratices

This an attempt in the same spirit as Morris Vanilla To Do to demonstrate how you can develop a PWA with only HTML, CSS and pure Javascript.

I have aggregated all the knowledge and edge cases I’ve experienced through many PWA development and in production

Want a demo? Go over her here: demo.purejs-pwa.alethgueguen.com
The Github repo: github.com/planeth44/pure-JS-PWA

The constraints

Technical choices

What the app does

One use case that PWAs are good for, is line of business mobile app.
Think field worker needing to fill a report. The conditions of use are often offline.
You could imagine the “form factor” as a tiny back-office for one.
This means that the app does mainly CRUD operations plus a significative one : Syncing to a central server.

Things

This PWA create a “Thing” (think: a report, some data entries). You can attach files to this “Thing”.

Files

HTML forms

Data binding

For some previous PWA in production I experimented with as standalone library : data-tier.
It was "kind of" great in that the concept and the docs resonated with what I had in mind.
But in use it turned out to be too much for the kind of app I make. The library weight [] and it’s quite slow on the initialization phase.
I think that the problem is that the library caters to too many use case that don’t apply to what I do.

Thinking about that, I realized that form controls give you a lot of built-in events. That would be a good start to implement a rustic data-binding.
I went for the change event. It is fired after the value has changed [MDN]

As the Thing could need an update, I wanted to have a way to update the form controls from a “Thing” object when the page is loaded.
This is done by looping through all the controls.
I wrote a formControlUpdater with one method per form control type.
This provide the other side of a two way data-binding. Very rustic but does only what I need and not more.

alternate method

Form validation

Syncing and offline

You don’t find a lot of well documented examples of uses cases for real life/ in-production apps. The code in this repo is the result of all the experiments I ran, and has been battle tested in prod.

Updating a “Thing”

This part is dependent on the server, because the server is the source of confidence as the server cannot call the client but the inverse is true.

possible scenario

Back-end

The repo has a very rustic back-end server to just respond to api calls(sync)

Offline Multi Page, how you do that?

Since the service worker will intercept all GET request, all you have to do is to generate all the pages you need. It’s the same —in fact— as all the resources as .css, .js and images.
1/ First, make your back-end serve static pages.
In symfony this how you do it:


$routes->add('edit', '/edit')
    ->controller(TemplateController::class)
    ->defaults(
        [
        'template'  => 'pages/edit.html.twig',
        'maxAge'    => 86400,
        'sharedAge' => 86400,
        'private' => true,
        'context' => [
            'title' => 'Add “a Thing” to indexedDB or edit it',
            ]
        ]
    );

2/ When installing ask your service worker to cache these pages.
Since I’m using the workbox library to generate the list —it can get quite long— this is how the workbox-config.js looks like:

module.exports = {
  // ... more config

  "templatedURLs":{
  // ... more page
    '/edit': [
      '../templates/pages/edit.html.twig',
      '../templates/base.html.twig',
    ],
  },

  // ... more config
};

3/ now, you want to add this line to your service worker:
when you’ll run the workbox-cli, this is where it gonna insert all your resources

workbox.precaching.precacheAndRoute(self.__WB_MANIFEST)

4/ now run the workbox-cli

workbox injectManifest <path/to/your/workbox-config.js>

5/ then watch in dev tool as the new service worker is installing that your pages are being cached.

Dependencies