From d64a7b9ef6ca47bf2b62777d2afc36b614350c83 Mon Sep 17 00:00:00 2001 From: Ro Savage Date: Thu, 1 Jun 2017 14:41:45 +1200 Subject: [PATCH 1/4] Add cache invalidation based on service work url --- .../template/src/registerServiceWorker.js | 80 +++++++++++++------ 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/packages/react-scripts/template/src/registerServiceWorker.js b/packages/react-scripts/template/src/registerServiceWorker.js index 9966897dc28..46b5f39e477 100644 --- a/packages/react-scripts/template/src/registerServiceWorker.js +++ b/packages/react-scripts/template/src/registerServiceWorker.js @@ -11,37 +11,69 @@ export default function register() { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { window.addEventListener('load', () => { + // this would become service-work-hash.js 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.'); - } + 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(error => { - console.error('Error during service worker registration:', error); + }); + } }); + } }); } } +function registerServiceWorker(url) { + navigator.serviceWorker + .register(url) + .then(registration => { + console.log('reg.scope', registration.scope); + 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 => { From f37690fcd1e163fa88e6a35f8f0775217282d1d0 Mon Sep 17 00:00:00 2001 From: Ro Savage Date: Thu, 1 Jun 2017 17:58:24 +1200 Subject: [PATCH 2/4] Add custom filepath and name for generated service worker --- packages/react-scripts/config/webpack.config.prod.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 5943c14502c..e0e481ef281 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -35,6 +35,8 @@ const shouldUseRelativeAssetPaths = publicPath === './'; const publicUrl = publicPath.slice(0, -1); // Get environment variables to inject into our app. const env = getClientEnvironment(publicUrl); +// Get packageName from package.json +const packageName = require(paths.appPackageJson).name; // Assert this just to be safe. // Development builds of React are slow and not intended for production. @@ -311,7 +313,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. From 7a15dd9213225e3334e5c95d3cc63d106cbce783 Mon Sep 17 00:00:00 2001 From: Ro Savage Date: Thu, 1 Jun 2017 18:16:37 +1200 Subject: [PATCH 3/4] Add package name to env vars so that registerServiceWorker can know projects package name --- packages/react-scripts/config/env.js | 5 ++++- packages/react-scripts/config/webpack.config.dev.js | 4 +++- packages/react-scripts/config/webpack.config.prod.js | 5 +++-- packages/react-scripts/template/src/registerServiceWorker.js | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) 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 e0e481ef281..a9f0a8cb18e 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -33,11 +33,12 @@ 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 environment variables to inject into our app. -const env = getClientEnvironment(publicUrl); // Get packageName from package.json const packageName = require(paths.appPackageJson).name; +// Get environment variables to inject into our app. +const env = getClientEnvironment(publicUrl, packageName); + // Assert this just to be safe. // Development builds of React are slow and not intended for production. if (env.stringified['process.env'].NODE_ENV !== '"production"') { diff --git a/packages/react-scripts/template/src/registerServiceWorker.js b/packages/react-scripts/template/src/registerServiceWorker.js index 46b5f39e477..f566dfe9fa4 100644 --- a/packages/react-scripts/template/src/registerServiceWorker.js +++ b/packages/react-scripts/template/src/registerServiceWorker.js @@ -12,7 +12,7 @@ export default function register() { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { window.addEventListener('load', () => { // this would become service-work-hash.js - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + const swUrl = `${process.env.PUBLIC_URL}/service-worker-${process.env.PACKAGE_NAME}.js`; if (!navigator.serviceWorker.controller) { // No service worker yet registerServiceWorker(swUrl); From 0edb08123982917c8f863808103b81ad73984256 Mon Sep 17 00:00:00 2001 From: Ro Savage Date: Thu, 1 Jun 2017 18:54:45 +1200 Subject: [PATCH 4/4] Added catches to fetch calls to catch timeout and connection errors --- .../template/src/registerServiceWorker.js | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/react-scripts/template/src/registerServiceWorker.js b/packages/react-scripts/template/src/registerServiceWorker.js index f566dfe9fa4..74e82dc1dc1 100644 --- a/packages/react-scripts/template/src/registerServiceWorker.js +++ b/packages/react-scripts/template/src/registerServiceWorker.js @@ -17,27 +17,38 @@ export default function register() { // 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'); - } - }); - } - }); + 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); + }); } }); } @@ -47,7 +58,7 @@ function registerServiceWorker(url) { navigator.serviceWorker .register(url) .then(registration => { - console.log('reg.scope', registration.scope); + console.log('register', registration); registration.onupdatefound = () => { const installingWorker = registration.installing; installingWorker.onstatechange = () => {