Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service worker: delete index.html from cache in onupdatefound event #7700

Open
dimitar-nikovski opened this issue Sep 19, 2019 · 17 comments
Open

Comments

@dimitar-nikovski
Copy link

dimitar-nikovski commented Sep 19, 2019

Is your proposal related to a problem?

When the service worker in registerServiceWorker.(js|ts ) is enabled it provides very useful PWA benefits.

However, in a production build it also caches the index.html file which references the names & hashes of the outputted JS chunks by the webpack build. When a new build is created, new chunks and hashes are generated, however, the service worker at the client keeps the old index.html and looks for the old chunks, which completely breaks the app as no JS can be obtained.

Describe the solution you'd like

The registration of the service worker in registerServiceWorker.ts already detects new content available from the server and logs a prompt to refresh in the console. In the onupdatefound event here there is an assumption that old content has been purged, when in fact even after closing all tabs and hard reloading I keep seeing this message and when I check the cache in DevTools and the network I see it's coming from the service worker. Other issues can be found here #7121 #4674 #5316 which can't get past this issue as well.

At the event handler as well as the console log of New content is available; please refresh., it would be better if index.html is manually deleted from the cache using the Cache API, the cache key seems to be in the form of workbox-precache-https://mysite.io/.

Describe alternatives you've considered

Turning the service worker off takes away a lot of benefits and PWA functionality, so it's not really an option as proposed in other issues related to this.

Many thanks :)

@ketysek
Copy link

ketysek commented Sep 25, 2019

I'm experiencing same problems last weeks. Service worker cache includes old index.html with references to old assets (js/css).

@dimitar-nikovski
Copy link
Author

dimitar-nikovski commented Sep 26, 2019

Hi @ketysek

What I have temporarily added to my registerServiceWorker.ts is :

const WORKBOX_CACHE_KEY = `workbox-precache-https://${window.location.hostname}/`;

const clearFromCache = async (requests: string[]) => {
  if ('caches' in self) {
    const cache = await caches.open(WORKBOX_CACHE_KEY);
    if (cache) {
      await Promise.all(requests.map(r =>
        cache.delete(r)
      ));

      requests.forEach(r => {
        console.log(`Cache => delete[${r}]`);
      });
    }
  }
}

which I have added to this snippet found here

to produce:

if (navigator.serviceWorker.controller) {
                // At this point, the old content will have been purged and
                // the fresh content will have been added to the cache.
                // It's the perfect time to display a 'New content is
                // available; please refresh.' message in your web app.
                console.log('New content is available; please refresh.');

                clearFromCache(['/index.html']);

              }

However, it runs on every single request for some reason and I am not sure why, since it seems that should only be ran when new content is available.

@ketysek
Copy link

ketysek commented Oct 8, 2019

@dimitar-nikovski I think it's because you clear it from cache but not from service worker ... so with every request service worker ads it again ... and again and so on :-)

@dimitar-nikovski
Copy link
Author

dimitar-nikovski commented Oct 8, 2019

@ketysek actually the cache is where workbox stores all of the files under its own workbox-precache-https://${window.location.hostname}/ namespace.

I manged to get it all sorted by adding process.env.BUILD_TIME = new Date() on every build. I did it in ejected config/.env.js, but it can also be done in the webpack define plugin, again when ejected. The hashes of the chunks change on every build anyway, so what I do is then in the above clearFromCache I check the time the file was cached and then if it's before the build time of the env, I delete it.

@sidati
Copy link

sidati commented Jan 25, 2020

If anyone still having this issue, this may help you if it's OK to eject config scripts (hopefully we can modify workbox config without ejecting in the next CRA versions ) :

        new WorkboxWebpackPlugin.GenerateSW({
          // ...
          runtimeCaching: [
            {
              urlPattern: /index\.html/,
              handler: 'NetworkFirst',
              options: {
                networkTimeoutSeconds: 5
              }
            }
          ]
        }),

more info here :
https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin#generateSW-runtimeCaching

@mexmirror
Copy link

mexmirror commented Mar 30, 2020

Our project is currently affected by this as well. Wouldn't it be a good default implementation if this was enabled by default? Correct me if I am wrong, but when we deploy the application, the bundler creates new bundle names which will break any cached index.html.

@tobias-tengler
Copy link

tobias-tengler commented Jun 5, 2020

This issue should be high priority IMO.
All it takes is for a developer to include serviceWorker.register();, deploying that version, people visiting that version of the site, pushing a new update and now the app is broken for everyone that previously visited that site. Seems like something really bad!
And the worst part: afaik the serviceWorker can't be easily disabled for people that are already affected by this.

