pwa
pwa copied to clipboard
Service Worker not busting cache.
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.
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. (https://github.com/vuejs-templates/pwa/issues/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.
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
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?
I also disabled cache in nginx, any thoughts?
Any updates? Cannot update the page hosted on Github Pages.
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)
},
})
}
this is a huge pain, how does one update the content or at least ask the user to update content?
@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)
the problem is reloading doesn't work too. the problem is with cache burst
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.
I used vue-cli and chose pwa support. I thought it'll all be managed.
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.
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',
},
},
…
Thank you so much!
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 ?
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.
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)
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 :)
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.
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]