Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cache invalidation based on service work url #2438

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/react-scripts/config/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion packages/react-scripts/config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 5 additions & 2 deletions packages/react-scripts/config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
97 changes: 70 additions & 27 deletions packages/react-scripts/template/src/registerServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
Copy link
Contributor Author

@ro-savage ro-savage Jun 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if this is actually needed.

It might be fine to say that if we can't fetch swUrl, then just unregister and reload the page.

  • For disconnected internet or server down it will get caught in the catch and will cached service work.
  • If we get 200 ok from swUrl, we can register it as usual.
  • If we get anything else just unregister and and reload.

I am not sure the is actually a reason to check the host directly?

.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 => {
Expand Down