index.html cache issue
See original GitHub issueLibrary Affected: workbox-webpack-plugin@4.2.0 with workbox-build@4.3.1
Browser & Platform: all browsers
Issue or Feature Request Description: Hi there, we’ve got service worker enabled for caching purposes and we also have notification bar in app which informs user about its new version. This notification bar also includes button to update app by click 😃
registerServiceWorker.js of CRA framework with little modifications has following code:
const isLocalhost = Boolean(
window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
window.location.hostname === "[::1]" ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
)
export default function register(config) {
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location)
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return
}
window.addEventListener("load", () => {
let isInitialServiceWorker = false
if (!navigator.serviceWorker.controller) {
isInitialServiceWorker = true
}
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config)
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl, config)
}
navigator.serviceWorker.addEventListener("controllerchange", () => {
if (navigator.serviceWorker.controller) {
if (!isInitialServiceWorker) {
window.location.reload()
}
}
})
})
}
}
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 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.")
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.")
}
}
}
}
if (registration.waiting === null) {
setInterval(() => registration.update(), 3600000)
} else {
// this needs to be done because of firefox, is it doesn't have
// serviceWorker in installing state before app load
const waitingWorker = registration.waiting
if (waitingWorker.state === "installed") {
if (navigator.serviceWorker.controller) {
console.log("New content is available; please refresh.")
if (config && config.onUpdate) {
config.onUpdate(registration)
}
}
}
}
})
.catch(error => {
console.error("Error during service worker registration:", error)
})
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get("content-type").indexOf("javascript") === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload()
})
})
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config)
}
})
.catch(() => {
console.log("No internet connection found. App is running in offline mode.")
})
}
and I append this to resulted serviceWorker.js file for “update” app functionality
self.addEventListener('message', function(event) {
// Force SW upgrade (activation of new installed SW version)
if ( event.data === 'skipWaiting' ) {
/* eslint-disable-next-line no-restricted-globals */
self.skipWaiting();
}
});
From time to time, users are reporting app unavailability with following error in console:
SyntaxError: expected expression, got '<'
The whole problem is that sometimes index.html
file is not replaced with the newest one, so it points to not-existing css/js files. How can it be possible?
Issue Analytics
- State:
- Created 4 years ago
- Reactions:7
- Comments:14 (2 by maintainers)
Top GitHub Comments
@jackdbd, this has always been one of the more difficult things to explain in our docs, but
workbox-build
does support that use case out of the box, via thetemplatedURLs
option.You provide an object whose keys are the “real” URLs that should be used as a cache key, and whose values are an array of one or more
glob
patterns. Anything that matches theglob
patterns will be used to generate a composite hash for the corresponding URL. This allows you to specify multiple files, updates to any of which are sufficient to cause the precached entry for the “real” URL to be updated.So in your case, you might have:
You can also do this “by hand” via
additionalManifestEntries
or via amanifestTransforms
callback, but that’s the “built-in” way of doing it.@blephy what it will solve? The revision number was different than the previous one. Also I’m using create-react-app, can’t inject webpack.hash in index.html now.