简体   繁体   中英

Progressive Web App and caching UI

I'm developing a PWA with all functionalities (as offline, add to home screen, notification,..) but I have some problem when I try to refresh the UI.

In other words, I have my PWA with the UI defined in the index.html file, the thing that I want to do is caching the PWA for use it offline (and I'm able to do this) and if the device is online check if there are some update of UI (in index.html file, or in the file which it depends), download this changes and refresh the UI on the device and inside the cache.

With the data I haven't any problem with the cache.

For example if I have this page index.html :

<!DOCTYPE html>
<html>
<head >
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title class="title">PWA</title>

  <link rel="manifest" href="manifest.json">

  <meta name="mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="PWA">
  <link rel="apple-touch-icon" href="imgs/icons/Icon-152.png">
  <meta name="msapplication-TileImage" content="imgs/icons/Icon-144.png">
  <meta name="msapplication-TileColor" content="#2F3BA2">
  <link rel="shortcut icon" sizes="32x32" href="imgs/icons/Icon-32.png">
  <link rel="shortcut icon" sizes="196x196" href="imgs/icons/Icon-196.png">
  <link rel="apple-touch-icon-precomposed" href="imgs/icons/Icon-152.png">
  </head>

<body>

<div class="container">
    <div class="row">
        <div class="col-12">
            <img src="imgs/images.png">
        </div>
    </div>
</div>

 <script src="js/jquery.js"></script>
 <script src="js/bootstrap.js"></script>
 <script src="js/app.js"></script>
</body>
</html>

app.js :

  if('serviceWorker' in navigator) {
    navigator.serviceWorker
             .register('service-worker.js')
             .then(function() { console.log('Service Worker Registered'); });
  }

service-worker.js :

var dataCacheName = 'dataCache-v2';
var cacheName = 'cache-v2';
var filesToCache = [
  'index.html',
  'imgs/images.png',
    'src/app.js'
];

self.addEventListener('install', function(e) {
  console.log('[ServiceWorker] Install');
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      console.log('[ServiceWorker] Caching App Shell');
      return cache.addAll(filesToCache);
    })
  );
});

self.addEventListener('activate', function(e) {
  console.log('[ServiceWorker] Activate');
  e.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        console.log('[ServiceWorker] Removing old cache', key);
        if (key !== cacheName && key !== dataCacheName) {
          return caches.delete(key);
        }
      }));
    })
  );
return self.clients.claim();
});

self.addEventListener('fetch', function(e) {
  console.log('[ServiceWorker] Fetch', e.request.url);
  var dataUrl = 'URL-WHERE-FIND-DATA';
  if (e.request.url.indexOf(dataUrl) === 0) {
    e.respondWith(
      fetch(e.request)
        .then(function(response) {
          return caches.open(dataCacheName).then(function(cache) {
            cache.put(e.request.url, response.clone());
            console.log('[ServiceWorker] Fetched&Cached Data');
            return response;
          });
        })
    );
  } else {
    e.respondWith(
      caches.match(e.request).then(function(response) {
        return response || fetch(e.request);
      })
    );
  }
});

Suppose that I replace the images.png with another image but with the same name, how can I show the new one to the user? If I refresh the page the data is get from the network (if it is avaiable) but the image still catch from the cache.

I hope that I have explained my problem well. Thanks a lot to everyone that will help me

UPDATE #1:

I tried to implements the "Cache then network" strategy as Arnelle Balane suggested to me, but the problem is always here, the browser show always the cached images (in the code below I try to update an image called 'demo.jpg'). Probably I'm doing something wrong. This is my code:

service-worker.js :

self.addEventListener('fetch', function(event) {
      event.respondWith(
    caches.open("my-cache").then(function(cache) {
      return fetch(event.request).then(function(response) {
          console.log('Fetch: ' + response.url);
        cache.put(event.request, response.clone());
        return response;
      });
    })
  );
});

app.js :

var networkDataReceived = false;


var networkUpdate = fetch('https://website.com/app/images/demo.jpg').then(function(response) {
  return response.blob();
}).then(function(data) {
  networkDataReceived = true;
  updatePage(data);
});

caches.match('https://website.com/app/images/demo.jpg').then(function(response) {
  if (!response) throw Error("No data");
  return response.blob();
}).then(function(data) {
  if (!networkDataReceived) {
    updatePage(data);
  }
}).catch(function() {
  return networkUpdate;
}).catch(showErrorMessage);