@dimitar-nikovski
Copy link
Author

@tobias-tengler agreed about the priority, this has caused me massive pain in production

@KaiMicahMills
Copy link

Soo....?

This issue should be high priority IMO.
All it takes is for a developer to include serviceWorker.register();, deploying that version, people visiting that version of the site, pushing a new update and now the app is broken for everyone that previously visited that site. Seems like something really bad!
And the worst part: afaik the serviceWorker can't be easily disabled for people that are already affected by this.

How has this not been solved yet? It's a major issue.

@BhavyaCodes
Copy link

please fix

@KarahanGuner
Copy link

Are there any updates on this issue? I dont want to eject my application to fix the issue.

@a-tonchev
Copy link

We also made many workarounds. This is really important for a clean solution.

It would be enough to have some function

oldSWCaches.delete('index.html")

Where I am able to call at any place.

@a-tonchev
Copy link

If anyone still having this issue, this may help you if it's OK to eject config scripts (hopefully we can modify workbox config without ejecting in the next CRA versions ) :

        new WorkboxWebpackPlugin.GenerateSW({
          // ...
          runtimeCaching: [
            {
              urlPattern: /index\.html/,
              handler: 'NetworkFirst',
              options: {
                networkTimeoutSeconds: 5
              }
            }
          ]
        }),

more info here :
https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin#generateSW-runtimeCaching

Does this not mean, that when we have active and waiting service worker, the active one may take immediately the html from the waiting one? Will this not cause errors?

@a-tonchev
Copy link

Finally I found a solution, You can chech here at Step 5:

https://dev.to/atonchev/flawless-and-silent-upgrade-of-the-service-worker-2o95

Basically, you need copy the content of the new service worker into the old one.

Cheers!

@caseycesari
Copy link

Would be curious if @jeffposnick has any advice on this. It happens with all of our CRA PWA apps.

@sonjaerock
Copy link

import { clientsClaim } from "workbox-core";
import { precacheAndRoute } from "workbox-precaching";
import { NavigationRoute, registerRoute, Route } from "workbox-routing";
import { NetworkFirst } from "workbox-strategies";

clientsClaim();

precacheAndRoute(self.__WB_MANIFEST);

const navigationRoute = new NavigationRoute(
    new NetworkFirst({
        cacheName: "navigations",
    }),
);

const imageAssetRoute = new Route(
    ({ request }) => {
        return request.destination === "image";
    },
    new NetworkFirst({
        cacheName: "image-assets",
    }),
);

registerRoute(navigationRoute);
registerRoute(imageAssetRoute);

finally i found solution.
this code(service-worker.js) is only image cache.(include font)
i dont know.. why font cached

@niranjan-borawake
Copy link

While all this is being worked upon, I was able to achieve expected results for my use-case. It might not cover all the scenarios but could be helpful for someone.

In service-worker.js get hold of install event and skip waiting. This will skip waiting and activate the new service-worker.

self.addEventListener('install', event => {
  self.skipWaiting();
});

But a reload is required for it to actually get into action. To do that in serviceWorkerRegistration.js add

function registerValidSW(swUrl, config) {
  navigator.serviceWorker
    .register(swUrl)
    .then(registration => {
      registration.onupdatefound = () => {
        const installingWorker = registration.installing;
        if (installingWorker == null) {
          return;
        }
        installingWorker.onstatechange = () => {
          if (installingWorker.state === 'installed') {
            if (navigator.serviceWorker.controller) {
              // At this point, the updated precached content has been fetched,
              // but the previous service worker will still serve the older
              // content until all client tabs are closed.
              console.log(
                'New content is available and will be used when all ' +
                  'tabs for this page are closed. See https://cra.link/PWA.'
              );

              // Execute callback
              if (config && config.onUpdate) {
                config.onUpdate(registration);
              }
            } else {
              // At this point, everything has been precached.
              // It's the perfect time to display a
              // "Content is cached for offline use." message.
              console.log('Content is cached for offline use.');

              // Execute callback
              if (config && config.onSuccess) {
                config.onSuccess(registration);
              }
            }
          }
// ----------- This part is added ---------------
          if (installingWorker.state === 'activated') {
            window.location.reload();
          }
// ----------- This part is added ---------------
        };
      };
    })
    .catch(error => {
      console.error('Error during service worker registration:', error);
    });
}

That's it. No hard reload. No closing of browser tabs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests