Alexander Tyapkov
Alexander Tyapkov

Reputation: 5017

PWA app, mobile Chrome, force reload js cached files

I have PWA app in which I am rendering js files using Webpack:

{% render_bundle 'app' 'js' %}

After launching the PWA app in mobile Chrome the file is not updated. Most probably Chrome uses cached version.

I tried to delete PWA app and install it again but it did not help.

Afterwards I have cleared the mobile Chrome cache manually and files were refreshed, however, most of the users won't do it so I need another solution which does not require any actions from end users.

Answers on similar question suggest to add parameter or version number to the js file.

<script type="text/javascript" src="myfile.js?REVISION"></script>

However, it is not clear how can I do it using Webpack?

One more popular answer explains that I can use hash or chunkhash to generate file name using Webpack:

output: {
    path: '/',
    filename: '[hash].js',
    chunkFilename: '[chunkhash].js',
},

This solution won't work for me because I cannot change the name of the file every time when there are some chnages in it. The name of the file should stay the same because I use django's collectfast app. It checks the md5sum of static files and updates only those ones which have been changed.

The name of the static js file should stay the same. At the same time, I need mechanism which will force mobile Chrome to update changed file.

Upvotes: 4

Views: 3959

Answers (3)

w. Patrick Gale
w. Patrick Gale

Reputation: 2307

I was trying to do the same thing with Svelte PWA code here: https://github.com/kuhlaid/svelte2/releases/tag/v0.1.6

I resorted to running the app build process and then using the 'replace-in-file' plugin (see the rollup.config.js script). If you search the source code for '__cVersion__' you will see where I am adding the file revision string to try and force a file cache update (not localstorage).

The OTHER thing that needs to be done for a PWA is making sure we clear the CacheStorage in the users browser if you are building your service working with something like Workbox to precache files within the service worker. This is something you are NOT ABLE to do during the build process since the code to clear the CacheStorage needs to be run at time of accessing the app in the browser. To clear this cache you can insert something along these lines in the javascript of your app:

const l = console.log    
if ('caches' in window) {
  l('CacheStorage is present for this app');
  caches.keys().then(function(cacheArray) {
    l(cacheArray); // just print the list of CacheStorage names to the console
    // for each cache, try and delete it
    cacheArray.forEach(function(cache) {
      caches.delete(cache).then((bool) => {
        l('deleted cache: '+cache); // print the successful deletion to console
      }).catch((err) => {l(err)});
    });
  });
}

This is all well and good, BUT this begs the next question of how you only execute this ONCE for a new code build/update? Well, possibly a 'code version' variable could be added to your javascript somewhere like:

const codeVersion = __cVersion__;

Then during the build/rollup of your code, you dynamically replace __cVersion__ with your new version string (eg. v0.112) and store this codeVersion value to localStorage for the app. Then each time you build you would check localStorage first for a change in the version string, and if it has changed then run the code to delete the CacheStorage for the app (as mentioned above). This version of my PWA code handles these cases: https://github.com/kuhlaid/svelte2/releases/tag/v0.1.7

Automatically clearing the cache for end-users is one of the complexities of PWA (or any app) that should be understood upfront by the developer. No one likes an app where you are told by the developer that YOU need to manually clear your browser cache each time they push a code update.

Upvotes: 1

jales cardoso
jales cardoso

Reputation: 636

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('service-worker.js')
        .then(reg => {
            reg.onupdatefound = () => {
                const installingWorker = reg.installing;
                installingWorker.onstatechange = () => {
                    switch (installingWorker.state) {
                        case 'installed':
                            if (navigator.serviceWorker.controller) {
                                // new update available
                                setTimeout(() => document.location.reload(true), 1000);
                                caches.keys().then(keys => {
                                    keys.forEach(key => caches.delete(key));
                                })
                            }
                            break;
                    }
                };
            };
        })
}

Upvotes: 1

user1544616
user1544616

Reputation:

you could try putting one of these in your html, that would force cache to expire.

<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />

Upvotes: 0

Related Questions