diff --git a/docusaurus/docs/making-a-progressive-web-app.md b/docusaurus/docs/making-a-progressive-web-app.md index ba9802e6088..a25c9c02ba2 100644 --- a/docusaurus/docs/making-a-progressive-web-app.md +++ b/docusaurus/docs/making-a-progressive-web-app.md @@ -5,12 +5,42 @@ 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 -registered, so it will not take control of your production web app. +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). + +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: + +```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. -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 @@ -24,23 +54,59 @@ 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: +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. -- 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. -- 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. +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 +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: -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. +```js +// eslint-disable-next-line no-restricted-globals +const ignored = self.__WB_MANIFEST; -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 -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 -[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. +// Your custom service worker code goes here. +``` ## Offline-First Considerations @@ -88,7 +154,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 4, this can be customized, as explained above. ## Progressive Web App Metadata diff --git a/packages/cra-template-typescript/template/src/index.tsx b/packages/cra-template-typescript/template/src/index.tsx index bdf2dd80541..e459b13323b 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'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( @@ -15,7 +15,7 @@ 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(); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) 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/template/src/index.js b/packages/cra-template/template/src/index.js index bdf2dd80541..e459b13323b 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'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( @@ -15,7 +15,7 @@ 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(); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) 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/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 11d81b7f5a7..516513081b2 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -72,6 +72,7 @@ module.exports = { testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), + swSrc: resolveModule(resolveApp, 'src/service-worker'), publicUrlOrPath, }; @@ -94,6 +95,7 @@ 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('.'), @@ -129,6 +131,7 @@ 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('.'), diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 2c32b66ac9a..1dba35cf903 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$/; @@ -692,20 +695,11 @@ module.exports = function (webpackEnv) { // 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, - 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('/[^/?]+\\.[^/]+$'), - ], + fs.existsSync(swSrc) && + new WorkboxWebpackPlugin.InjectManifest({ + swSrc, + dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./, + exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/], }), // TypeScript type checking useTypeScript && diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 8c1d20dcc17..e0395e27558 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",