I'm using the newest versions of create-react-app and react-scripts.
For a fully functional offline PWA, I would need to cache network requests to show some data while the user is offline, Most tutorials I saw were listening to the fetch event in the service-worker itself using self.addEventListener, but since CRA uses workbox-plugin to autogenerate the service worker as a developer I don't have access to "self" so I can't freely listen to the fetch event.
As a workaround I did something like this to cache my get requests..
function cachedGet (url, config) {
return Promise.race([
getFromCacheAfter4Seconds(url, config),
getFromNetworkAndSaveToCache(url, config)
])
.catch(error => {
console.error('error', error)
return getFromCacheAfter4Seconds(url, config)
})
}
function getFromCacheAfter4Seconds (url, config) {
const cacheKey = url.split('?')[0]
return new Promise((resolve, reject) => {
let timeout = setTimeout(() => {
clearTimeout(timeout)
const cachedResponse = window.localStorage.getItem(cacheKey)
if (cachedResponse) {
const response = JSON.parse(cachedResponse)
console.log('getting from cache', url)
console.log('cachedResponse', response)
resolve(response)
} else {
console.log('no cached response found falling back to network', url)
getFromNetworkAndSaveToCache(url, config)
.then(resolve)
}
}, 4000)
})
}
function getFromNetworkAndSaveToCache (url, config) {
const cacheKey = url.split('?')[0] // For requests that use a startDate & endDate parameters
return axios.get(url, config)
.then(res => res.data)
.then(data => {
window.localStorage.setItem(cacheKey, JSON.stringify(data))
return data
})
}
or to avoid calling the setTimeout anyway whenever Promise.race() is called I did something like this
function cachedGet (url, config) {
return getFromNetworkAndSaveToCache(url, config)
.catch(error => {
if (!error.status) {
// Network error
return getFromCacheAfter4Seconds(url, config)
} else {
throw error
}
})
}
Is there a better/ more recommended way? perhaps a configuration file I can use to specify cache-first vs offline-first vs whichever is faster? Same thing if I were to use other service worker features like background sync and/or push notifications.
You can do this if you set runtimeCaching option in webpack config, or use InjectManifest plugin instead of GenerateSW which CRA currently uses. Both of those don't seem possible without forking react-scripts or ejecting. I think the optimal solution here is not using CRA's service worker and just using workbox directly. It's always loaded from CDN anyway.
This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in 5 days if no further activity occurs.
@khaledosman You can append an epilog to the auto-generated SW in a build step (without ejecting or forking CRA) and, there, add a message listener which can access SW self object. For example, I use this technique to upgrade SW in https://github.com/emibcn/Rac1.js/blob/master/app/src/sw-epilog.js :
// Add a listener to receive messages from clients
self.addEventListener('message', function(event) {
// Force SW upgrade (activation of new installed SW version)
if ( event.data === 'skipWaiting' ) {
self.skipWaiting();
}
});
And then, from your app https://github.com/emibcn/Rac1.js/blob/master/app/src/index.js :
registration.waiting.postMessage('skipWaiting');
Note: Don't forget to add the epilog to your build process in your package.json. For example https://github.com/emibcn/Rac1.js/blob/master/app/package.json#L27 :
...
"build": "react-scripts build && yarn sw-epilog",
"sw-epilog": "cat src/sw-epilog.js >> build/service-worker.js",
...
@emibcn that's exactly what I needed, thanks!
@khaledosman You can append an epilog to the auto-generated SW in a build step (without ejecting or forking CRA) and, there, add a message listener which can access SW
selfobject. For example, I use this technique to upgrade SW in https://github.com/emibcn/Rac1.js/blob/master/app/src/sw-epilog.js :// Add a listener to receive messages from clients self.addEventListener('message', function(event) { // Force SW upgrade (activation of new installed SW version) if ( event.data === 'skipWaiting' ) { self.skipWaiting(); } });And then, from your app https://github.com/emibcn/Rac1.js/blob/master/app/src/index.js :
registration.waiting.postMessage('skipWaiting');Note: Don't forget to add the epilog to your build process in your
package.json. For example https://github.com/emibcn/Rac1.js/blob/master/app/package.json#L27 :... "build": "react-scripts build && yarn sw-epilog", "sw-epilog": "cat src/sw-epilog.js >> build/service-worker.js", ...
You literally saved me, I have been struggling with this for last 3 days. Thank you so very much. I learned a lot about how little I know ;-)
This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in 5 days if no further activity occurs.
This issue has been automatically closed because it has not had any recent activity. If you have a question or comment, please open a new issue.
Most helpful comment
@khaledosman You can append an epilog to the auto-generated SW in a build step (without ejecting or forking CRA) and, there, add a message listener which can access SW
selfobject. For example, I use this technique to upgrade SW in https://github.com/emibcn/Rac1.js/blob/master/app/src/sw-epilog.js :And then, from your app https://github.com/emibcn/Rac1.js/blob/master/app/src/index.js :
Note: Don't forget to add the epilog to your build process in your
package.json. For example https://github.com/emibcn/Rac1.js/blob/master/app/package.json#L27 :