function showErrorMessage(response){
    console.log("Error: " + response);
}

function updatePage(response) {
  var img = document.getElementById('demo');
  var imageUrl = URL.createObjectURL(response);
  img.src = imageUrl;
}

Any new suggestions? Thanks

UPDATE #2:

Now I'm trying to do everything from the beginning. I have copy the service worker from this google's example: https://developers.google.com/web/fundamentals/getting-started/codelabs/your-first-pwapp/ That implements the "Cache then network" strategy. The code of service-worker.js is this:

var dataCacheName = 'dataCache1';
var cacheName = 'cache1';

var filesToCache = [
    '/',
    'index.html',
    'css/main.css',
    'src/app.js'
];

self.addEventListener('install', function(e) {
    console.log('[ServiceWorker] Install');
    e.waitUntil(
        caches.open(cacheName).then(function(cache) {
            console.log('[ServiceWorker] Caching app shell');
            return cache.addAll(filesToCache);
        })
    );
});

self.addEventListener('activate', function(e) {
    console.log('[ServiceWorker] Activate');
    e.waitUntil(
        caches.keys().then(function(keyList) {
            return Promise.all(keyList.map(function(key) {
                if (key !== cacheName && key !== dataCacheName) {
                    console.log('[ServiceWorker] Removing old cache', key);
                    return caches.delete(key);
                }
            }));
        })
    );
    /*
     * Fixes a corner case in which the app wasn't returning the latest data.
     * You can reproduce the corner case by commenting out the line below and
     * then doing the following steps: 1) load app for first time so that the
     * initial New York City data is shown 2) press the refresh button on the
     * app 3) go offline 4) reload the app. You expect to see the newer NYC
     * data, but you actually see the initial data. This happens because the
     * service worker is not yet activated. The code below essentially lets
     * you activate the service worker faster.
     */
    return self.clients.claim();
});

self.addEventListener('fetch', function(e) {
    console.log('[Service Worker] Fetch', e.request.url);
    var dataUrl = 'https:/mywebsite.it/service/images/demo.jpg';
    if (e.request.url.indexOf(dataUrl) > -1) {
        /*
         * When the request URL contains dataUrl, the app is asking for fresh
         * weather data. In this case, the service worker always goes to the
         * network and then caches the response. This is called the "Cache then
         * network" strategy:
         * https://jakearchibald.com/2014/offline-cookbook/#cache-then-network
         */
        e.respondWith(
            caches.open(dataCacheName).then(function(cache) {
                return fetch(e.request).then(function(response){
                    cache.put(e.request.url, response.clone());
                    return response;
                });
            })
        );
    } else {
        /*
         * The app is asking for app shell files. In this scenario the app uses the
         * "Cache, falling back to the network" offline strategy:
         * https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network
         */
        e.respondWith(
            caches.match(e.request).then(function(response) {
                return response || fetch(e.request);
            })
        );
    }
});

And the result is always the same, the image won't to get update. With the fetch(url) method, the service worker should get the image from the network, right? The result of the code is shown in the image below.

When I reload the page the only fetched request is the folder “/service/” 主要要求

If I try to “force” the loading of the image that I want to update with an explicit request to browser ( https://mywebsite.com/service/images/demo.jpg ) the service worker correclty fetch the request but shown always the old image. 强制要求 I think that I'm doing something foolishly wrong but I not understand what.

The reason why the image is being served from the cache is because that's what the service worker is coded to do:

e.respondWith(
  caches.match(e.request).then(function(response) {
    return response || fetch(e.request);
  })
);

This checks your cache first if a response for the request is already stored, and only fetches the resource from the network if not.

As I see it, you have two possible ways of dealing with that:

  1. Make sure that the file names of the resources (including images) change when the actual content of that resource changes. There are several build tools out there that can do this for you by appending the resource's hash to its file name. A change in the resource's contents will result into a different hash, and thus a different file name.

  2. You can use the Cache then network strategy as described in this article by Jake Archibald. The idea is that you serve the cached resource if it is available, while at the same time you request for that resource over the network. Once the network request completes, you replace the previously-served content with the one you got from the network, as well as update the cached version of the resource. This way, you can be sure that the user is seeing the latest version of a resource, while still not breaking the offline experience by having an updated cached version of that resource.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM