[PWA] Service worker app cache duration

I found some old topics about it but none really new, so I allow my self to start a new feed.

Right now I’m in an analysis phase, so I basically build a new app every 5 minutes and deploy it to test and I notice that most of the time my app scripts aren’t refreshed because the service-worker.js cache my main.js

Is it possible to set a max-age for that toolbox precache ?

I ask also myself how to handle this with live production app respectively how could a service-worker cached app notice that a new bundle was deployed on the server?

P.S.: I found the following really useful, in case you use firebase, you could set a cache-control expiracy, just add the following in your firebase.json

"headers": [{
  "source" : "service-worker.js",
  "headers" : [{
    "key" : "Cache-Control",
    "value" : "max-age=0"
  }]
}]
2 Likes

Have you had a look at Angular’s @angular/service-worker module?

See:

From the angular site:

Tip: When testing Angular service workers, it’s a good idea to use an incognito or private window in your browser to ensure the service worker doesn’t end up reading from a previous leftover state, which can cause unexpected behavior.

2 Likes

@Tommertom yep it’s exactly what I do to test my pwa

any tips about the 2nd part of my question respectively what about live production? respectively how to let the app of the users notice there are updates we should replace what’s cached

ps.: I link the current other open conversation on the forum which goes in the same direction Why ionic search build/assets for fonts

No experience from practice on my side…

In dev I would brute-force remove all service workers before registering the new one :slight_smile:

    navigator.serviceWorker.getRegistrations().then(function (registrations) {
        for (let registration of registrations) {
          console.log('registration', registration)
          registration.unregister()
        }
      })

In production I would go for some references on websites (including angular) to notify change by amending a manifest file. (angular’s sw has its own way?)

I believe somewhere else it says that if the service-worker.js changes only slightly (a byte size change), the browser will refresh (after a refresh?). Next, I saw some implementations where the sw has a version for the cache and as such pulls a full refresh.

Hope this helps…

@Tommertom cool yes that helps, maybe that’s the way, having a version number (for example) or hash in service-worker.js in order to refresh the app when a new version would be published…that deserve a try, could be a quick win

Changing the service-worker.js file on each build as suggested by @Tommertom is also what I did.
I run this gulp task after each production build:

const gulp = require('gulp');
const footer = require('gulp-footer');

gulp.task('sw-build-date', () => {
  return gulp.src('www/service-worker.js')
    .pipe(footer("//" + (new Date()).toISOString()))
    .pipe(gulp.dest('www'))
});
1 Like

On another note, I recently built a PWA with stencil. There the sw.js file is created automatically using workbox and has revisions for each file. So the sw.js is changed automatically when there is an update.
However I still had some caching issues and found that it helps if the index.html file also changes on each deploy. So now I also write the build date and time into a comment in index.html before each production build. Not sure if this is a good solution though.

2 Likes

@pwespi cool cool cool

have a couple of things to finish first but I’ll definitely try that, thx a lot @pwespi and @Tommertom

the master himself has published another marble

Unfortunately requiring angular6

2 Likes

Sounds interesting well spotted

About this subject…

  1. I think one think I should do, since I’m using lazy loading is adding all *.js to the self.toolbox.precache of sw-toolbox which may help to detect when a new build is published

  2. But I discovered that actually sw-toolbox and sw-precache are kind of deprecated. Looks like the team behind it have created and are actively support workbox which is even already use in the ionic-pwa-toolkit so no sure if I should not migrate that first because it might solve the preaching and new bundle question automatically (maybe?) https://developers.google.com/web/tools/workbox/modules/workbox-precaching

workbox sound definitely interesting and might be the way to solve the cache problem when updating the app and automatically

workbox-webpack-plugin could be chained to webpack and on each build will generate a new list of the resources to cache :slight_smile:

but unfortunately can’t be used :frowning: I did the migration, everything works fine … till I run a --prod build. workbox-webpack-plugin will generate a precache-hash.js file in the www/build folder containing the list of resource to cache, awesome, but problem is that ionic app-script try to minify all files under www/build and unfortunately fails on minifying that particular file

SOLUTION

