diff --git a/packages/react-scripts/config/env.js b/packages/react-scripts/config/env.js index e7d7f9f3206..0aaf9e9df7e 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, packageName) { 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. + PACKAGE_NAME: packageName, } ); // Stringify all values so we can feed into Webpack DefinePlugin diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 55d3b6ad74c..a484ef85f57 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -29,8 +29,10 @@ const publicPath = '/'; // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. // Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz. const publicUrl = ''; +// Get packageName from package.json +const packageName = require(paths.appPackageJson).name; // Get environment variables to inject into our app. -const env = getClientEnvironment(publicUrl); +const env = getClientEnvironment(publicUrl, packageName); // This is the development configuration. // It is focused on developer experience and fast rebuilds. diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 5943c14502c..a9f0a8cb18e 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -33,8 +33,11 @@ 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); +// Get packageName from package.json +const packageName = require(paths.appPackageJson).name; + // Get environment variables to inject into our app. -const env = getClientEnvironment(publicUrl); +const env = getClientEnvironment(publicUrl, packageName); // Assert this just to be safe. // Development builds of React are slow and not intended for production. @@ -311,7 +314,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: `service-worker-${packageName}.js`, 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..74e82dc1dc1 100644 --- a/packages/react-scripts/template/src/registerServiceWorker.js +++ b/packages/react-scripts/template/src/registerServiceWorker.js @@ -11,37 +11,80 @@ export default function register() { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 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.'); - } - } - }; - }; - }) - .catch(error => { - console.error('Error during service worker registration:', error); - }); + // this would become service-work-hash.js + const swUrl = `${process.env.PUBLIC_URL}/service-worker-${process.env.PACKAGE_NAME}.js`; + if (!navigator.serviceWorker.controller) { + // No service worker yet + registerServiceWorker(swUrl); + } else { + fetch(swUrl) + .then(res => { + // Check to see if the SW URL is valid + if (res.ok) { + // Matches. All good. Continue with registering SW + registerServiceWorker(swUrl); + } else { + // SW URL was invalid. + fetch(`${window.location.protocol}//${window.location.host}`) + .then(res2 => { + // Just check if online + if (res2.ok) { + // Unregister and refresh page + unregister(); + window.location.reload(true); + } else { + console.log('Offline. Using cached copy'); + } + }) + .catch(err => { + // Host down. Do nothing. + console.log( + `Caught - fetch ${window.location.protocol}//${window.location.host}`, + err + ); + }); + } + }) + .catch(err => { + // Couldn't access service worker url becaose of timeout/fetch error. Do nothing. + console.log(`Caught - fetch ${swUrl}`, err); + }); + } }); } } +function registerServiceWorker(url) { + navigator.serviceWorker + .register(url) + .then(registration => { + console.log('register', 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.log('No service worker found'); + console.error('Error during service worker registration:', error); + }); +} + export function unregister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => {