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 not busting cache. #177

Open
trusktr opened this issue Apr 18, 2018 · 21 comments
Open

Service Worker not busting cache. #177

trusktr opened this issue Apr 18, 2018 · 21 comments

Comments

@trusktr
Copy link

trusktr commented Apr 18, 2018

Hello, I've got a weird issue.

In devtools, I have cache disabled.

When I hit ctrl+r, it still lads a cached old file with an error.

When I hit shift+ctrl+r, no problem, and the new file is used and error is gone.

Despite Chrome devtools having cache disabled, that has no effect. Only pressing shift+ctrl+r loads the non-cached version, while pressing ctrl+r always loads the older cached file.

Seems like this has to do with service worker. How do I ensure that caching works properly with the service worker, to eliminate that as possible source of the problem?

Note, I've loaded the production build with a static server a couple times to test it, not sure if that's somehow messing up with the service worker while I'm in dev mode.

I'm experiencing this in dev mode.

@jacebot
Copy link

jacebot commented Apr 24, 2018

I too can confirm this.

I noticed there is another ticket open to move to the webpack offline plugin preload, and I would like to promote that. (#113)

I also use offline-plugin for webpack in another app that uses a similar set up with no issues. Perhaps we can take a look at what they are doing different?

Currently my app will not update but uses cached files instead unless I hard clear the browser with dev tools open.

This occurs in live production, harder to reproduce or tell in dev since its hot reloaded.

@jeffposnick
Copy link
Collaborator

Are you serving your service-worker.js file with HTTP caching enabled? If so, this could lead to delayed updates of cached resources for up to 24 hours (in Chrome):

https://stackoverflow.com/questions/38843970/service-worker-javascript-update-frequency-every-24-hours/38854905#38854905

@kgrosvenor
Copy link

I get same in Dev mode also, would be good if it could work like productio does, can we not just check for localhost else activate worker?

@kgrosvenor
Copy link

I also disabled cache in nginx, any thoughts?

@Andrey-Pavlov
Copy link

Any updates? Cannot update the page hosted on Github Pages.

@u12206050
Copy link

This works for me, within registerServiceWorker.js

in the updated() function add:

      setTimeout(() => {
        window.location.reload(true)
      }, 1000)

Full result

/* eslint-disable no-console */

import { register } from 'register-service-worker'

if (process.env.NODE_ENV === 'production') {
  register(`${process.env.BASE_URL}service-worker.js`, {
    ready() {
      console.log(
        'App is being served from cache by a service worker.\n'
        + 'For more details, visit https://goo.gl/AFskqB',
      )
    },
    cached() {
      console.log('Content has been cached for offline use.')
    },
    updatefound() {
      console.log('New content is downloading.')
    },
    updated() {
      console.log('New content is available; please refresh.')

      setTimeout(() => {
        window.location.reload(true)
      }, 1000)
    },
    offline() {
      console.log('No internet connection found. App is running in offline mode.')
    },
    error(error) {
      console.error('Error during service worker registration:', error)
    },
  })
}

@xerosanyam
Copy link

this is a huge pain, how does one update the content or at least ask the user to update content?

@u12206050
Copy link

@xerosanyam here is a crude way:
Create a single javascript file that you exclude from cache and then check for that file say every 10seconds from within your app somewhere. Once the file is not found (due to the suffix of it changing, you can force reload the entire page using window.location.reload(true)

@xerosanyam
Copy link

the problem is reloading doesn't work too. the problem is with cache burst

@u12206050
Copy link

Are you using a service worker and manifest file correctly? Make sure your manifest file doesn't get cache. Once it changes the service worker will update all the new files in the background and then you can again reload once the updated() function is called.

@xerosanyam
Copy link

I used vue-cli and chose pwa support. I thought it'll all be managed.

@u12206050
Copy link

No only the bundling of the service-worker and manifest file is managed. Cache busting isn't hence this issue.

Here is my setup that works:

Update these two files as follows

service-worker.js

// service-worker.js

workbox.core.setCacheNameDetails({ prefix: 'd4' })

const LATEST_VERSION = 'v1.8' //Change this value every time before you build

self.addEventListener('activate', (event) => {
  console.log(`%c ${LATEST_VERSION} `, 'background: #ddd; color: #0000ff')
  if (caches) {
    caches.keys().then((arr) => {
      arr.forEach((key) => {
        if (key.indexOf('d4-precache') < -1) {
          caches.delete(key).then(() => console.log(`%c Cleared ${key}`, 'background: #333; color: #ff0000'))
        } else {
          caches.open(key).then((cache) => {
            cache.match('version').then((res) => {
              if (!res) {
                cache.put('version', new Response(LATEST_VERSION, { status: 200, statusText: LATEST_VERSION }))
              } else if (res.statusText !== LATEST_VERSION) {
                caches.delete(key).then(() => console.log(`%c Cleared Cache ${LATEST_VERSION}`, 'background: #333; color: #ff0000'))
              } else console.log(`%c Great you have the latest version ${LATEST_VERSION}`, 'background: #333; color: #00ff00')
            })
          })
        }
      })
    })
  }
})

workbox.skipWaiting()
workbox.clientsClaim()

self.__precacheManifest = [].concat(self.__precacheManifest || [])
workbox.precaching.suppressWarnings()
workbox.precaching.precacheAndRoute(self.__precacheManifest, {})

And in your regis
src/registerServiceWorker.js

/* eslint-disable no-console */

import { register } from 'register-service-worker'

if (process.env.NODE_ENV === 'production') {
  register(`${process.env.BASE_URL}service-worker.js`, {
    ready () {
      console.log('Site is ready')
    },
    cached () {
      console.log('Content has been cached for offline use.')
    },
    updatefound () {
      console.log('New content is downloading.')
    },
    updated () {
      console.log('New content is available; Refresh...')
      setTimeout(() => {
        window.location.reload(true)
      }, 1000)
    },
    offline () {
      console.log('No internet connection found. App is running in offline mode.')
    },
    error (error) {
      console.error('Error during service worker registration:', error)
    },
  })
}

Now every time you make build make sure to update the version within the service-worker.js As long as it is different from what users already have installed, it will trigger a full cache bust before installing the new version and reloading automatically.

@u12206050
Copy link

Sorry one more file. In you vue.config.js

Update the options or add it if it is't used already the following:

module.exports = {
  pwa: {
    themeColor: manifestJSON.theme_color,
    name: manifestJSON.short_name,
    msTileColor: manifestJSON.background_color,
    appleMobileWebAppCapable: 'yes',
    appleMobileWebAppStatusBarStyle: 'black',
    workboxPluginMode: 'InjectManifest',
    workboxOptions: {
      swSrc: 'service-worker.js',
    },
  },
…

@xerosanyam
Copy link

Thank you so much!

@xerosanyam
Copy link

My service-worker file looks like this.
dist/service-worker.js

/**
 * Welcome to your Workbox-powered service worker!
 *
 * You'll need to register this file in your web app and you should
 * disable HTTP caching for this file too.
 * See https://goo.gl/nhQhGp
 *
 * The rest of the code is auto-generated. Please don't update this file
 * directly; instead, make changes to your Workbox build configuration
 * and re-run your build process.
 * See https://goo.gl/2aRDsh
 */

importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js");

importScripts(
  "/precache-manifest.65dad24bd5ebee9890500122a011e661.js"
);

workbox.core.setCacheNameDetails({prefix: "dashboard-sense-smart"});

/**
 * The workboxSW.precacheAndRoute() method efficiently caches and responds to
 * requests for URLs in the manifest.
 * See https://goo.gl/S9QRab
 */
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});

It says that I shouldn't change this file directly, so for updating version, is there any better way ?

@u12206050
Copy link

That is because it currently gets generated on every build. You need to update the vue.config.js file as I mentioned above, with

  pwa: {
    workboxPluginMode: 'InjectManifest',
    workboxOptions: {
      swSrc: 'service-worker.js',
    },
  }

This tells the pwa plugin that you supply your own service-worker.

@floydback
Copy link

I've create a test project on production to solve same problem. So now it's work!

vue.config.js is very simple, just add skipWaiting

module.exports = {
  pwa: {
    name: 'My App',
    themeColor: '#4DBA87',
    msTileColor: '#000000',
    appleMobileWebAppCapable: 'yes',
    appleMobileWebAppStatusBarStyle: 'black',

    // configure the workbox plugin
    workboxPluginMode: 'GenerateSW',
    workboxOptions: {
      // cleanupOutdatedCaches: true,
      skipWaiting: true
    }
  }
}

Add refresh link to registerServiceWorker.js

/* eslint-disable no-console */
import { register } from 'register-service-worker'

if (process.env.NODE_ENV === 'production') {
  register(`${process.env.BASE_URL}service-worker.js`, {
    ready () {
      console.log(
        'App is being served from cache by a service worker.\n' +
        'For more details, visit https://goo.gl/AFskqB'
      )
    },
    registered () {
      console.log('Service worker has been registered.')
    },
    cached () {
      console.log('Content has been cached for offline use.')
    },
    updatefound () {
      console.log('New content is downloading.')
    },
    updated () {
      document.getElementById('refresh').innerHTML = 'New content is available; please <a onclick="document.location.reload(true)">refresh to update</a>'
      console.log('New content is available; please refresh.')
    },
    offline () {
      console.log('No internet connection found. App is running in offline mode.')
    },
    error (error) {
      console.error('Error during service worker registration:', error)
    }
  })
}

and simplest nginx.conf without any cache headers

server {
    server_name domain.com;
    include /etc/nginx/snippets/ssl_http2.conf;
    ssl_certificate /etc/ssl/domain.crt;
    ssl_certificate_key /etc/ssl/domain.key;

    access_log off;
    error_log off;
    root /var/www/pwa_test;
    index index.html;

    location / {
        try_files $uri $uri/ $uri.html /index.html;
    }

    location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
        expires max;
        add_header Pragma public;
        add_header Cache-Control "public, must-revalidate, proxy-revalidate";
    }
}

I'm not sure, but it seems I already try something like that a few months ago and it did not work. It works now, which means problem probably was gone with a new package version (I tested @vue/cli-plugin-pwa ":" ^ 4.0.5)

@davidecaruso
Copy link

This is a great solution and it works properly. I've found an alternative without the need of a human interaction:

/* tslint:disable:no-console */

import { register } from 'register-service-worker';

if (process.env.NODE_ENV === 'production') {
  register(`${process.env.BASE_URL}service-worker.js`, {
    ready() {
      console.log(
        'App is being served from cache by a service worker.\n' +
        'For more details, visit https://goo.gl/AFskqB',
      );
    },
    registered() {
      console.log('Service worker has been registered.');
    },
    cached() {
      console.log('Content has been cached for offline use.');
    },
    updatefound() {
      console.log('New content is downloading.');
    },
    updated() {
      console.log('New content is available; please refresh.');
  
      const script = document.createElement('script');
      script.setAttribute('type', 'text/javascript');
      script.innerHTML = 'window.location.reload(true);';
  
      const meta = document.createElement('meta');
      meta.setAttribute('http-equiv', 'refresh');
      meta.setAttribute('content', '0;');
  
      const noscript = document.createElement('noscript');
      noscript.appendChild(meta);
  
      const body = document.getElementsByTagName('body')[0];
      body.appendChild(script);
      body.appendChild(noscript);
    },
    offline() {
      console.log('No internet connection found. App is running in offline mode.');
    },
    error(error) {
      console.error('Error during service worker registration:', error);
    },
  });
}

