Yes
Yes
https://github.com/facebook/create-react-app/issues/2398
Environment Info:
System:
OS: macOS High Sierra 10.13.6
CPU: x64 Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
Binaries:
Node: 9.10.0 - /usr/local/bin/node
Yarn: 1.10.1 - /usr/local/bin/yarn
npm: 5.6.0 - /usr/local/bin/npm
Browsers:
Chrome: 69.0.3497.100
Firefox: 62.0.3
Safari: 12.0
npmPackages:
react: ^16.5.2 => 16.5.2
react-dom: ^16.5.2 => 16.5.2
react-scripts: 2.0.4 => 2.0.4
npmGlobalPackages:
create-react-app: 2.0.3
(Write your steps here:)
yarn install
. Create a build. yarn run build
.serve -s build
(if you don't have serve yarn global add serve
), or node server.js
.App.js
and run yarn run build
again.After refreshing the page at point 7. I should see my changes.
I see the old version of my app.
(Write what happened. Please add screenshots!)
You need to close all tabs or fully close the browser to see the changes reflected. A page reload will not reflect the changes.
Hey @timer, from what I see on the video, OP is using the incognito mode with a single open tab.
I cloned the repo and I can reproduce the problem as well. Maybe something is wrong with the V2 service worker?
/cc @jeffposnick
Is this the expected behaviour? Does it happen only on localhost? When onUpdate gets called I would like to add a popup which prompts the user to reload the page.
Yes -- the expected behavior is that you must close all tabs or browser before the new content appears. This is the behavior everywhere, not just on localhost
.
You should be able to override this via ServiceWorkerGlobalScope.skipWaiting()
but you need to make sure you coordinate this across all instances of your apps running in their browser.
May we convert this into a "doc improvement" issue so that we keep track of this? I think the current documentation should at least mention it.
Yeah we should probably note this in https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#offline-first-considerations
In the meantime I found this: https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin skipWaiting: true
does the job.
Yes, this does warrant improvements to the v2 docs. I can tackle that next week.
FYI, https://github.com/facebook/create-react-app/issues/3613#issuecomment-353467430 is the best explanation of why defaulting skipWaiting
to false
was the safest thing to do for c-r-a
v2.
tl;dr is that if skipWaiting
is true
and you lazy-load resources, there's a decent chance that you might end up trying to lazy-load a URL that no longer exists in the service worker's cache or on the server. If you don't lazy-load versioned URLs and you want the old behavior, then changing your config to using skipWaiting: true
is the alternative.
@gabrielmicko , @jeffposnick , Can the skipWaiting: true
option be applied without ejecting?
On this line the workbox-webpack-plugin is registered with clientsClaim: true
and the documentation for workbox-webpack-plugin
's skipWaiting
option says
Whether or not the service worker should skip over the waiting lifecycle stage. Normally this is used with
clientsClaim: true
.
Should the default behaviour be setting clientsClaim: true
and skipWaiting: true
?
@mymattcarroll yes, that way you get the update asap. For now I don't know how am I going to get a callback about the update onUpdate seems to be broken if skipWaiting is set to true. I'll take a look later.
Also I don't know how I can add skipWaiting: true without hacking in the node_modules. Any idea?
Finally I was able to test with "react-scripts": "^1.1.0". Actually it works how it should!
serviceWorker.register({
onUpdate:() => {
//Some UI notification comes here, then reload
window.location.reload();
}
})
Once it reloads, I instantly see the change, whereas in create-react-app v2 I won't. I guess this is something that has to be addressed. Please at least make it configurable.
For example:
serviceWorker.register({
skipWaiting: true
onUpdate:() => {
});
This case onUpdate would instantly fire once skipWaiting() get's called.
What do you think?
/cc @jeffposnick
Edit: actually I am a little confused if it could be added to serviceWorker.js file. Cause I guess it has to be in the service-worker.js.
@gabrielmicko, it will have to be a build time configuration as it needs to be added to the webpack configuration here.
Perhaps configuration in package.json
? The package.json
is already being used for build time configuration using the browserlist
property. Maybe something like:
{
"serviceWorkerOptions": {
"skipWaiting": true
}
}
@mymattcarroll This would be awesome. Other questions is how do you let serviceWorker.js know that skipWaiting has been called? The developer want's to subscribe to that event, so he can let the users know there was an update.
@Timer, it seems the default behaviour of the service worker has changed between v1 and v2. Our project is no longer hitting this line to allow us to prompt the user when new content is available by just refreshing the current page.
Was this intentional?
If it was, I have been able to make changes to a local fork to get v1 behaviour to occur again by making the following changes:
serviceWorker.js
check the event
object passed to registration.installing.onstatechange
instead of checking registration.installing.state
(it seemed there was a race condition where multiple state changes were occurring so fast that registration.installing.state
was always equal to 'activated'
)webpack.config.prod.js
to pass skipWaiting: true
to workbox-webpack-plugin configurationI would be happy to contribute with a pull request if you believe these changes are desirable for others.
If setting skipWaiting: true
by default is not desirable, would you consider a configuration option, perhaps in package.json
? I would be happy to work on making the UX similar to how the browserslist
property is added to package.json
with a prompt.
This library is awesome, would hate to consider ejecting...
Very well said @mymattcarroll! This is what I realised as well! I guess the default behaviour should match v1 so that devs would not need to change anything!
So I had mistakenly thought that being able to override the default Workbox options via an external config file had made it into c-r-a
v2 branch, but as per https://github.com/facebook/create-react-app/pull/5111#issuecomment-425458687 that was just a proposal and not actually something that landed.
That would allow folks who feel comfortable with the tradeoffs around potentially buggy lazy-loading to opt-in to using skipWaiting: true
, and get a quicker update flow.
I've filed separate issue to track the addition of that config file: https://github.com/facebook/create-react-app/issues/5359
I'm in favor of adding a cra.config.js
for these config and others, overriding default ones!
At first I thought it could already be done.
@mymattcarroll
Can the skipWaiting: true option be applied without ejecting?
This is the hacky way I had to do it
install replace-in-file
npm install --save-dev replace-in-file
create a file in the root called hack-add-skipWaiting-true-to-react-script.js
UPDATE
Changed webpack-config.prod.js to webpack-config.js since the file is renamed in current version
'use strict';
const replace = require('replace-in-file');
try {
console.log('Adding skipWaiting: true to react-script webpack-config.js');
replace.sync({
files: 'node_modules/react-scripts/config/webpack.config.js',
from: /clientsClaim: true,$/gm,
to: (match) => {
return match + ' skipWaiting: true,';
},
});
} catch (e) {
console.log('Something went wrong when trying to add skipWaiting: true to react-script webpack-config.js', e);
process.exit();
}
Add it as a postinstall script to your package.json
{
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"postinstall": "node hack-add-skipWaiting-true-to-react-script.js"
}
}
run npm install
and skipWaiting should now be set to true
Very sneaky @andidev ;)
I have rolled back to [email protected]
for now in the hope #5369 gets accepted.
If you use react-app-rewired it is fairly easy to achieve this. I can post it if somebody wants it.
If you use react-app-rewired it is fairly easy to achieve this. I can post it if somebody wants it.
That'd be good but isn't react-app-wired deprecated on CRA v2.0?
@kevinOriginal nope, the message on their repo is confusing. It works just fine, it's just that the utilities used to inject your own configuration work just on V1. But you can either write the code yourself or use the ones provided by customize-cra
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.
What's the point of this stale bot? Why should you close an issue if it's waiting to be addressed?
Any updates here? This seems far too confusing to the 'average use-case' of developers wanting to enable SW to just allow for PWA behavior on mobile. My recommendation is to make SW non-cache friendly by-default to getting cache-stuck.
I've got my app somewhat working with CRA2 via following "Offer a page reload for users": https://developers.google.com/web/tools/workbox/guides/advanced-recipes
However, in order to send a message to CRA2's auto-generated SW, I use an npm script to append the below lines to the created worker after its built:
self.addEventListener('message', (event) => {
if (!event.data){
return;
}
switch (event.data) {
case 'skipWaiting':
self.skipWaiting();
break;
default:
// NOOP
break;
}
});
I'd recommend adding this into the default generated SW as this technique is considered best practice for updating SW apps.
If you use react-app-rewired it is fairly easy to achieve this. I can post it if somebody wants it.
Yes please
I've got my app somewhat working with CRA2 via following "Offer a page reload for users": https://developers.google.com/web/tools/workbox/guides/advanced-recipes
However, in order to send a message to CRA2's auto-generated SW, I use an npm script to append the below lines to the created worker after its built:
self.addEventListener('message', (event) => { if (!event.data){ return; } switch (event.data) { case 'skipWaiting': self.skipWaiting(); break; default: // NOOP break; } });
I'd recommend adding this into the default generated SW as this technique is considered best practice for updating SW apps.
Can you please share how you did it? @jadbox
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.
Can you guys just disable this stupid bot please?
FWIW, I wrote a simple craco plugin called craco-workbox to handle this in CRA2 until #5369 gets merged.
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.
No, @stale.
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.
No.
I don't understand the purpose of this bot :(
By now what is the best way to make skipWaiting
true?
I guess customize-cra
is the way to go for now.
@sky93 With CRA v3 you can post a message SKIP_WAITING
to the service worker to trigger skip waiting.
// index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(<App />, document.getElementById("root"));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.register({
onUpdate: registration => {
const waitingServiceWorker = registration.waiting
if (waitingServiceWorker) {
waitingServiceWorker.addEventListener("statechange", event => {
if (event.target.state === "activated") {
window.location.reload()
}
});
waitingServiceWorker.postMessage({ type: "SKIP_WAITING" });
}
}
});
But usualy you want ot ask user before doing reload. I used wrapping service worker registration into the React context:
import React from "react";
import * as serviceWorker from "./serviceWorker";
const ServiceWorkerContext = React.createContext();
function ServiceWorkerProvider(props) {
const [waitingServiceWorker, setWaitingServiceWorker] = React.useState(null);
const [assetsUpdateReady, setAssetsUpdateReady] = React.useState(false);
const [assetsCached, setAssetsCached] = React.useState(false);
const value = React.useMemo(
() => ({
assetsUpdateReady,
assetsCached,
// Call when the user confirm update of application and reload page
updateAssets: () => {
if (waitingServiceWorker) {
waitingServiceWorker.addEventListener("statechange", event => {
if (event.target.state === "activated") {
window.location.reload()
}
});
waitingServiceWorker.postMessage({ type: "SKIP_WAITING" });
}
}
}),
[assetsUpdateReady, assetsCached, waitingServiceWorker]
);
// Once on component mounted subscribe to Update and Succes events in
// CRA's service worker wrapper
React.useEffect(() => {
serviceWorker.register({
onUpdate: registration => {
setWaitingServiceWorker(registration.waiting);
setAssetsUpdateReady(true);
},
onSuccess: () => {
setAssetsCached(true);
}
});
}, []);
return <ServiceWorkerContext.Provider value={value} {...props} />;
}
function useServiceWorker() {
const context = React.useContext(ServiceWorkerContext);
if (!context) {
throw new Error(
"useServiceWorker must be used within a ServiceWorkerProvider"
);
}
return context;
}
export { ServiceWorkerProvider, useServiceWorker };
The only additional change required in the serviceWorker.js
file of CRA if you want to register service worker from the React application. You need to get rid of the window.onload
event waiting in the register
function :
// serviceWorker.js
export 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.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;
}
// We are not waiting for window's load event as it was for the create-react-app template
// because we register a service worker in the React app that runs after window loaded
// and the load event already was emitted.
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 http://bit.ly/CRA-PWA"
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
}
}
@EddiG shouldn't the service worker handle the SKIP_WAITING
message? I don't see it documented in workbox
@FezVrasta You can find additional info here (Code in your service worker
section). Also, if you run yarn build
in CRA3 you see the code at the /build/service-worker.js
which is responsible for the handling of SKIP_WAITING
message:
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
@sky93 With CRA v3 you can post a message
SKIP_WAITING
to the service worker to trigger skip waiting.// index.js import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; ReactDOM.render(<App />, document.getElementById("root")); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.register({ onUpdate: registration => { const waitingServiceWorker = registration.waiting if (waitingServiceWorker) { waitingServiceWorker.addEventListener("statechange", event => { if (event.target.state === "activated") { window.location.reload() } }); waitingServiceWorker.postMessage({ type: "SKIP_WAITING" }); } } });
But usualy you want ot ask user before doing reload. I used wrapping service worker registration into the React context:
import React from "react"; import * as serviceWorker from "./serviceWorker"; const ServiceWorkerContext = React.createContext(); function ServiceWorkerProvider(props) { const [waitingServiceWorker, setWaitingServiceWorker] = React.useState(null); const [assetsUpdateReady, setAssetsUpdateReady] = React.useState(false); const [assetsCached, setAssetsCached] = React.useState(false); const value = React.useMemo( () => ({ assetsUpdateReady, assetsCached, // Call when the user confirm update of application and reload page updateAssets: () => { if (waitingServiceWorker) { waitingServiceWorker.addEventListener("statechange", event => { if (event.target.state === "activated") { window.location.reload() } }); waitingServiceWorker.postMessage({ type: "SKIP_WAITING" }); } } }), [assetsUpdateReady, assetsCached, waitingServiceWorker] ); // Once on component mounted subscribe to Update and Succes events in // CRA's service worker wrapper React.useEffect(() => { serviceWorker.register({ onUpdate: registration => { setWaitingServiceWorker(registration.waiting); setAssetsUpdateReady(true); }, onSuccess: () => { setAssetsCached(true); } }); }, []); return <ServiceWorkerContext.Provider value={value} {...props} />; } function useServiceWorker() { const context = React.useContext(ServiceWorkerContext); if (!context) { throw new Error( "useServiceWorker must be used within a ServiceWorkerProvider" ); } return context; } export { ServiceWorkerProvider, useServiceWorker };
The only additional change required in the
serviceWorker.js
file of CRA if you want to register service worker from the React application. You need to get rid of thewindow.onload
event waiting in theregister
function :// serviceWorker.js export 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.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; } // We are not waiting for window's load event as it was for the create-react-app template // because we register a service worker in the React app that runs after window loaded // and the load event already was emitted. 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 http://bit.ly/CRA-PWA" ); }); } else { // Is not localhost. Just register service worker registerValidSW(swUrl, config); } } }
@EddiG the first code when you using skip_wating
that means the service worker update himself, right?
@NuruddinBadawi Please read the documentation (Skip the waiting phase
particularly), I can't explain better.
@sky93 With CRA v3 you can post a message
SKIP_WAITING
to the service worker to trigger skip waiting.// index.js import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; ReactDOM.render(<App />, document.getElementById("root")); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.register({ onUpdate: registration => { const waitingServiceWorker = registration.waiting if (waitingServiceWorker) { waitingServiceWorker.addEventListener("statechange", event => { if (event.target.state === "activated") { window.location.reload() } }); waitingServiceWorker.postMessage({ type: "SKIP_WAITING" }); } } });
But usualy you want ot ask user before doing reload. I used wrapping service worker registration into the React context:
import React from "react"; import * as serviceWorker from "./serviceWorker"; const ServiceWorkerContext = React.createContext(); function ServiceWorkerProvider(props) { const [waitingServiceWorker, setWaitingServiceWorker] = React.useState(null); const [assetsUpdateReady, setAssetsUpdateReady] = React.useState(false); const [assetsCached, setAssetsCached] = React.useState(false); const value = React.useMemo( () => ({ assetsUpdateReady, assetsCached, // Call when the user confirm update of application and reload page updateAssets: () => { if (waitingServiceWorker) { waitingServiceWorker.addEventListener("statechange", event => { if (event.target.state === "activated") { window.location.reload() } }); waitingServiceWorker.postMessage({ type: "SKIP_WAITING" }); } } }), [assetsUpdateReady, assetsCached, waitingServiceWorker] ); // Once on component mounted subscribe to Update and Succes events in // CRA's service worker wrapper React.useEffect(() => { serviceWorker.register({ onUpdate: registration => { setWaitingServiceWorker(registration.waiting); setAssetsUpdateReady(true); }, onSuccess: () => { setAssetsCached(true); } }); }, []); return <ServiceWorkerContext.Provider value={value} {...props} />; } function useServiceWorker() { const context = React.useContext(ServiceWorkerContext); if (!context) { throw new Error( "useServiceWorker must be used within a ServiceWorkerProvider" ); } return context; } export { ServiceWorkerProvider, useServiceWorker };
The only additional change required in the
serviceWorker.js
file of CRA if you want to register service worker from the React application. You need to get rid of thewindow.onload
event waiting in theregister
function :// serviceWorker.js export 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.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; } // We are not waiting for window's load event as it was for the create-react-app template // because we register a service worker in the React app that runs after window loaded // and the load event already was emitted. 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 http://bit.ly/CRA-PWA" ); }); } else { // Is not localhost. Just register service worker registerValidSW(swUrl, config); } } }
This solution has been worked on iOS PWA?
We have severous problems with cache/precache assets on execute PWA in iOS.
@marlonmleite Not working for me in iOS at all. I have to clear safari cache manually to see new changes :(
Thanks for the solutions. Works perfect for me on ios. I had to tweak obviously to get it working how I wanted.
Thanks for the solutions. Works perfect for me on ios. I had to tweak obviously to get it working how I wanted.
Can you share tweaks please?
Still not able to make it work on iOS as a PWA neither as web app on Safari.
Thanks for the solutions. Works perfect for me on ios. I had to tweak obviously to get it working how I wanted.
Can you share tweaks please?
Still not able to make it work on iOS as a PWA neither as web app on Safari.
I'd recommend posting what you have. And are you posting skip waiting on a button click?
Thanks for the solutions. Works perfect for me on ios. I had to tweak obviously to get it working how I wanted.
Can you share tweaks please?
Still not able to make it work on iOS as a PWA neither as web app on Safari.I'd recommend posting what you have. And are you posting skip waiting on a button click?
No, i just used the following snipped that was shared above.
Not sure how to trigger skip waiting on a button click i am afraid.
serviceWorker.register({
onUpdate: registration => {
const waitingServiceWorker = registration.waiting
if (waitingServiceWorker) {
waitingServiceWorker.addEventListener("statechange", event => {
if (event.target.state === "activated") {
window.location.reload()
}
});
waitingServiceWorker.postMessage({ type: "SKIP_WAITING" });
}
}
});
@andrewBsk
The problem of not updating the cache in iOS, I opened this issue for Safari (only PWA): https://bugs.webkit.org/show_bug.cgi?id=199110
While this problem is not solved, I did a workround that worked well for me: https://gist.github.com/kawazoe/fa3b5a3c998d16871ffb9e2fd721cb4b#gistcomment-2953706
Thanks for the solutions. Works perfect for me on ios. I had to tweak obviously to get it working how I wanted.
Can you share tweaks please?
Still not able to make it work on iOS as a PWA neither as web app on Safari.I'd recommend posting what you have. And are you posting skip waiting on a button click?
No, i just used the following snipped that was shared above.
Not sure how to trigger skip waiting on a button click i am afraid.serviceWorker.register({ onUpdate: registration => { const waitingServiceWorker = registration.waiting if (waitingServiceWorker) { waitingServiceWorker.addEventListener("statechange", event => { if (event.target.state === "activated") { window.location.reload() } }); waitingServiceWorker.postMessage({ type: "SKIP_WAITING" }); } } });
And what's in your service-worker.js? as the above alone won't trigger the update.
The default service-worker.js doesnt have any params for the register function. So you'd need to change that.
and in the register & registerValidSW function you need to have props.onUpdate attached to an event that tells the worker theres an update.
Also without those it shouldn't work on chrome too? Or has it been working there for you?
Can use the chrome tab to debug
alidSW function you need to have props.onUpdate attached to an event that tells the worker theres an update.
Also without those it shouldn't
Thanks for your response.
No, i haven't tried other browsers.
Service worker is the default generated by create-react-app, haven't changed anything there yet.
Feeling lost, is there a snippet available i can have a look maybe?
On > > > > > Thanks for the solutions. Works perfect for me on ios. I had to tweak obviously to get it working how I wanted.
Can you share tweaks please?
Still not able to make it work on iOS as a PWA neither as web app on Safari.I'd recommend posting what you have. And are you posting skip waiting on a button click?
No, i just used the following snipped that was shared above.
Not sure how to trigger skip waiting on a button click i am afraid.serviceWorker.register({ onUpdate: registration => { const waitingServiceWorker = registration.waiting if (waitingServiceWorker) { waitingServiceWorker.addEventListener("statechange", event => { if (event.target.state === "activated") { window.location.reload() } }); waitingServiceWorker.postMessage({ type: "SKIP_WAITING" }); } } });
And what's in your service-worker.js? as the above alone won't trigger the update.
The default service-worker.js doesnt have any params for the register function. So you'd need to change that.and in the register & registerValidSW function you need to have props.onUpdate attached to an event that tells the worker theres an update.
Also without those it shouldn't work on chrome too? Or has it been working there for you?
Can use the chrome tab to debug
I was able to make the update work following this guide but for chrome and Android only.
On iOS and safari I get the notification but service worker does not get updated
https://colmenerodigital.com/blog/implementing-pwa-update-notification/
Yes, this does warrant improvements to the v2 docs. I can tackle that next week.
FYI, #3613 (comment) is the best explanation of why defaulting
skipWaiting
tofalse
was the safest thing to do forc-r-a
v2.tl;dr is that if
skipWaiting
istrue
and you lazy-load resources, there's a decent chance that you might end up trying to lazy-load a URL that no longer exists in the service worker's cache or on the server. If you _don't_ lazy-load versioned URLs and you want the old behavior, then changing your config to usingskipWaiting: true
is the alternative.
this is precisely what had happened to me. On the smartphone app became dead and users can do nothing with that
so index.html refer old chunks
Does anyone know fix of that?
This is code I use:
/* eslint-disable */
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
import Toaster from './services/Toaster';
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 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.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;
}
window.addEventListener('load', () => {
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);
}
});
}
}
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 updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
Toaster.show({
message: 'New version available!',
intent: 'success',
action: {
onClick: () => {
installingWorker.postMessage({ action: 'skipWaiting' });
window.location.reload();
},
text: 'Update'
}
});
// Execute callback
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.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(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.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.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.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
If anyone wonder, here is a TypeScript version of @EddiG's onUpdate
:
serviceWorker.register({
onUpdate: registration => {
const waitingServiceWorker = registration.waiting;
interface ServiceWorkerEvent extends Event {
target: Partial<ServiceWorker> & EventTarget | null;
}
if (waitingServiceWorker) {
waitingServiceWorker.addEventListener(
'statechange',
(event: ServiceWorkerEvent) => {
if (event.target && event.target.state === 'activated') {
window.location.reload();
}
}
);
waitingServiceWorker.postMessage({ type: 'SKIP_WAITING' });
}
}
});
It probably could be improved, but it seems to do the job.
pro tip:
After lots of experimenting and using techniques mentioned above I have found this way to add a custom dialog that lets you ask the user if they want to update
in the bottom of your index file add
registerServiceWorker({
onUpdate: async function (registration) {
const waitingServiceWorker = registration.waiting
const bc = new BroadcastChannel('app-update');
bc.postMessage('App has updated.');
}
})
then add a component (make sure you render it somewhere)
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import * as React from "react";
import withTheme from "./components/tmx/withTheme";
class ReleaseDialog extends React.Component {
public state = {
open: false,
};
public componentWillMount = () => {
if (typeof BroadcastChannel !== 'undefined') {
const updateChannel = new BroadcastChannel('app-update');
updateChannel.addEventListener('message', event => {
this.setState({open: true})
});
}
}
public handleAccept = async () => {
try {
if (navigator && navigator.serviceWorker) {
const waitingServiceWorker = await navigator.serviceWorker.ready
if (waitingServiceWorker.waiting) {
waitingServiceWorker.waiting.postMessage({type: "SKIP_WAITING"});
}
}
} catch (e) {
console.error(e)
}
}
public handleClose = () => {
this.setState({open: false});
};
public render() {
const {version} = this.props;
return (
<div>
<Dialog
open={this.state.open}
onClose={this.handleClose}
>
<DialogTitle id="alert-dialog-title">The site Just got better!</DialogTitle>
<DialogActions>
<Button onClick={this.handleClose} color="default">
Cancel
</Button>
<Button onClick={this.handleAccept} color="primary" autoFocus={true}>
UPDATE
</Button>
</DialogActions>
</Dialog>
</div>
)
}
}
also add this just before the closing tag of
function registerValidSW(swUrl, config) {
inside registerServiceWorker.js
let refreshing = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) {
return;
}
refreshing = true;
console.log("controller change")
window.location.reload();
});
The most important thing to note is you need to send SKIP_WAITING to the waiting serviceWorker not the active one!
_PS I stripped out/altered some stuff from the code above so it may need to be altered_
There are a lot of solutions. Do we have one universal solution to solve this issue without custom webpack config, workbox and etc.?
Can create-react-app
solve this issue out of the box? I think it's major question!
I think because there isn’t really a one solution for all and it’s more of
a service worker thing. Developers may have their own way they want to
handle service workers.
On Thu, 31 Oct 2019 at 10:57, Denys Morozov notifications@github.com
wrote:
There are a lot of solutions. Do we have one universal solution to solve
this issue without custom webpack config, workbox and etc.?
Can create-react-app solve this issue out of the box? I think it's major
question!—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/facebook/create-react-app/issues/5316?email_source=notifications&email_token=ABEA4E44JPIYMJAB4BSSH3TQRK227A5CNFSM4FZJ37O2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECXJ7HY#issuecomment-548315039,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ABEA4E4MILKKSOPAKWZMO6TQRK227ANCNFSM4FZJ37OQ
.>
Ricco
Web designer/developer
*m: *07957914698
*w: *riccoski.com
@sky93 With CRA v3 you can post a message
SKIP_WAITING
to the service worker to trigger skip waiting.// index.js import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; ReactDOM.render(<App />, document.getElementById("root")); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.register({ onUpdate: registration => { const waitingServiceWorker = registration.waiting if (waitingServiceWorker) { waitingServiceWorker.addEventListener("statechange", event => { if (event.target.state === "activated") { window.location.reload() } }); waitingServiceWorker.postMessage({ type: "SKIP_WAITING" }); } } });
But usualy you want ot ask user before doing reload. I used wrapping service worker registration into the React context:
import React from "react"; import * as serviceWorker from "./serviceWorker"; const ServiceWorkerContext = React.createContext(); function ServiceWorkerProvider(props) { const [waitingServiceWorker, setWaitingServiceWorker] = React.useState(null); const [assetsUpdateReady, setAssetsUpdateReady] = React.useState(false); const [assetsCached, setAssetsCached] = React.useState(false); const value = React.useMemo( () => ({ assetsUpdateReady, assetsCached, // Call when the user confirm update of application and reload page updateAssets: () => { if (waitingServiceWorker) { waitingServiceWorker.addEventListener("statechange", event => { if (event.target.state === "activated") { window.location.reload() } }); waitingServiceWorker.postMessage({ type: "SKIP_WAITING" }); } } }), [assetsUpdateReady, assetsCached, waitingServiceWorker] ); // Once on component mounted subscribe to Update and Succes events in // CRA's service worker wrapper React.useEffect(() => { serviceWorker.register({ onUpdate: registration => { setWaitingServiceWorker(registration.waiting); setAssetsUpdateReady(true); }, onSuccess: () => { setAssetsCached(true); } }); }, []); return <ServiceWorkerContext.Provider value={value} {...props} />; } function useServiceWorker() { const context = React.useContext(ServiceWorkerContext); if (!context) { throw new Error( "useServiceWorker must be used within a ServiceWorkerProvider" ); } return context; } export { ServiceWorkerProvider, useServiceWorker };
The only additional change required in the
serviceWorker.js
file of CRA if you want to register service worker from the React application. You need to get rid of thewindow.onload
event waiting in theregister
function :// serviceWorker.js export 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.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; } // We are not waiting for window's load event as it was for the create-react-app template // because we register a service worker in the React app that runs after window loaded // and the load event already was emitted. 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 http://bit.ly/CRA-PWA" ); }); } else { // Is not localhost. Just register service worker registerValidSW(swUrl, config); } } }
Thanks!
I don't understand why it's advised to ask the user to update the app before triggering skipWaiting()
. This piece of code just tells the browser to replace the old service worker with the new one and on the next refresh, new version will be installed right ?
serviceWorker.register({
onUpdate: (registration: ServiceWorkerRegistration) => {
if (registration.waiting) {
registration.waiting.postMessage({ type: "SKIP_WAITING" });
}
}
});
I don't understand why it's advised to ask the user to update the app before triggering
skipWaiting()
. This piece of code just tells the browser to replace the old service worker with the new one and on the next refresh, new version will be installed right ?serviceWorker.register({ onUpdate: (registration: ServiceWorkerRegistration) => { if (registration.waiting) { registration.waiting.postMessage({ type: "SKIP_WAITING" }); } } });
Yes. You are right. And you can force refresh a page.
Sometimes you want to notify the user that there are updates. And let them
choose to update in case they are in the middle of something
On Fri, 22 Nov 2019 at 15:22, Denys Morozov notifications@github.com
wrote:
I don't understand why it's advised to ask the user to update the app
before triggering skipWaiting(). This piece of code just tells the
browser to replace the old service worker with the new one and on the next
refresh, new version will be installed right ?serviceWorker.register({
onUpdate: (registration: ServiceWorkerRegistration) => {
if (registration.waiting) {
registration.waiting.postMessage({ type: "SKIP_WAITING" });
}
}
});Yes. You are right. And you can force refresh a page.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/facebook/create-react-app/issues/5316?email_source=notifications&email_token=ABEA4EZKCXKOYJYYYEOFHYLQU72KBA5CNFSM4FZJ37O2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEE56Q2A#issuecomment-557574248,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ABEA4E4UU5EYBO3B2O5CJLLQU72KBANCNFSM4FZJ37OQ
.>
Ricco
Web designer/developer
*m: *07957914698
*w: *riccoski.com
I don't understand why it's advised to ask the user to update the app before triggering
skipWaiting()
. This piece of code just tells the browser to replace the old service worker with the new one and on the next refresh, new version will be installed right ?serviceWorker.register({ onUpdate: (registration: ServiceWorkerRegistration) => { if (registration.waiting) { registration.waiting.postMessage({ type: "SKIP_WAITING" }); } } });
Ok I have an issue with this solution. whenever I refresh a lot, in a small period of time, when I know there's a new update available, it will skip the update and it will stay in a "waiting to activate" state ... Any chance I can trigger the update when it's in this state ?
Ok, to make sure skipWaiting()
is called whenever a new service worker is in a waiting state (ready to replace the old one), you need to have this setup:
index.js:
serviceWorker.register({
onUpdate: (registration: ServiceWorkerRegistration) => {
if (registration.waiting) {
registration.waiting.postMessage({ type: "SKIP_WAITING" });
}
}
});
serviceWorker.js:
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
[...]
};
if (registration.waiting && config && config.onUpdate) {
config.onUpdate(registration);
}
})
}
I ran into the same issue with users struggling to load the updated version of my app. I read through this thread and came up with a simplified solution thanks to the updates that have been made to the serviceWorker.js included in CRA 3.2.0. Note that this solution only works well if you are Not lazy-loading.
in index.tsx add:
serviceWorker.register({
onUpdate: registration => {
alert('New version available! Ready to update?');
window.location.reload();
if (registration && registration.waiting) {
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
}
}
});
I wrote a blog post that explains more here: https://steemit.com/typescript/@jfbloom22/how-to-handle-upgrades-with-service-workers-with-cra3-and-typescript
serviceWorker.register({
onUpdate: async registration => {
// We want to run this code only if we detect a new service worker is
// waiting to be activated.
// Details about it: https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle
if (registration && registration.waiting) {
await registration.unregister();
// Makes Workbox call skipWaiting()
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
// Once the service worker is unregistered, we can reload the page to let
// the browser download a fresh copy of our app (invalidating the cache)
window.location.reload();
}
},
});
Just to add to this thread and make it useful for more people, in my case my PWA is going to run for a long time without a refresh (a long-running always open dashboard in a TV, no end-user interactions). In this case, I understand I to need to trigger periodically check for updates by myself. A change needs to be done inside serviceWorker.ts file for that:
navigator.serviceWorker
.register(swUrl)
.then(registration => {
// Start addition---
// https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#manual_updates
// Added code, as our application will be open for a long time and we are a SPA, we need
// to trigger checks for updates frequently
setInterval(() => {
console.log('Checking if service worker was updated in server')
registration.update()
}, 15 * 60 * 1000) // Every 15 mins check
// End addition---
registration.onupdatefound = () => {...}
Hope that is useful and feel free to correct me if I'm wrong.
Can anyone confirm if iOS 14 helps with these issues?
Most helpful comment
@sky93 With CRA v3 you can post a message
SKIP_WAITING
to the service worker to trigger skip waiting.But usualy you want ot ask user before doing reload. I used wrapping service worker registration into the React context:
The only additional change required in the
serviceWorker.js
file of CRA if you want to register service worker from the React application. You need to get rid of thewindow.onload
event waiting in theregister
function :