diff --git a/packages/react-scripts/config/env.js b/packages/react-scripts/config/env.js index e7d7f9f3206..da06b833974 100644 --- a/packages/react-scripts/config/env.js +++ b/packages/react-scripts/config/env.js @@ -67,7 +67,7 @@ process.env.NODE_PATH = (process.env.NODE_PATH || '') // injected into the application via DefinePlugin in Webpack configuration. const REACT_APP = /^REACT_APP_/i; -function getClientEnvironment(publicUrl) { +function getClientEnvironment(publicUrl, serviceWorkerName) { const raw = Object.keys(process.env) .filter(key => REACT_APP.test(key)) .reduce( @@ -84,6 +84,9 @@ function getClientEnvironment(publicUrl) { // This should only be used as an escape hatch. Normally you would put // images into the `src` and `import` them in code to get their paths. PUBLIC_URL: publicUrl, + // package.json name property. + // Used by service worker to know the name of the generated service worker. + SERVICE_WORKER_NAME: serviceWorkerName, } ); // Stringify all values so we can feed into Webpack DefinePlugin diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 5943c14502c..50ee17eadc9 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -33,8 +33,13 @@ const shouldUseRelativeAssetPaths = publicPath === './'; // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz. const publicUrl = publicPath.slice(0, -1); +// generate service worker name +const serviceWorkerName = `service-worker-${Math.random() + .toString(16) + .substring(7)}.js`; + // Get environment variables to inject into our app. -const env = getClientEnvironment(publicUrl); +const env = getClientEnvironment(publicUrl, serviceWorkerName); // Assert this just to be safe. // Development builds of React are slow and not intended for production. @@ -311,7 +316,7 @@ module.exports = { // If a URL is already hashed by Webpack, then there is no concern // about it being stale, and the cache-busting can be skipped. dontCacheBustUrlsMatching: /\.\w{8}\./, - filename: 'service-worker.js', + filename: serviceWorkerName, logger(message) { if (message.indexOf('Total precache size is') === 0) { // This message occurs for every build and is a bit too noisy. diff --git a/packages/react-scripts/template/src/registerServiceWorker.js b/packages/react-scripts/template/src/registerServiceWorker.js index 9966897dc28..a81e0c3d701 100644 --- a/packages/react-scripts/template/src/registerServiceWorker.js +++ b/packages/react-scripts/template/src/registerServiceWorker.js @@ -10,38 +10,71 @@ export default function register() { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // Hide document while we decide if this is the correct service worker + const bodyStyle = document.body.style.display; + document.body.style.display = 'none'; + window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - 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.'); - } 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.'); - } - } - }; - }; + const swUrl = `${process.env.PUBLIC_URL}/${process.env.SERVICE_WORKER_NAME}`; + + // This is used to check validaity of service worker. And reload page if changed had been made. + 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 + ) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Found original service worker. + document.body.style.display = bodyStyle; + registerValidSW(swUrl); + } }) - .catch(error => { - console.error('Error during service worker registration:', error); + .catch(() => { + document.body.style.display = bodyStyle; + console.log( + 'No internet connection found. App is running in offline mode.' + ); }); }); } } +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) { + // 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.'); + } 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.'); + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + export function unregister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => {