Open
Description
Relates to #3534
Proposal
Developers should be able to manually update their service worker on request
Current Behavior
Service workers update lazily, sometimes on navigation. Sometimes when the app reloads. In the case of an iOS PWA, the answer it VERY unpredictable. As of iOS 12.2, the app context is frozen and resumes whenever you reopen, which means your service worker will likely not be updated until the app unloads itself which could be anywhere between 24 hours and 2 weeks.
Working example
- With minimal tweaks, I was able to get this working in my own project
- I added an "immediate" option to the serviceWorker.register config
- For my iOS PWA, I used the page visibility api to call serviceWorker.register({immediate: true}) whenever the page becomes active again. Works like a charm.
Modifications to the register function
export function register(config?: 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 as { env: { [key: string]: string } }).env.PUBLIC_URL,
window.location.href
);
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/facebook/create-react-app/issues/2374
return;
}
// PULL OUT THIS FUNCTION SO IT CAN BE REUSED
const doRegister = () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
};
// EITHER CALL REGISTER IMMEDIATELY, OR WAIT FOR WINDOW LOAD (CURRENT BEHAVIOR)
if (config && config.immediate) {
doRegister();
} else {
window.addEventListener('load', doRegister);
}
}
}
function registerValidSW(swUrl: string, config?: Config) {
if (config && config.immediate) {
// TO MY SURPRISE, THESE TWO LINES RE-TRIGGERED ALL OF THE APPROPRIATE BEHAVIOR
navigator.serviceWorker.getRegistration(swUrl).then(registration => {
registration && registration.update();
});
} else {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
//... REST IS THE SAME
})
Why?
Usually I wouldn't bother proposing stuff like this, but it only adds about 6 new lines of code (rerranges a bit more) and gives the developer more control over the update cycle of their app.