I want to turn my normal website into a progressive web app. For that everything is finished. The Website is responsible and I already set up a manifest. Now the last part comes to me. The ServiceWorker to save the website to the cache of the device. My problem is that I can not find a simple solution for that. Because for my website it is important that the user runs the new version every time I made an update. Do you know a solution how let the ServiceWorker check if anything changed once the user opens the app and after that delete the old cache and make a new one based on the new files and the new code?
It would be very great, when somebody could provide a code example.
My HTML SIDE:
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js')
.then(reg => console.log(reg))
.catch(err => console.log(err));
}
</script>
AND HERE THE sw.js:
self.addEventListener('install', event => event.waitUntil(
caches.open('web_pwa').then(cache => cache.add('/'))
));
self.addEventListener('fetch', event => event.respondWith(
caches.open('web_pwa')
.then(cache => cache.match(event.request))
.then(response => response || fetch(event.request))
));
Short description: Update SW file a bit and handle removing old cache in activate event.
Detailed description
Whenever you do changes in your code files (other than sw.js). You need to trigger both the install and activate events in service worker. In order to update your changes.
In order to trigger these events the sw.js should be updated even a single bit. (simply atleast a character).
By convention developers handle this by maintaining Versioning. Also it will be easy to add and delete caches.
const version = 'v2';
self.addEventListener('install', res => {
caches.open(version).then(cache => {
cache.addAll(caches);
});
console.log('[sw] Installed successfully :');
// self.skipWaiting();
});
self.addEventListener('fetch', event => event.respondWith(
caches.open(version)
.then(cache => cache.match(event.request))
.then(response => response || fetch(event.request))
));
If you need to directly update the sw.js inspite of user's status then uncomment the self.skipWaiting() as it skips waiting for activation and gets activated immediately.
Inside activate event is where you have to delete the old caches based on cache version.
self.addEventListener('activate', event => {
caches.keys().then(function(cacheNames) {
cacheNames.forEach(function(cacheName) {
if (cacheName !== version) {
caches.delete(cacheName);
}
});
});
}
Related
Essentially I'm wondering what to change in my sw.js file so that if I add a new HTML element, when the user opens up the PWA it takes a moment to update and refresh itself and display the new content as well?
Currently when I'm connected to the Wifi, the PWA does not make any changes, and when I disconnect my Wifi, it only caches my HTML file and gets rid of my CSS. So I guess my follow up question is how do I make the other pages cache as well so that they're available offline? (My app is only 200kb).
Here is my sw.js code:
// On install - caching the application shell
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('sw-cache').then(function(cache) {
// cache any static files that make up the application shell
return cache.add('index.html');
})
);
});
// On network request
self.addEventListener('fetch', function(event) {
event.respondWith(
// Try the cache
caches.match(event.request).then(function(response) {
//If response found return it, else fetch again
return response || fetch(event.request);
})
);
});
I have a PWA made with HTML, CSS and JS with Node.js, everytime I change the styles.css of the app, I have to upload it again, I.e. change the port. For example in localhost:3000 it would have the old styling, but if I upload it to localhost:3100, the styling changed to the new one, how can I make it so that cached css files will be deleted and uploaded with the new ones?
This is my service worker:
var CACHE_NAME = 'version-1'; // bump this version when you make changes.
// Put all your urls that you want to cache in this array
var urlsToCache = [
'index.html',
'assets/logo-192.png',
'images/airplane.png',
'images/backspace.png',
'images/calcToggle.png',
'images/diamond.png',
'images/favicon.png',
'images/hamburger.png',
'images/history.png',
'images/like.png',
'images/love.png',
'images/menu2.png',
'images/menu3.png',
'images/menu4.png',
'images/menu5.png',
'images/menu6.png',
'images/menu7.png',
'images/menu8.png',
'images/plane.png',
'images/science.png',
'images/settings.png',
'images/trash.png',
'styles.css'
];
// Install the service worker and open the cache and add files mentioned in array to cache
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// keep fetching the requests from the user
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) return response;
return fetch(event.request);
})
);
});
self.addEventListener('activate', function(event) {
var cacheWhitelist = []; // add cache names which you do not want to delete
cacheWhitelist.push(CACHE_NAME);
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (!cacheWhitelist.includes(cacheName)) {
return caches.delete(cacheName);
}
})
);
})
);
});
If you are doing this for development just open your dev tools. Select the application tab, then the service worker panel.
Click the 'Bypass for Network' option.
I wrote an article on service worker dev best practices that might help:
https://love2dev.com/serviceworker/development-best-practices/
If you need to update in production that is different. I generally do a periodic HEAD request on the network asset (CSS file in your example). If the resource is newer than the cached version I update to the latest version as needed.
I have other techniques I use from time to time as well. It varies by application and the requirements, etc.
Good day StackOverflow!
i got a concern regarding my sw. when opening the page. I dont know how to explain this using technical terms so i will just explain thin based on my observation.
When i open a new home page, it loads the page and saves the cache. when i login to the website, the page changes as it should, generating the image, username, etc. So far so good. When i logout, my expected page should be the default homepage but it still displaying the page like when a user log in to the site.
this happens on my local server, remote pc, and smart phones(both on web page and PWA app)
If i delete the generated cache, it returns to normal. What should I do in this situation?
here is my sw.js file:
const dynamicCacheName = 'ver-v1';
// activate event
self.addEventListener('activate', evt => {
evt.waitUntil(
caches.keys().then(keys => {
return Promise.all(keys
.filter(key => key !== dynamicCacheName)
.map(key => caches.delete(key))
);
})
);
});
// fetch event
self.addEventListener('fetch', evt => {
evt.respondWith(
caches.match(evt.request).then(cacheRes => {
return cacheRes || fetch(evt.request).then(fetchRes => {
return caches.open(dynamicCacheName).then(cache => {
cache.put(evt.request.url, fetchRes.clone());
return fetchRes;
})
});
})
);
});
You are caching the authenticated page. You need to capture when the user logs out and clear the cached page and fetch the unauthenticated version.
There are many, more complex ways to handle this scenario, but I have a feel a bit beyond where you are right now.
Just a hint. You can render and cache responses in your service worker. You can catch requests and serve different versions based on the request before they are sent to the network. For example, you can check to see if the Bearer token is in your request header. If so and it is current/valid then you serve the cached authenticated version, if not then the public version.
I do all sorts of things like this and more in my applications. Service worker is a fantastic tool, but it requires thinking differently than we did in the past and sometimes those concepts are difficult to grasp.
Good day. I used Workbox and now got it to work, but cache is too big to use.
This is what i did:
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.4.1/workbox-sw.js');
if (workbox) {
const CACHE_PREFIX = 'v01';
workbox.setConfig({
clientsClaim: true,
debug: true,
skipWaiting: true
});
workbox.core.setLogLevel(workbox.core.LOG_LEVELS.warn);
// Javascript and CSS rule
workbox.routing.registerRoute(
new RegExp('^.*(?:js|css)'),
new workbox.strategies.NetworkFirst(
{ cacheName: `${CACHE_PREFIX}-asset` }
),
);
// Main rule
workbox.routing.registerRoute(
new RegExp('^.*(?:)'),
new workbox.strategies.NetworkFirst(
{ cacheName: `${CACHE_PREFIX}-main` }
),
);
} else {
console.log(`Boo! Workbox didn't load 😬`);
}
I'm creating a PWA using React.
My Service Worker seems to be working fine except for the fetch event listener, which does not execute when a GET HTTP request fires within my React app using the fetch API to get data from an external API on the web.
Where should I be placing my fetch event listener?
Does it work for external requests or only for requests to files that are part of the app?
Please let me know if you notice any issues with my code below.
I'm using the boilerplate Service Worker file that comes when using create-react-app to start a new project.
Here is my code:
(The execution never gets into the window.addEventListener('fetch', ...) part)
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
window.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
if (response) {
return response
}
return fetch(event.request).then(response => {
caches.open('fetch').then((cache) => {
cache.put(event.request, response.clone());
});
return response;
})
}
)
);
});
console.log('New content is available; please refresh.');
} else {
// static files caching
cacheStaticFiles();
// external api data caching
cacheApiData();
// At this point, everything has been precached
console.log('Content is now cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
So I ran into this issue. It turns out that CRA actually compiles that service worker into a different service-worker.js file in your build folder, and that gets registered. If you append your code to the end of that file it will work, but annoyingly you'd have to do that on every build.
I'm using this addin: https://www.npmjs.com/package/cra-append-sw to append it automatically instead.
At present it had this minor issue, which requires running it with a different option: https://github.com/tszarzynski/cra-append-sw/issues/18
I'm having some problems on setting up a service worker for my website.
I only want to cache css/js/fonts and some images/svg, I don't want to cache the HTML since all of it is updated every minute.
It kinda works, but trying on my smartphone I keep getting the notification "Add to homescreen" even when I've already added it. And on the Chrome Dev app I don't get the Add button.
Also with the Lighthouse I get the following errors:
"Does not respond with a 200 when offline"
"User will not be prompted to Install the Web App, Failures: Manifest start_url is not cached by a Service Worker."
Right now my sw.js is like this. As you can see I commented the fetch part because it was caching the HTML and also the Cookies weren't working.
Is there around a simple Service Worker "template" to use?
const PRECACHE = 'app-name';
const RUNTIME = 'runtime';
// A list of local resources we always want to be cached.
const PRECACHE_URLS = [
'/css/file.css',
'/js/file.js',
'/images/logo.png',
'/fonts/roboto/Roboto-Regular.woff2'
]
// The install handler takes care of precaching the resources we always need.
self.addEventListener('install', event => {
event.waitUntil(
caches.open(PRECACHE)
.then(cache => cache.addAll(PRECACHE_URLS))
.then(self.skipWaiting())
);
});
// The activate handler takes care of cleaning up old caches.
self.addEventListener('activate', event => {
const currentCaches = [PRECACHE, RUNTIME];
event.waitUntil(
caches.keys().then(cacheNames => {
return cacheNames.filter(cacheName => !currentCaches.includes(cacheName));
}).then(cachesToDelete => {
return Promise.all(cachesToDelete.map(cacheToDelete => {
return caches.delete(cacheToDelete);
}));
}).then(() => self.clients.claim())
);
});
// The fetch handler serves responses for same-origin resources from a cache.
// If no response is found, it populates the runtime cache with the response
// from the network before returning it to the page.
self.addEventListener('fetch', event => {
// Skip cross-origin requests, like those for Google Analytics.
// if (event.request.url.startsWith(self.location.origin)) {
// event.respondWith(
// caches.match(event.request).then(cachedResponse => {
// if (cachedResponse) {
// return cachedResponse;
// }
// return caches.open(RUNTIME).then(cache => {
// return fetch(event.request).then(response => {
// // Put a copy of the response in the runtime cache.
// return cache.put(event.request, response.clone()).then(() => {
// return response;
// });
// });
// });
// })
// );
// }
});
I'm not sure why the install banner appears but the two errors given by lighthouse are related to the missing caching of the very start_url, propably index.html. So Lighthouse will always be telling you about those if you follow the caching strategy you described here.
I suggest you could try Workbox and their runtime caching. Runtime caching, in a nutshell, works like so: you specify urls like *.svg, *.css etc. and the SW caches them once the client first asks them. In the future, when the files are already cached, the SW serves them from the cache to the client. Basically you tell the SW to cache this and that kind of urls when it encounters them and not in advance.
Runtime caching could very well be accompanied by precaching (that may also be found from Workbox!) to cache a bunch of files.
Check it out here: https://workboxjs.org
They have a couple of examples and plugins for build tooling.