I hope help you :)

@u12206050
Copy link

VUE PWA Cache busting HOW TO

@MoWafa
Copy link

MoWafa commented Jun 16, 2020

This works for me.
Update the following files. And you can add a versioning mechanizm that shows a message if a new version is being downloaded while the timeout is running.

registerServiceWorker.js

    updated() {
      console.log('New content is available; please refresh.')
      setTimeout(() => {
        window.location.reload(true)
      }, 3000)
    },

vue.config.js

module.exports = {
  pwa: {
    ....
    workboxPluginMode: 'GenerateSW',
    workboxOptions: {
      skipWaiting: true
    }
  },
}

Let me know if it works for you. Or if you have any comments or concerns!
Good luck.

@mrfrase3
Copy link

Just thought I might throw in my two cents for tying it to the package.json version

service-worker.js

// Do not touch this line
const LATEST_VERSION = '3.1.0';

bust-cache.js

const fs = require('fs');
const { version } = require('./package.json');

let swFile = fs.readFileSync('./service-worker.js', 'utf-8');
swFile = swFile.replace(/LATEST_VERSION = '[^']+';/, `LATEST_VERSION = '${version}';`);
fs.writeFileSync('./service-worker.js', swFile);

package.json

  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "version": "node bust-cache.js && git add package.json service-worker.js"
  },

Then just run npm version [major|minor|patch]

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

No branches or pull requests