workbox-build could be use and chained with gulp (reference: https://developers.google.com/web/tools/workbox/guides/precache-files/workbox-build)

  1. remove sw-toolbox, install workbox and workbox-build

     npm uninstall sw-toolbox --save
     npm install workbox --save
     npm install workbox-build --save-dev
    
  2. modify src/service-worker.js (example taken from the Ionic pwa toolkit https://github.com/ionic-team/ionic-pwa-toolkit/blob/master/src/sw.js)

     'use strict';
     importScripts('build/workbox-sw.js');
     
     self.workbox.skipWaiting();
     self.workbox.clientsClaim();
     
     self.workbox.precaching.precacheAndRoute([]);
    
  3. edit your local copy of copy.config.js from Ionic app-script in order to copy workbox-sw.js instead of sw-toolbox.js to the build dir

      copySwToolbox: {
              src: ['{{ROOT}}/node_modules/workbox-sw/build/workbox-sw.js'],
              dest: '{{BUILD}}'
      }
    
  4. if you don’t use it yet, install gulp and create a gulpfile.js

  5. edit your gulpfile and add a new task

      gulp.task('build-sw', function () {
          return workboxBuild.injectManifest({
              swSrc: 'src/service-worker.js',
              swDest: 'www/service-worker.js',
              globDirectory: 'www',
              globPatterns: [
                     '**\/*.{js,css,html,png,json,eot,svg,ttf,woff,woff2,svg,ico,jpg}',
              ]
          }).then(({count, size, warnings}) => {
              // Optionally, log any warnings and details.
              warnings.forEach(console.warn);
              console.log(`${count} files will be precached, totaling ${size} bytes.`);
          });
      });
    
  6. edit your package.json and create a cmd to chain everything

      "scripts": [
           ...
           "pwa": "ionic-app-scripts build --prod && gulp build-sw"
        ]
    
  7. modify index.html in order to keep the load of the page performant while loading the service-worker (see https://developers.google.com/web/tools/workbox/guides/get-started) replace the default serviceWorker load code in index.html

     <script>
     // Check that service workers are registered
     if ('serviceWorker' in navigator) {
       // Use the window load event to keep the page load performant
       window.addEventListener('load', () => {
             navigator.serviceWorker.register('/service-worker.js')
                 .then(() => console.log('service worker installed'))
                 .catch(err => console.error('Error', err));
       });
     }
     </script>
    

build and enjoy with npm run pwa

1 Like

Extra note: Just did a real test, sounds no that bad

I have built my app and then deploy it to my server
In an incognito browser I accessed my app, load everything and stuffs
In my code I added an alert, build it again and deployed it to the server
In the same browser as before which I didn’t close, I accessed my app again, I didn’t saw the alert. Was skeptical did it again and I saw the alert

For me even if I have to access my app twice, it displayed that the new bundle was successfully loaded, so it’s cool

Hi there, reedrichards What about the problem with the lazy loading pages that are not capable to auto-update when a new update was released. I am struggling with that, and I am feeling that may be a good idea to migrate from sw-toolbox to workbox, with workbox that not happens?. Thx in advance.

@brodriguezs can’t answer if it happens or not. generally speaking, sometimes it may still takes time to update the pages but I’ve the feeling that generally speaking it’s better with workbox than sw-toolbox

with workbox you will generate and deploy a list of hash of your bundled js, so if you use lazy loading it will contains x.js entries identified with hash. if you do a modification, then only the hash of the related js will be updated. this list helps workbox to notice which files have to be updated

...
{
    "url": "build/31.js",
   "revision": "c46081f51218522127856c4cba816d56"
},
{
   "url": "build/32.js",
   "revision": "5c6c59dfcbc226bef6d56afc4f07502e"
}
...

note: if you use Ionic v4, don’t go for workbox, go for the integrated Angular v6 pwa support, faster to add to the build

I am facing the same issue, i always need to double cache clean on browser / mobile to get the newest version of my PWA.

I tried a lot of thing like :

service-worker.js

'use strict';
importScripts('./build/sw-toolbox.js');

self.toolbox.options.cache = {
  name: 'ionic-cache',
  maxEntries: 0,
  maxAgeSeconds: 60
};

// pre-cache our key assets
self.toolbox.precache(
  [
    './build/main.js',
    './build/vendor.js',
    './build/main.css',
    './build/polyfills.js',
    'index.html',
    'manifest.json'
  ]
);

// dynamically cache any other local assets
self.toolbox.router.any('/*', self.toolbox.networkFirst);

// for any other requests go to the network, cache,
// and then only use that cached resource if your user goes offline
self.toolbox.router.default = self.toolbox.networkFirst;

Changing my firebase.json…

firebase.json

{
  "hosting": {
    "public": "platforms/browser/www",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "headers": [
      { "source":"/service-worker.js", "headers": [{"key": "Cache-Control", "value" : "no-cache, no-store, must-revalidate"}] }
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

but i still get the old version of my PWA.

I don’t understand what’s wrong with firebase hosting…

I just started using SwUpdate, part of angular/service-worker.
This will detect a new version of your PWA and let the user know it is ready to download it ( refresh ).
The alert can inform the user of any message you want as well.
This occurs after one load but it seems to take a couple of seconds.

Short usage:
ng add @angular/pwa --project app ( this will do most of the work )

in app.components.ts:

import { SwUpdate } from '@angular/service-worker';
 constructor(
    public updates: SwUpdate, ...

 this.updates.available.subscribe(event => {
      const changelog = event.available.appData['changelog'];
      const message = changelog + " Click to refresh.";
      if (confirm(message)) {
        window.location.reload();
      }
    });

In ngsw-config.json:

"appData": {
    "changelog": "Add your custom message here"
  }