From 54d3740d255c83a978dd368422d12cc9a534855f Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Thu, 11 Jun 2020 11:56:01 -0400 Subject: [PATCH 01/25] WIP --- packages/react-scripts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 0114e79544b..d7ec4e67054 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -82,7 +82,7 @@ "webpack": "4.43.0", "webpack-dev-server": "3.11.0", "webpack-manifest-plugin": "2.2.0", - "workbox-webpack-plugin": "4.3.1" + "workbox-webpack-plugin": "5.1.3" }, "devDependencies": { "react": "^16.12.0", From e9fa48b4470c10dac120260be92e7e4b2cde9de0 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Thu, 11 Jun 2020 12:24:53 -0400 Subject: [PATCH 02/25] WIP --- .../template/src/index.tsx | 4 ++-- ...Worker.ts => serviceWorkerRegistration.ts} | 0 .../template/src/sw.ts | 23 +++++++++++++++++++ packages/cra-template/template/src/index.js | 4 ++-- ...Worker.js => serviceWorkerRegistration.js} | 0 packages/cra-template/template/src/sw.js | 21 +++++++++++++++++ packages/react-scripts/config/paths.js | 3 +++ .../react-scripts/config/webpack.config.js | 21 ++++++----------- 8 files changed, 58 insertions(+), 18 deletions(-) rename packages/cra-template-typescript/template/src/{serviceWorker.ts => serviceWorkerRegistration.ts} (100%) create mode 100644 packages/cra-template-typescript/template/src/sw.ts rename packages/cra-template/template/src/{serviceWorker.js => serviceWorkerRegistration.js} (100%) create mode 100644 packages/cra-template/template/src/sw.js diff --git a/packages/cra-template-typescript/template/src/index.tsx b/packages/cra-template-typescript/template/src/index.tsx index f85c4d0ffde..55fb320b883 100644 --- a/packages/cra-template-typescript/template/src/index.tsx +++ b/packages/cra-template-typescript/template/src/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; -import * as serviceWorker from './serviceWorker'; +import * as serviceWorkerRegistration from './serviceWorkerRegistration'; ReactDOM.render( @@ -14,4 +14,4 @@ ReactDOM.render( // 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://cra.link/PWA -serviceWorker.unregister(); +serviceWorkerRegistration.unregister(); diff --git a/packages/cra-template-typescript/template/src/serviceWorker.ts b/packages/cra-template-typescript/template/src/serviceWorkerRegistration.ts similarity index 100% rename from packages/cra-template-typescript/template/src/serviceWorker.ts rename to packages/cra-template-typescript/template/src/serviceWorkerRegistration.ts diff --git a/packages/cra-template-typescript/template/src/sw.ts b/packages/cra-template-typescript/template/src/sw.ts new file mode 100644 index 00000000000..b008b4d98b7 --- /dev/null +++ b/packages/cra-template-typescript/template/src/sw.ts @@ -0,0 +1,23 @@ +declare const self: ServiceWorkerGlobalScope; + +import {clientsClaim} from 'workbox-core'; +import {precacheAndRoute, createHandlerBoundToURL} from 'workbox-precaching'; +import {registerRoute, NavigationRoute} from 'workbox-routing'; + +clientsClaim(); + +precacheAndRoute(self.__WB_MANIFEST); + +const handler = createHandlerBoundToURL('index.html'); +const navigationRoute = new NavigationRoute(handler, { + denylist: [ + // Exclude URLs starting with /_, as they're likely an API call + new RegExp('^/_'), + // Exclude any URLs whose last part seems to be a file extension + // as they're likely a resource and not a SPA route. + // URLs containing a "?" character won't be blacklisted as they're likely // URLs containing a "?" character won't be blacklisted as they're likely + // a route with query params (e.g. auth callbacks). + new RegExp('/[^/?]+\\.[^/]+$'), + ], +}); +registerRoute(navigationRoute); diff --git a/packages/cra-template/template/src/index.js b/packages/cra-template/template/src/index.js index f85c4d0ffde..55fb320b883 100644 --- a/packages/cra-template/template/src/index.js +++ b/packages/cra-template/template/src/index.js @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; -import * as serviceWorker from './serviceWorker'; +import * as serviceWorkerRegistration from './serviceWorkerRegistration'; ReactDOM.render( @@ -14,4 +14,4 @@ ReactDOM.render( // 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://cra.link/PWA -serviceWorker.unregister(); +serviceWorkerRegistration.unregister(); diff --git a/packages/cra-template/template/src/serviceWorker.js b/packages/cra-template/template/src/serviceWorkerRegistration.js similarity index 100% rename from packages/cra-template/template/src/serviceWorker.js rename to packages/cra-template/template/src/serviceWorkerRegistration.js diff --git a/packages/cra-template/template/src/sw.js b/packages/cra-template/template/src/sw.js new file mode 100644 index 00000000000..9e7f403ff18 --- /dev/null +++ b/packages/cra-template/template/src/sw.js @@ -0,0 +1,21 @@ +import {clientsClaim} from 'workbox-core'; +import {precacheAndRoute, createHandlerBoundToURL} from 'workbox-precaching'; +import {registerRoute, NavigationRoute} from 'workbox-routing'; + +clientsClaim(); + +precacheAndRoute(self.__WB_MANIFEST); + +const handler = createHandlerBoundToURL('index.html'); +const navigationRoute = new NavigationRoute(handler, { + denylist: [ + // Exclude URLs starting with /_, as they're likely an API call + new RegExp('^/_'), + // Exclude any URLs whose last part seems to be a file extension + // as they're likely a resource and not a SPA route. + // URLs containing a "?" character won't be blacklisted as they're likely // URLs containing a "?" character won't be blacklisted as they're likely + // a route with query params (e.g. auth callbacks). + new RegExp('/[^/?]+\\.[^/]+$'), + ], +}); +registerRoute(navigationRoute); diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 11d81b7f5a7..6712e4b20d0 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -73,6 +73,7 @@ module.exports = { proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), publicUrlOrPath, + swSrc: resolveApp('src/sw'), }; // @remove-on-eject-begin @@ -100,6 +101,7 @@ module.exports = { ownNodeModules: resolveOwn('node_modules'), // This is empty on npm 3 appTypeDeclarations: resolveApp('src/react-app-env.d.ts'), ownTypeDeclarations: resolveOwn('lib/react-app.d.ts'), + swSrc: resolveApp('src/sw'), }; const ownPackageJson = require('../package.json'); @@ -135,6 +137,7 @@ if ( ownNodeModules: resolveOwn('node_modules'), appTypeDeclarations: resolveOwn(`${templatePath}/src/react-app-env.d.ts`), ownTypeDeclarations: resolveOwn('lib/react-app.d.ts'), + swSrc: resolveApp('src/sw'), }; } // @remove-on-eject-end diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 2c32b66ac9a..c332578a34b 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -60,6 +60,9 @@ const imageInlineSizeLimit = parseInt( // Check if TypeScript is setup const useTypeScript = fs.existsSync(paths.appTsConfig); +// Get the path to the uncompiled service worker (if it exists). +const swSrc = paths.swSrc; + // style files regexes const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; @@ -691,21 +694,11 @@ module.exports = function (webpackEnv) { new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // Generate a service worker script that will precache, and keep up to date, // the HTML & assets that are part of the webpack build. - isEnvProduction && - new WorkboxWebpackPlugin.GenerateSW({ - clientsClaim: true, + isEnvProduction && fs.existsSync(swSrc) && + new WorkboxWebpackPlugin.InjectManifest({ + swSrc, + dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./, exclude: [/\.map$/, /asset-manifest\.json$/], - importWorkboxFrom: 'cdn', - navigateFallback: paths.publicUrlOrPath + 'index.html', - navigateFallbackBlacklist: [ - // Exclude URLs starting with /_, as they're likely an API call - new RegExp('^/_'), - // Exclude any URLs whose last part seems to be a file extension - // as they're likely a resource and not a SPA route. - // URLs containing a "?" character won't be blacklisted as they're likely - // a route with query params (e.g. auth callbacks). - new RegExp('/[^/?]+\\.[^/]+$'), - ], }), // TypeScript type checking useTypeScript && From 5a3a6c72c9652234ee9bc99ff9504f1f44c21aed Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Thu, 11 Jun 2020 13:07:05 -0400 Subject: [PATCH 03/25] Rename --- .../template/src/{sw.ts => service-worker.ts} | 0 .../cra-template/template/src/{sw.js => service-worker.js} | 0 packages/react-scripts/config/paths.js | 6 +++--- 3 files changed, 3 insertions(+), 3 deletions(-) rename packages/cra-template-typescript/template/src/{sw.ts => service-worker.ts} (100%) rename packages/cra-template/template/src/{sw.js => service-worker.js} (100%) diff --git a/packages/cra-template-typescript/template/src/sw.ts b/packages/cra-template-typescript/template/src/service-worker.ts similarity index 100% rename from packages/cra-template-typescript/template/src/sw.ts rename to packages/cra-template-typescript/template/src/service-worker.ts diff --git a/packages/cra-template/template/src/sw.js b/packages/cra-template/template/src/service-worker.js similarity index 100% rename from packages/cra-template/template/src/sw.js rename to packages/cra-template/template/src/service-worker.js diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 6712e4b20d0..8e016d07343 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -73,7 +73,7 @@ module.exports = { proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), publicUrlOrPath, - swSrc: resolveApp('src/sw'), + swSrc: resolveApp('src/service-worker'), }; // @remove-on-eject-begin @@ -101,7 +101,7 @@ module.exports = { ownNodeModules: resolveOwn('node_modules'), // This is empty on npm 3 appTypeDeclarations: resolveApp('src/react-app-env.d.ts'), ownTypeDeclarations: resolveOwn('lib/react-app.d.ts'), - swSrc: resolveApp('src/sw'), + swSrc: resolveApp('src/service-worker'), }; const ownPackageJson = require('../package.json'); @@ -137,7 +137,7 @@ if ( ownNodeModules: resolveOwn('node_modules'), appTypeDeclarations: resolveOwn(`${templatePath}/src/react-app-env.d.ts`), ownTypeDeclarations: resolveOwn('lib/react-app.d.ts'), - swSrc: resolveApp('src/sw'), + swSrc: resolveApp('src/service-worker'), }; } // @remove-on-eject-end From 0eed437a319f18f290e98c66fe08edef50f8df9b Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Thu, 11 Jun 2020 13:12:58 -0400 Subject: [PATCH 04/25] Use publicPath --- packages/cra-template-typescript/template/src/service-worker.ts | 2 +- packages/cra-template/template/src/service-worker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cra-template-typescript/template/src/service-worker.ts b/packages/cra-template-typescript/template/src/service-worker.ts index b008b4d98b7..d647b5e6167 100644 --- a/packages/cra-template-typescript/template/src/service-worker.ts +++ b/packages/cra-template-typescript/template/src/service-worker.ts @@ -8,7 +8,7 @@ clientsClaim(); precacheAndRoute(self.__WB_MANIFEST); -const handler = createHandlerBoundToURL('index.html'); +const handler = createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'); const navigationRoute = new NavigationRoute(handler, { denylist: [ // Exclude URLs starting with /_, as they're likely an API call diff --git a/packages/cra-template/template/src/service-worker.js b/packages/cra-template/template/src/service-worker.js index 9e7f403ff18..1092114fda4 100644 --- a/packages/cra-template/template/src/service-worker.js +++ b/packages/cra-template/template/src/service-worker.js @@ -6,7 +6,7 @@ clientsClaim(); precacheAndRoute(self.__WB_MANIFEST); -const handler = createHandlerBoundToURL('index.html'); +const handler = createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'); const navigationRoute = new NavigationRoute(handler, { denylist: [ // Exclude URLs starting with /_, as they're likely an API call From 3308fb628e1134aade3595dc69715b80626c0695 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Thu, 11 Jun 2020 13:33:54 -0400 Subject: [PATCH 05/25] Getting closer. --- packages/cra-template/template/src/service-worker.js | 3 ++- packages/react-scripts/config/paths.js | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/cra-template/template/src/service-worker.js b/packages/cra-template/template/src/service-worker.js index 1092114fda4..879c69710cf 100644 --- a/packages/cra-template/template/src/service-worker.js +++ b/packages/cra-template/template/src/service-worker.js @@ -4,7 +4,8 @@ import {registerRoute, NavigationRoute} from 'workbox-routing'; clientsClaim(); -precacheAndRoute(self.__WB_MANIFEST); +// eslint-disable-next-line no-restricted-globals +precacheAndRoute(self.__WB_MANIFEST); const handler = createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'); const navigationRoute = new NavigationRoute(handler, { diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 8e016d07343..516513081b2 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -72,8 +72,8 @@ module.exports = { testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), + swSrc: resolveModule(resolveApp, 'src/service-worker'), publicUrlOrPath, - swSrc: resolveApp('src/service-worker'), }; // @remove-on-eject-begin @@ -95,13 +95,13 @@ module.exports = { testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), + swSrc: resolveModule(resolveApp, 'src/service-worker'), publicUrlOrPath, // These properties only exist before ejecting: ownPath: resolveOwn('.'), ownNodeModules: resolveOwn('node_modules'), // This is empty on npm 3 appTypeDeclarations: resolveApp('src/react-app-env.d.ts'), ownTypeDeclarations: resolveOwn('lib/react-app.d.ts'), - swSrc: resolveApp('src/service-worker'), }; const ownPackageJson = require('../package.json'); @@ -131,13 +131,13 @@ if ( testsSetup: resolveModule(resolveOwn, `${templatePath}/src/setupTests`), proxySetup: resolveOwn(`${templatePath}/src/setupProxy.js`), appNodeModules: resolveOwn('node_modules'), + swSrc: resolveModule(resolveOwn, `${templatePath}/src/service-worker`), publicUrlOrPath, // These properties only exist before ejecting: ownPath: resolveOwn('.'), ownNodeModules: resolveOwn('node_modules'), appTypeDeclarations: resolveOwn(`${templatePath}/src/react-app-env.d.ts`), ownTypeDeclarations: resolveOwn('lib/react-app.d.ts'), - swSrc: resolveApp('src/service-worker'), }; } // @remove-on-eject-end From b0cab1bec3bc04181bc639108974e7eb2d973407 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Tue, 23 Jun 2020 11:09:26 -0400 Subject: [PATCH 06/25] Move off of NavigationRoute --- .../template/src/service-worker.ts | 59 +++++++++++++----- .../template/src/service-worker.js | 62 +++++++++++++------ 2 files changed, 87 insertions(+), 34 deletions(-) diff --git a/packages/cra-template-typescript/template/src/service-worker.ts b/packages/cra-template-typescript/template/src/service-worker.ts index d647b5e6167..f5a0f7ab496 100644 --- a/packages/cra-template-typescript/template/src/service-worker.ts +++ b/packages/cra-template-typescript/template/src/service-worker.ts @@ -1,23 +1,50 @@ +// This service worker can be customized! +// See https://developers.google.com/web/tools/workbox/modules +// for the list of available Workbox modules, or add any other +// code you'd like. +// You can also remove this file if you'd prefer not to use a +// service worker, and the Workbox build step will be skipped. + declare const self: ServiceWorkerGlobalScope; import {clientsClaim} from 'workbox-core'; import {precacheAndRoute, createHandlerBoundToURL} from 'workbox-precaching'; -import {registerRoute, NavigationRoute} from 'workbox-routing'; +import {registerRoute} from 'workbox-routing'; clientsClaim(); -precacheAndRoute(self.__WB_MANIFEST); - -const handler = createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'); -const navigationRoute = new NavigationRoute(handler, { - denylist: [ - // Exclude URLs starting with /_, as they're likely an API call - new RegExp('^/_'), - // Exclude any URLs whose last part seems to be a file extension - // as they're likely a resource and not a SPA route. - // URLs containing a "?" character won't be blacklisted as they're likely // URLs containing a "?" character won't be blacklisted as they're likely - // a route with query params (e.g. auth callbacks). - new RegExp('/[^/?]+\\.[^/]+$'), - ], -}); -registerRoute(navigationRoute); +// Precache all of the assets generated by your build process. +// Their URLs are injected into the manifest variable below. +// eslint-disable-next-line no-restricted-globals +precacheAndRoute(self.__WB_MANIFEST); + +// Set up App Shell-style routing, so that all navigation requests +// are fulfilled with your index.html shell. Learn more at +// https://developers.google.com/web/fundamentals/architecture/app-shell +const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); +registerRoute( + // Return false to exempt requests from being fulfilled by index.html. + ({request, url}: {request: Request, url: URL}) => { + // If this isn't a navigation, skip. + if (request.mode !== 'navigate') { + return false; + } + + // If this is a URL that starts with /_, skip. + if (url.pathname.startsWith('/_')) { + return false; + } + + // If this looks like a URL for a resource, because it contains + // a file extension, skip. + if (url.pathname.match(fileExtensionRegexp)) { + return false; + } + + // Return true to signal that we want to use the handler. + return true; + }, + createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'), +); + +// Any other custom service worker logic can go here. diff --git a/packages/cra-template/template/src/service-worker.js b/packages/cra-template/template/src/service-worker.js index 879c69710cf..de7aa7a2b22 100644 --- a/packages/cra-template/template/src/service-worker.js +++ b/packages/cra-template/template/src/service-worker.js @@ -1,22 +1,48 @@ -import {clientsClaim} from 'workbox-core'; -import {precacheAndRoute, createHandlerBoundToURL} from 'workbox-precaching'; -import {registerRoute, NavigationRoute} from 'workbox-routing'; +// This service worker can be customized! +// See https://developers.google.com/web/tools/workbox/modules +// for the list of available Workbox modules, or add any other +// code you'd like. +// You can also remove this file if you'd prefer not to use a +// service worker, and the Workbox build step will be skipped. + +import { clientsClaim } from 'workbox-core'; +import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; +import { registerRoute } from 'workbox-routing'; clientsClaim(); +// Precache all of the assets generated by your build process. +// Their URLs are injected into the manifest variable below. // eslint-disable-next-line no-restricted-globals -precacheAndRoute(self.__WB_MANIFEST); - -const handler = createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'); -const navigationRoute = new NavigationRoute(handler, { - denylist: [ - // Exclude URLs starting with /_, as they're likely an API call - new RegExp('^/_'), - // Exclude any URLs whose last part seems to be a file extension - // as they're likely a resource and not a SPA route. - // URLs containing a "?" character won't be blacklisted as they're likely // URLs containing a "?" character won't be blacklisted as they're likely - // a route with query params (e.g. auth callbacks). - new RegExp('/[^/?]+\\.[^/]+$'), - ], -}); -registerRoute(navigationRoute); +precacheAndRoute(self.__WB_MANIFEST); + +// Set up App Shell-style routing, so that all navigation requests +// are fulfilled with your index.html shell. Learn more at +// https://developers.google.com/web/fundamentals/architecture/app-shell +const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); +registerRoute( + // Return false to exempt requests from being fulfilled by index.html. + ({ request, url }) => { + // If this isn't a navigation, skip. + if (request.mode !== 'navigate') { + return false; + } + + // If this is a URL that starts with /_, skip. + if (url.pathname.startsWith('/_')) { + return false; + } + + // If this looks like a URL for a resource, because it contains + // a file extension, skip. + if (url.pathname.match(fileExtensionRegexp)) { + return false; + } + + // Return true to signal that we want to use the handler. + return true; + }, + createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') +); + +// Any other custom service worker logic can go here. From 53b142a9e219e62a8eb075f5231ea9725d29b188 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Tue, 23 Jun 2020 11:30:54 -0400 Subject: [PATCH 07/25] Update the PWA guide --- .../docs/making-a-progressive-web-app.md | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/making-a-progressive-web-app.md b/docusaurus/docs/making-a-progressive-web-app.md index ba9802e6088..02682e40550 100644 --- a/docusaurus/docs/making-a-progressive-web-app.md +++ b/docusaurus/docs/making-a-progressive-web-app.md @@ -6,9 +6,15 @@ title: Making a Progressive Web App The production build has all the tools necessary to generate a first-class [Progressive Web App](https://developers.google.com/web/progressive-web-apps/), but **the offline/cache-first behavior is opt-in only**. By default, -the build process will generate a service worker file, but it will not be +the build process will compile a service worker file, but it will not be registered, so it will not take control of your production web app. +If you know that you won't be using service workers, you can speed up the build +process by removing the +[`src/service-worker.js`](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/src/service-worker.js) +file from your local project. This will skip the step that compiles your +service worker at build time. + In order to opt-in to the offline-first behavior, developers should look for the following in their [`src/index.js`](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/src/index.js) file: @@ -34,7 +40,7 @@ However, they [can make debugging deployments more challenging](https://github.c The [`workbox-webpack-plugin`](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin) is integrated into production configuration, -and it will take care of generating a service worker file that will automatically +and it will take care of compiling a service worker file that will automatically precache all of your local assets and keep them up to date as you deploy updates. The service worker will use a [cache-first strategy](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network) for handling all requests for local assets, including @@ -42,6 +48,25 @@ for handling all requests for local assets, including for your HTML, ensuring that your web app is consistently fast, even on a slow or unreliable network. +## Customization + +Starting with Create React App 3, you have full control over customizing the +logic in this service worker, by making changes to +[`src/service-worker.js`](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/src/service-worker.js). +You can use +[additional modules](https://developers.google.com/web/tools/workbox/modules) +from the Workbox project, add in a push notification library, or remove some of +the default caching logic. The one requirement is that you keep +`self.__WB_MANIFEST` somewhere in your file, as the Workbox compilation plugin +checks for this value when generating a manifest of URLs to precache. If you +would prefer not to use precaching, you can surround this in a comment, like: + +```js +/* self.__WB_MANIFEST */ + +// Your custom service worker code goes here. +``` + ## Offline-First Considerations If you do decide to opt-in to service worker registration, please take the @@ -88,7 +113,8 @@ following into account: 1. By default, the generated service worker file will not intercept or cache any cross-origin traffic, like HTTP [API requests](integrating-with-an-api-backend.md), - images, or embeds loaded from a different domain. + images, or embeds loaded from a different domain. Starting with Create + React App 3, this can be customized, as explained above. ## Progressive Web App Metadata From 8707cc7faba0511f9d2ed87cf776bf732b4eb506 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Tue, 23 Jun 2020 11:36:36 -0400 Subject: [PATCH 08/25] Don't precache any LICENSEs. --- packages/react-scripts/config/webpack.config.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index c332578a34b..1dba35cf903 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -694,11 +694,12 @@ module.exports = function (webpackEnv) { new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // Generate a service worker script that will precache, and keep up to date, // the HTML & assets that are part of the webpack build. - isEnvProduction && fs.existsSync(swSrc) && + isEnvProduction && + fs.existsSync(swSrc) && new WorkboxWebpackPlugin.InjectManifest({ swSrc, dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./, - exclude: [/\.map$/, /asset-manifest\.json$/], + exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/], }), // TypeScript type checking useTypeScript && From d2545aa3c53809c35f2618d6b91239e3dcb5e553 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Tue, 23 Jun 2020 11:45:23 -0400 Subject: [PATCH 09/25] Updated the ignore instructions --- docusaurus/docs/making-a-progressive-web-app.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/making-a-progressive-web-app.md b/docusaurus/docs/making-a-progressive-web-app.md index 02682e40550..0704502ebdd 100644 --- a/docusaurus/docs/making-a-progressive-web-app.md +++ b/docusaurus/docs/making-a-progressive-web-app.md @@ -59,10 +59,12 @@ from the Workbox project, add in a push notification library, or remove some of the default caching logic. The one requirement is that you keep `self.__WB_MANIFEST` somewhere in your file, as the Workbox compilation plugin checks for this value when generating a manifest of URLs to precache. If you -would prefer not to use precaching, you can surround this in a comment, like: +would prefer not to use precaching, you can just assign `self.__WB_MANIFEST` +to a variable that will be ignored, like: ```js -/* self.__WB_MANIFEST */ +// eslint-disable-next-line no-restricted-globals +const ignored = self.__WB_MANIFEST; // Your custom service worker code goes here. ``` From 3ee8b1f1f45a67c8d48aa6e6c1c04a70e45513a9 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Tue, 23 Jun 2020 12:35:26 -0400 Subject: [PATCH 10/25] skipWaiting message handler --- .../template/src/service-worker.ts | 8 ++++++++ packages/cra-template/template/src/service-worker.js | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/cra-template-typescript/template/src/service-worker.ts b/packages/cra-template-typescript/template/src/service-worker.ts index f5a0f7ab496..e2a3f813eb0 100644 --- a/packages/cra-template-typescript/template/src/service-worker.ts +++ b/packages/cra-template-typescript/template/src/service-worker.ts @@ -47,4 +47,12 @@ registerRoute( createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'), ); +// This allows the web app to trigger skipWaiting via +// registration.waiting.postMessage({type: 'SKIP_WAITING'}) +self.addEventListener('message', (event: MessageEvent) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); + // Any other custom service worker logic can go here. diff --git a/packages/cra-template/template/src/service-worker.js b/packages/cra-template/template/src/service-worker.js index de7aa7a2b22..feee29272d3 100644 --- a/packages/cra-template/template/src/service-worker.js +++ b/packages/cra-template/template/src/service-worker.js @@ -45,4 +45,12 @@ registerRoute( createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') ); +// This allows the web app to trigger skipWaiting via +// registration.waiting.postMessage({type: 'SKIP_WAITING'}) +self.addEventListener('message', event => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); + // Any other custom service worker logic can go here. From a893129ee776db3a5b2165962af25c51b666eaee Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Tue, 23 Jun 2020 12:42:39 -0400 Subject: [PATCH 11/25] Added a comment --- packages/cra-template-typescript/template/src/service-worker.ts | 2 ++ packages/cra-template/template/src/service-worker.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/cra-template-typescript/template/src/service-worker.ts b/packages/cra-template-typescript/template/src/service-worker.ts index e2a3f813eb0..685fe788c04 100644 --- a/packages/cra-template-typescript/template/src/service-worker.ts +++ b/packages/cra-template-typescript/template/src/service-worker.ts @@ -15,6 +15,8 @@ clientsClaim(); // Precache all of the assets generated by your build process. // Their URLs are injected into the manifest variable below. +// This variable must be present somewhere in your service worker file, +// even if you decide not to use precaching. See https://cra.link/PWA // eslint-disable-next-line no-restricted-globals precacheAndRoute(self.__WB_MANIFEST); diff --git a/packages/cra-template/template/src/service-worker.js b/packages/cra-template/template/src/service-worker.js index feee29272d3..ff6990de0f1 100644 --- a/packages/cra-template/template/src/service-worker.js +++ b/packages/cra-template/template/src/service-worker.js @@ -13,6 +13,8 @@ clientsClaim(); // Precache all of the assets generated by your build process. // Their URLs are injected into the manifest variable below. +// This variable must be present somewhere in your service worker file, +// even if you decide not to use precaching. See https://cra.link/PWA // eslint-disable-next-line no-restricted-globals precacheAndRoute(self.__WB_MANIFEST); From f96d1d2d8cdb5ef3c2fe0217eec4f9b219e040e7 Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Fri, 26 Jun 2020 08:44:43 -0700 Subject: [PATCH 12/25] Add back web vitals to typescript template --- packages/cra-template-typescript/template/src/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/cra-template-typescript/template/src/index.tsx b/packages/cra-template-typescript/template/src/index.tsx index 4e9f99fc3a2..b00b901a4c7 100644 --- a/packages/cra-template-typescript/template/src/index.tsx +++ b/packages/cra-template-typescript/template/src/index.tsx @@ -16,3 +16,8 @@ ReactDOM.render( // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://cra.link/PWA serviceWorkerRegistration.unregister(); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); From c674f0182cc6dc024c476ba74e2190d2b9daa112 Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Fri, 26 Jun 2020 08:51:28 -0700 Subject: [PATCH 13/25] Add web vitals back to javascript template --- packages/cra-template-typescript/template/src/index.tsx | 6 +++--- packages/cra-template/template/src/index.js | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/cra-template-typescript/template/src/index.tsx b/packages/cra-template-typescript/template/src/index.tsx index b00b901a4c7..e459b13323b 100644 --- a/packages/cra-template-typescript/template/src/index.tsx +++ b/packages/cra-template-typescript/template/src/index.tsx @@ -17,7 +17,7 @@ ReactDOM.render( // Learn more about service workers: https://cra.link/PWA serviceWorkerRegistration.unregister(); -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); diff --git a/packages/cra-template/template/src/index.js b/packages/cra-template/template/src/index.js index 4e9f99fc3a2..e459b13323b 100644 --- a/packages/cra-template/template/src/index.js +++ b/packages/cra-template/template/src/index.js @@ -16,3 +16,8 @@ ReactDOM.render( // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://cra.link/PWA serviceWorkerRegistration.unregister(); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); From d22c7dc312a36da53ed87de8506324a3eb9cfc54 Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Fri, 26 Jun 2020 08:57:17 -0700 Subject: [PATCH 14/25] Change references of CRA 3 to 4 --- docusaurus/docs/making-a-progressive-web-app.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/making-a-progressive-web-app.md b/docusaurus/docs/making-a-progressive-web-app.md index 0704502ebdd..6a9266f0c62 100644 --- a/docusaurus/docs/making-a-progressive-web-app.md +++ b/docusaurus/docs/making-a-progressive-web-app.md @@ -50,7 +50,7 @@ or unreliable network. ## Customization -Starting with Create React App 3, you have full control over customizing the +Starting with Create React App 4, you have full control over customizing the logic in this service worker, by making changes to [`src/service-worker.js`](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/src/service-worker.js). You can use @@ -116,7 +116,7 @@ following into account: 1. By default, the generated service worker file will not intercept or cache any cross-origin traffic, like HTTP [API requests](integrating-with-an-api-backend.md), images, or embeds loaded from a different domain. Starting with Create - React App 3, this can be customized, as explained above. + React App 4, this can be customized, as explained above. ## Progressive Web App Metadata From 563589c61c57b9b5f93f392b82fbd0df02d2ede4 Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Fri, 26 Jun 2020 09:03:19 -0700 Subject: [PATCH 15/25] Add webworker lib ref --- .../template/src/service-worker.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/cra-template-typescript/template/src/service-worker.ts b/packages/cra-template-typescript/template/src/service-worker.ts index 685fe788c04..b209a80a577 100644 --- a/packages/cra-template-typescript/template/src/service-worker.ts +++ b/packages/cra-template-typescript/template/src/service-worker.ts @@ -1,3 +1,5 @@ +/// + // This service worker can be customized! // See https://developers.google.com/web/tools/workbox/modules // for the list of available Workbox modules, or add any other @@ -7,9 +9,9 @@ declare const self: ServiceWorkerGlobalScope; -import {clientsClaim} from 'workbox-core'; -import {precacheAndRoute, createHandlerBoundToURL} from 'workbox-precaching'; -import {registerRoute} from 'workbox-routing'; +import { clientsClaim } from 'workbox-core'; +import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; +import { registerRoute } from 'workbox-routing'; clientsClaim(); @@ -18,7 +20,7 @@ clientsClaim(); // This variable must be present somewhere in your service worker file, // even if you decide not to use precaching. See https://cra.link/PWA // eslint-disable-next-line no-restricted-globals -precacheAndRoute(self.__WB_MANIFEST); +precacheAndRoute(self.__WB_MANIFEST); // Set up App Shell-style routing, so that all navigation requests // are fulfilled with your index.html shell. Learn more at @@ -26,7 +28,7 @@ precacheAndRoute(self.__WB_MANIFEST); const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); registerRoute( // Return false to exempt requests from being fulfilled by index.html. - ({request, url}: {request: Request, url: URL}) => { + ({ request, url }: { request: Request; url: URL }) => { // If this isn't a navigation, skip. if (request.mode !== 'navigate') { return false; @@ -46,7 +48,7 @@ registerRoute( // Return true to signal that we want to use the handler. return true; }, - createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'), + createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') ); // This allows the web app to trigger skipWaiting via From 49b27d55014b9dff2783c37e2c7986ece5f233b3 Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Fri, 26 Jun 2020 09:14:48 -0700 Subject: [PATCH 16/25] Fix TypeScript errors in service-worker.ts --- .../cra-template-typescript/template/src/service-worker.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/cra-template-typescript/template/src/service-worker.ts b/packages/cra-template-typescript/template/src/service-worker.ts index b209a80a577..e7547f172a0 100644 --- a/packages/cra-template-typescript/template/src/service-worker.ts +++ b/packages/cra-template-typescript/template/src/service-worker.ts @@ -7,19 +7,18 @@ // You can also remove this file if you'd prefer not to use a // service worker, and the Workbox build step will be skipped. -declare const self: ServiceWorkerGlobalScope; - import { clientsClaim } from 'workbox-core'; import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; import { registerRoute } from 'workbox-routing'; +declare const self: ServiceWorkerGlobalScope; + clientsClaim(); // Precache all of the assets generated by your build process. // Their URLs are injected into the manifest variable below. // This variable must be present somewhere in your service worker file, // even if you decide not to use precaching. See https://cra.link/PWA -// eslint-disable-next-line no-restricted-globals precacheAndRoute(self.__WB_MANIFEST); // Set up App Shell-style routing, so that all navigation requests @@ -53,7 +52,7 @@ registerRoute( // This allows the web app to trigger skipWaiting via // registration.waiting.postMessage({type: 'SKIP_WAITING'}) -self.addEventListener('message', (event: MessageEvent) => { +self.addEventListener('message', event => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } From f8245c97644d892c21c3bac73ce54f663fc8df2e Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Fri, 26 Jun 2020 09:21:00 -0700 Subject: [PATCH 17/25] Add back eslint disable comment It appeared to be needed after all? --- packages/cra-template-typescript/template/src/service-worker.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cra-template-typescript/template/src/service-worker.ts b/packages/cra-template-typescript/template/src/service-worker.ts index e7547f172a0..c05cb8da040 100644 --- a/packages/cra-template-typescript/template/src/service-worker.ts +++ b/packages/cra-template-typescript/template/src/service-worker.ts @@ -19,6 +19,7 @@ clientsClaim(); // Their URLs are injected into the manifest variable below. // This variable must be present somewhere in your service worker file, // even if you decide not to use precaching. See https://cra.link/PWA +// eslint-disable-next-line no-restricted-globals precacheAndRoute(self.__WB_MANIFEST); // Set up App Shell-style routing, so that all navigation requests From f3adc737e49f7a3da06f90ecd514dd6d4216047a Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Fri, 26 Jun 2020 09:27:35 -0700 Subject: [PATCH 18/25] Ignore restricted-globals across entire file --- .../cra-template-typescript/template/src/service-worker.ts | 2 +- packages/cra-template/template/src/service-worker.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cra-template-typescript/template/src/service-worker.ts b/packages/cra-template-typescript/template/src/service-worker.ts index c05cb8da040..939db67f014 100644 --- a/packages/cra-template-typescript/template/src/service-worker.ts +++ b/packages/cra-template-typescript/template/src/service-worker.ts @@ -1,4 +1,5 @@ /// +// eslint-disable no-restricted-globals // This service worker can be customized! // See https://developers.google.com/web/tools/workbox/modules @@ -19,7 +20,6 @@ clientsClaim(); // Their URLs are injected into the manifest variable below. // This variable must be present somewhere in your service worker file, // even if you decide not to use precaching. See https://cra.link/PWA -// eslint-disable-next-line no-restricted-globals precacheAndRoute(self.__WB_MANIFEST); // Set up App Shell-style routing, so that all navigation requests diff --git a/packages/cra-template/template/src/service-worker.js b/packages/cra-template/template/src/service-worker.js index ff6990de0f1..7ee3952c83b 100644 --- a/packages/cra-template/template/src/service-worker.js +++ b/packages/cra-template/template/src/service-worker.js @@ -1,3 +1,5 @@ +// eslint-disable no-restricted-globals + // This service worker can be customized! // See https://developers.google.com/web/tools/workbox/modules // for the list of available Workbox modules, or add any other @@ -15,7 +17,6 @@ clientsClaim(); // Their URLs are injected into the manifest variable below. // This variable must be present somewhere in your service worker file, // even if you decide not to use precaching. See https://cra.link/PWA -// eslint-disable-next-line no-restricted-globals precacheAndRoute(self.__WB_MANIFEST); // Set up App Shell-style routing, so that all navigation requests From 94d6494a9a8bf41742e18b734aeb64f746e3bc09 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Fri, 26 Jun 2020 13:54:07 -0400 Subject: [PATCH 19/25] Runtime caching --- .../docs/making-a-progressive-web-app.md | 12 +++++++++-- .../template/src/service-worker.ts | 20 +++++++++++++++++- .../template/src/service-worker.js | 21 ++++++++++++++++++- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/docusaurus/docs/making-a-progressive-web-app.md b/docusaurus/docs/making-a-progressive-web-app.md index 6a9266f0c62..487e252b4a0 100644 --- a/docusaurus/docs/making-a-progressive-web-app.md +++ b/docusaurus/docs/making-a-progressive-web-app.md @@ -41,13 +41,21 @@ However, they [can make debugging deployments more challenging](https://github.c The [`workbox-webpack-plugin`](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin) is integrated into production configuration, and it will take care of compiling a service worker file that will automatically -precache all of your local assets and keep them up to date as you deploy updates. +precache all of your `webpack`-generated assets and keep them up to date as you +deploy updates. The service worker will use a [cache-first strategy](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network) -for handling all requests for local assets, including +for handling all requests for `webpack`-generated assets, including [navigation requests](https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests) for your HTML, ensuring that your web app is consistently fast, even on a slow or unreliable network. +Note: Resources that are not generated by `webpack`, such as static files that are +copied over from your local +[`public/` directory](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/public/) +or third-party resources, will not be precached. You can optionally set up Workbox +[routes](https://developers.google.com/web/tools/workbox/guides/route-requests) +to apply the runtime caching strategy of your choice to those resources. + ## Customization Starting with Create React App 4, you have full control over customizing the diff --git a/packages/cra-template-typescript/template/src/service-worker.ts b/packages/cra-template-typescript/template/src/service-worker.ts index 939db67f014..84892530d1a 100644 --- a/packages/cra-template-typescript/template/src/service-worker.ts +++ b/packages/cra-template-typescript/template/src/service-worker.ts @@ -1,5 +1,5 @@ /// -// eslint-disable no-restricted-globals +/* eslint-disable no-restricted-globals */ // This service worker can be customized! // See https://developers.google.com/web/tools/workbox/modules @@ -9,8 +9,10 @@ // service worker, and the Workbox build step will be skipped. import { clientsClaim } from 'workbox-core'; +import { ExpirationPlugin } from 'workbox-expiration'; import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; import { registerRoute } from 'workbox-routing'; +import { StaleWhileRevalidate} from 'workbox-strategies'; declare const self: ServiceWorkerGlobalScope; @@ -51,6 +53,22 @@ registerRoute( createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') ); +// An example runtime caching route for requests that aren't handled by the +// precache, in this case same-origin .png requests like those from in public/ +registerRoute( + // Add in any other file extensions or routing criteria as needed. + ({url}) => url.origin === self.location.origin && url.pathname.endsWith('.png'), + // Customize this strategy as needed, e.g., by changing to CacheFirst. + new StaleWhileRevalidate({ + cacheName: 'images', + plugins: [ + // Ensure that once this runtime cache reaches a maximum size the + // least-recently used images are removed. + new ExpirationPlugin({maxEntries: 50}), + ], + }), +); + // This allows the web app to trigger skipWaiting via // registration.waiting.postMessage({type: 'SKIP_WAITING'}) self.addEventListener('message', event => { diff --git a/packages/cra-template/template/src/service-worker.js b/packages/cra-template/template/src/service-worker.js index 7ee3952c83b..234d844f16d 100644 --- a/packages/cra-template/template/src/service-worker.js +++ b/packages/cra-template/template/src/service-worker.js @@ -1,4 +1,4 @@ -// eslint-disable no-restricted-globals +/* eslint-disable no-restricted-globals */ // This service worker can be customized! // See https://developers.google.com/web/tools/workbox/modules @@ -8,8 +8,10 @@ // service worker, and the Workbox build step will be skipped. import { clientsClaim } from 'workbox-core'; +import { ExpirationPlugin } from 'workbox-expiration'; import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; import { registerRoute } from 'workbox-routing'; +import { StaleWhileRevalidate } from 'workbox-strategies'; clientsClaim(); @@ -48,6 +50,23 @@ registerRoute( createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') ); +// An example runtime caching route for requests that aren't handled by the +// precache, in this case same-origin .png requests like those from in public/ +registerRoute( + // Add in any other file extensions or routing criteria as needed. + ({ url }) => + url.origin === self.location.origin && url.pathname.endsWith('.png'), + // Customize this strategy as needed, e.g., by changing to CacheFirst. + new StaleWhileRevalidate({ + cacheName: 'images', + plugins: [ + // Ensure that once this runtime cache reaches a maximum size the + // least-recently used images are removed. + new ExpirationPlugin({ maxEntries: 50 }), + ], + }) +); + // This allows the web app to trigger skipWaiting via // registration.waiting.postMessage({type: 'SKIP_WAITING'}) self.addEventListener('message', event => { From d949faa26a82d0b4d2f4cfefa5b229efae238065 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Mon, 29 Jun 2020 13:55:50 -0400 Subject: [PATCH 20/25] Explicitly depend on Workbox libs --- packages/react-scripts/package.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index e0395e27558..8c35d1e50bf 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -82,6 +82,18 @@ "webpack": "4.43.0", "webpack-dev-server": "3.11.0", "webpack-manifest-plugin": "2.2.0", + "workbox-background-sync": "^5.1.3", + "workbox-broadcast-update": "^5.1.3", + "workbox-cacheable-response": "^5.1.3", + "workbox-core": "^5.1.3", + "workbox-expiration": "^5.1.3", + "workbox-google-analytics": "^5.1.3", + "workbox-navigation-preload": "^5.1.3", + "workbox-precaching": "^5.1.3", + "workbox-range-requests": "^5.1.3", + "workbox-routing": "^5.1.3", + "workbox-strategies": "^5.1.3", + "workbox-streams": "^5.1.3", "workbox-webpack-plugin": "5.1.3" }, "devDependencies": { From b02ebb41457dda5eeb820a6a3094aa65ae8e9738 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Mon, 13 Jul 2020 10:43:22 -0400 Subject: [PATCH 21/25] Remove SW and package.json deps --- .../template/src/service-worker.ts | 80 ------------------- .../template/src/service-worker.js | 78 ------------------ packages/react-scripts/package.json | 12 --- 3 files changed, 170 deletions(-) delete mode 100644 packages/cra-template-typescript/template/src/service-worker.ts delete mode 100644 packages/cra-template/template/src/service-worker.js diff --git a/packages/cra-template-typescript/template/src/service-worker.ts b/packages/cra-template-typescript/template/src/service-worker.ts deleted file mode 100644 index 84892530d1a..00000000000 --- a/packages/cra-template-typescript/template/src/service-worker.ts +++ /dev/null @@ -1,80 +0,0 @@ -/// -/* eslint-disable no-restricted-globals */ - -// This service worker can be customized! -// See https://developers.google.com/web/tools/workbox/modules -// for the list of available Workbox modules, or add any other -// code you'd like. -// You can also remove this file if you'd prefer not to use a -// service worker, and the Workbox build step will be skipped. - -import { clientsClaim } from 'workbox-core'; -import { ExpirationPlugin } from 'workbox-expiration'; -import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; -import { registerRoute } from 'workbox-routing'; -import { StaleWhileRevalidate} from 'workbox-strategies'; - -declare const self: ServiceWorkerGlobalScope; - -clientsClaim(); - -// Precache all of the assets generated by your build process. -// Their URLs are injected into the manifest variable below. -// This variable must be present somewhere in your service worker file, -// even if you decide not to use precaching. See https://cra.link/PWA -precacheAndRoute(self.__WB_MANIFEST); - -// Set up App Shell-style routing, so that all navigation requests -// are fulfilled with your index.html shell. Learn more at -// https://developers.google.com/web/fundamentals/architecture/app-shell -const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); -registerRoute( - // Return false to exempt requests from being fulfilled by index.html. - ({ request, url }: { request: Request; url: URL }) => { - // If this isn't a navigation, skip. - if (request.mode !== 'navigate') { - return false; - } - - // If this is a URL that starts with /_, skip. - if (url.pathname.startsWith('/_')) { - return false; - } - - // If this looks like a URL for a resource, because it contains - // a file extension, skip. - if (url.pathname.match(fileExtensionRegexp)) { - return false; - } - - // Return true to signal that we want to use the handler. - return true; - }, - createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') -); - -// An example runtime caching route for requests that aren't handled by the -// precache, in this case same-origin .png requests like those from in public/ -registerRoute( - // Add in any other file extensions or routing criteria as needed. - ({url}) => url.origin === self.location.origin && url.pathname.endsWith('.png'), - // Customize this strategy as needed, e.g., by changing to CacheFirst. - new StaleWhileRevalidate({ - cacheName: 'images', - plugins: [ - // Ensure that once this runtime cache reaches a maximum size the - // least-recently used images are removed. - new ExpirationPlugin({maxEntries: 50}), - ], - }), -); - -// This allows the web app to trigger skipWaiting via -// registration.waiting.postMessage({type: 'SKIP_WAITING'}) -self.addEventListener('message', event => { - if (event.data && event.data.type === 'SKIP_WAITING') { - self.skipWaiting(); - } -}); - -// Any other custom service worker logic can go here. diff --git a/packages/cra-template/template/src/service-worker.js b/packages/cra-template/template/src/service-worker.js deleted file mode 100644 index 234d844f16d..00000000000 --- a/packages/cra-template/template/src/service-worker.js +++ /dev/null @@ -1,78 +0,0 @@ -/* eslint-disable no-restricted-globals */ - -// This service worker can be customized! -// See https://developers.google.com/web/tools/workbox/modules -// for the list of available Workbox modules, or add any other -// code you'd like. -// You can also remove this file if you'd prefer not to use a -// service worker, and the Workbox build step will be skipped. - -import { clientsClaim } from 'workbox-core'; -import { ExpirationPlugin } from 'workbox-expiration'; -import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; -import { registerRoute } from 'workbox-routing'; -import { StaleWhileRevalidate } from 'workbox-strategies'; - -clientsClaim(); - -// Precache all of the assets generated by your build process. -// Their URLs are injected into the manifest variable below. -// This variable must be present somewhere in your service worker file, -// even if you decide not to use precaching. See https://cra.link/PWA -precacheAndRoute(self.__WB_MANIFEST); - -// Set up App Shell-style routing, so that all navigation requests -// are fulfilled with your index.html shell. Learn more at -// https://developers.google.com/web/fundamentals/architecture/app-shell -const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); -registerRoute( - // Return false to exempt requests from being fulfilled by index.html. - ({ request, url }) => { - // If this isn't a navigation, skip. - if (request.mode !== 'navigate') { - return false; - } - - // If this is a URL that starts with /_, skip. - if (url.pathname.startsWith('/_')) { - return false; - } - - // If this looks like a URL for a resource, because it contains - // a file extension, skip. - if (url.pathname.match(fileExtensionRegexp)) { - return false; - } - - // Return true to signal that we want to use the handler. - return true; - }, - createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') -); - -// An example runtime caching route for requests that aren't handled by the -// precache, in this case same-origin .png requests like those from in public/ -registerRoute( - // Add in any other file extensions or routing criteria as needed. - ({ url }) => - url.origin === self.location.origin && url.pathname.endsWith('.png'), - // Customize this strategy as needed, e.g., by changing to CacheFirst. - new StaleWhileRevalidate({ - cacheName: 'images', - plugins: [ - // Ensure that once this runtime cache reaches a maximum size the - // least-recently used images are removed. - new ExpirationPlugin({ maxEntries: 50 }), - ], - }) -); - -// This allows the web app to trigger skipWaiting via -// registration.waiting.postMessage({type: 'SKIP_WAITING'}) -self.addEventListener('message', event => { - if (event.data && event.data.type === 'SKIP_WAITING') { - self.skipWaiting(); - } -}); - -// Any other custom service worker logic can go here. diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 8c35d1e50bf..e0395e27558 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -82,18 +82,6 @@ "webpack": "4.43.0", "webpack-dev-server": "3.11.0", "webpack-manifest-plugin": "2.2.0", - "workbox-background-sync": "^5.1.3", - "workbox-broadcast-update": "^5.1.3", - "workbox-cacheable-response": "^5.1.3", - "workbox-core": "^5.1.3", - "workbox-expiration": "^5.1.3", - "workbox-google-analytics": "^5.1.3", - "workbox-navigation-preload": "^5.1.3", - "workbox-precaching": "^5.1.3", - "workbox-range-requests": "^5.1.3", - "workbox-routing": "^5.1.3", - "workbox-strategies": "^5.1.3", - "workbox-streams": "^5.1.3", "workbox-webpack-plugin": "5.1.3" }, "devDependencies": { From 7f768c9a2dfe546f1da4ec14b1537a6707a6872f Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Mon, 13 Jul 2020 11:27:39 -0400 Subject: [PATCH 22/25] WIP update to README. --- .../docs/making-a-progressive-web-app.md | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/docusaurus/docs/making-a-progressive-web-app.md b/docusaurus/docs/making-a-progressive-web-app.md index 487e252b4a0..c172c82134f 100644 --- a/docusaurus/docs/making-a-progressive-web-app.md +++ b/docusaurus/docs/making-a-progressive-web-app.md @@ -5,18 +5,15 @@ title: Making a Progressive Web App The production build has all the tools necessary to generate a first-class [Progressive Web App](https://developers.google.com/web/progressive-web-apps/), -but **the offline/cache-first behavior is opt-in only**. By default, -the build process will compile a service worker file, but it will not be -registered, so it will not take control of your production web app. +but **the offline/cache-first behavior is opt-in only**. -If you know that you won't be using service workers, you can speed up the build -process by removing the -[`src/service-worker.js`](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/src/service-worker.js) -file from your local project. This will skip the step that compiles your -service worker at build time. +Starting with Create React App 4, you can add a `src/service-worker.js` file to your project to use the built-in support for [Workbox](https://developers.google.com/web/tools/workbox/)'s [`InjectManifest`](https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-webpack-plugin.InjectManifest) plugin, which will [compile](https://developers.google.com/web/tools/workbox/guides/using-bundlers) your service worker and inject into it a list of URLs to [precache](https://developers.google.com/web/tools/workbox/guides/precache-files). -In order to opt-in to the offline-first behavior, developers should look for the -following in their [`src/index.js`](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/src/index.js) file: +There a + +If you know that you won't be using service workers, or if you'd prefer to use a different approach to creating your service worker, don't create a `src/service-worker.js` file. The `InjectManifest` plugin won't be run in that case. + +In addition to creating your local `src/service-worker.js` file, it needs to be registered before it will be used. In order to opt-in to the offline-first behavior, developers should look for the following in their [`src/index.js`](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/src/index.js) file: ```js // If you want your app to work offline and load faster, you can change @@ -32,11 +29,11 @@ As the comment states, switching `serviceWorker.unregister()` to Offline-first Progressive Web Apps are faster and more reliable than traditional web pages, and provide an engaging mobile experience: -- All static site assets are cached so that your page loads fast on subsequent visits, regardless of network connectivity (such as 2G or 3G). Updates are downloaded in the background. +- All static site assets that are a part of your `webpack` build are cached so that your page loads fast on subsequent visits, regardless of network connectivity (such as 2G or 3G). Updates are downloaded in the background. - Your app will work regardless of network state, even if offline. This means your users will be able to use your app at 10,000 feet and on the subway. - On mobile devices, your app can be added directly to the user's home screen, app icon and all. This eliminates the need for the app store. -However, they [can make debugging deployments more challenging](https://github.com/facebook/create-react-app/issues/2398) so, starting with Create React App 2, service workers are opt-in. +However, they [can make debugging deployments more challenging](https://github.com/facebook/create-react-app/issues/2398). The [`workbox-webpack-plugin`](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin) is integrated into production configuration, @@ -59,10 +56,8 @@ to apply the runtime caching strategy of your choice to those resources. ## Customization Starting with Create React App 4, you have full control over customizing the -logic in this service worker, by making changes to -[`src/service-worker.js`](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/src/service-worker.js). -You can use -[additional modules](https://developers.google.com/web/tools/workbox/modules) +logic in this service worker, by creating a `src/service-worker.js` file. +You can use [additional modules](https://developers.google.com/web/tools/workbox/modules) from the Workbox project, add in a push notification library, or remove some of the default caching logic. The one requirement is that you keep `self.__WB_MANIFEST` somewhere in your file, as the Workbox compilation plugin From 34a46dcf7e9d33ef983c9be8c6a997b90e1feea1 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Mon, 13 Jul 2020 11:38:59 -0400 Subject: [PATCH 23/25] Updates to the docs. --- .../docs/making-a-progressive-web-app.md | 92 +++++++++++++------ 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/docusaurus/docs/making-a-progressive-web-app.md b/docusaurus/docs/making-a-progressive-web-app.md index c172c82134f..a25c9c02ba2 100644 --- a/docusaurus/docs/making-a-progressive-web-app.md +++ b/docusaurus/docs/making-a-progressive-web-app.md @@ -7,13 +7,40 @@ The production build has all the tools necessary to generate a first-class [Progressive Web App](https://developers.google.com/web/progressive-web-apps/), but **the offline/cache-first behavior is opt-in only**. -Starting with Create React App 4, you can add a `src/service-worker.js` file to your project to use the built-in support for [Workbox](https://developers.google.com/web/tools/workbox/)'s [`InjectManifest`](https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-webpack-plugin.InjectManifest) plugin, which will [compile](https://developers.google.com/web/tools/workbox/guides/using-bundlers) your service worker and inject into it a list of URLs to [precache](https://developers.google.com/web/tools/workbox/guides/precache-files). +Starting with Create React App 4, you can add a `src/service-worker.js` file to +your project to use the built-in support for +[Workbox](https://developers.google.com/web/tools/workbox/)'s +[`InjectManifest`](https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-webpack-plugin.InjectManifest) +plugin, which will +[compile](https://developers.google.com/web/tools/workbox/guides/using-bundlers) +your service worker and inject into it a list of URLs to +[precache](https://developers.google.com/web/tools/workbox/guides/precache-files). + +If you start a new project using one of the PWA [custom +templates](https://create-react-app.dev/docs/custom-templates/), you'll get a +`src/service-worker.js` file that serves as a good starting point for an +offline-first service worker: + +```sh +npx create-react-app my-app --template cra-template-pwa +``` + +The TypeScript equivalent is: -There a +```sh +npx create-react-app my-app --template cra-template-pwa-typescript +``` -If you know that you won't be using service workers, or if you'd prefer to use a different approach to creating your service worker, don't create a `src/service-worker.js` file. The `InjectManifest` plugin won't be run in that case. +If you know that you won't be using service workers, or if you'd prefer to use a +different approach to creating your service worker, don't create a +`src/service-worker.js` file. The `InjectManifest` plugin won't be run in that +case. -In addition to creating your local `src/service-worker.js` file, it needs to be registered before it will be used. In order to opt-in to the offline-first behavior, developers should look for the following in their [`src/index.js`](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/src/index.js) file: +In addition to creating your local `src/service-worker.js` file, it needs to be +registered before it will be used. In order to opt-in to the offline-first +behavior, developers should look for the following in their +[`src/index.js`](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/src/index.js) +file: ```js // If you want your app to work offline and load faster, you can change @@ -27,22 +54,29 @@ As the comment states, switching `serviceWorker.unregister()` to ## Why Opt-in? -Offline-first Progressive Web Apps are faster and more reliable than traditional web pages, and provide an engaging mobile experience: - -- All static site assets that are a part of your `webpack` build are cached so that your page loads fast on subsequent visits, regardless of network connectivity (such as 2G or 3G). Updates are downloaded in the background. -- Your app will work regardless of network state, even if offline. This means your users will be able to use your app at 10,000 feet and on the subway. -- On mobile devices, your app can be added directly to the user's home screen, app icon and all. This eliminates the need for the app store. - -However, they [can make debugging deployments more challenging](https://github.com/facebook/create-react-app/issues/2398). - -The [`workbox-webpack-plugin`](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin) -is integrated into production configuration, -and it will take care of compiling a service worker file that will automatically -precache all of your `webpack`-generated assets and keep them up to date as you -deploy updates. -The service worker will use a [cache-first strategy](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network) -for handling all requests for `webpack`-generated assets, including -[navigation requests](https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests) +Offline-first Progressive Web Apps are faster and more reliable than traditional +web pages, and provide an engaging mobile experience: + +- All static site assets that are a part of your `webpack` build are cached so + that your page loads fast on subsequent visits, regardless of network + connectivity (such as 2G or 3G). Updates are downloaded in the background. +- Your app will work regardless of network state, even if offline. This means + your users will be able to use your app at 10,000 feet and on the subway. +- On mobile devices, your app can be added directly to the user's home screen, + app icon and all. This eliminates the need for the app store. + +However, they [can make debugging deployments more +challenging](https://github.com/facebook/create-react-app/issues/2398). + +The +[`workbox-webpack-plugin`](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin) +is integrated into production configuration, and it will take care of compiling +a service worker file that will automatically precache all of your +`webpack`-generated assets and keep them up to date as you deploy updates. The +service worker will use a [cache-first +strategy](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network) +for handling all requests for `webpack`-generated assets, including [navigation +requests](https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests) for your HTML, ensuring that your web app is consistently fast, even on a slow or unreliable network. @@ -56,14 +90,16 @@ to apply the runtime caching strategy of your choice to those resources. ## Customization Starting with Create React App 4, you have full control over customizing the -logic in this service worker, by creating a `src/service-worker.js` file. -You can use [additional modules](https://developers.google.com/web/tools/workbox/modules) -from the Workbox project, add in a push notification library, or remove some of -the default caching logic. The one requirement is that you keep -`self.__WB_MANIFEST` somewhere in your file, as the Workbox compilation plugin -checks for this value when generating a manifest of URLs to precache. If you -would prefer not to use precaching, you can just assign `self.__WB_MANIFEST` -to a variable that will be ignored, like: +logic in this service worker, by creating your own `src/service-worker.js` file, +or customizing the one added by the `cra-template-pwa` (or +`cra-template-pwa-typescript`) template. You can use [additional +modules](https://developers.google.com/web/tools/workbox/modules) from the +Workbox project, add in a push notification library, or remove some of the +default caching logic. The one requirement is that you keep `self.__WB_MANIFEST` +somewhere in your file, as the Workbox compilation plugin checks for this value +when generating a manifest of URLs to precache. If you would prefer not to use +precaching, you can just assign `self.__WB_MANIFEST` to a variable that will be +ignored, like: ```js // eslint-disable-next-line no-restricted-globals From 419a8833c4461f81ddb03134a4a8aa6debd4a771 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Mon, 13 Jul 2020 12:40:23 -0400 Subject: [PATCH 24/25] Explicitly require InjectManifest --- packages/react-scripts/config/webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 1dba35cf903..56ed80cee17 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -22,7 +22,7 @@ const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const safePostCssParser = require('postcss-safe-parser'); const ManifestPlugin = require('webpack-manifest-plugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); -const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); +const {InjectManifest} = require('workbox-webpack-plugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); @@ -696,7 +696,7 @@ module.exports = function (webpackEnv) { // the HTML & assets that are part of the webpack build. isEnvProduction && fs.existsSync(swSrc) && - new WorkboxWebpackPlugin.InjectManifest({ + new InjectManifest({ swSrc, dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./, exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/], From 991e683dd8e19fba535a57f26d3ce31991510245 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Mon, 13 Jul 2020 13:04:33 -0400 Subject: [PATCH 25/25] Switch back. --- packages/react-scripts/config/webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 56ed80cee17..1dba35cf903 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -22,7 +22,7 @@ const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const safePostCssParser = require('postcss-safe-parser'); const ManifestPlugin = require('webpack-manifest-plugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); -const {InjectManifest} = require('workbox-webpack-plugin'); +const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); @@ -696,7 +696,7 @@ module.exports = function (webpackEnv) { // the HTML & assets that are part of the webpack build. isEnvProduction && fs.existsSync(swSrc) && - new InjectManifest({ + new WorkboxWebpackPlugin.InjectManifest({ swSrc, dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./, exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/],