Skip to content

Commit

Permalink
Switch to the Workbox InjectManifest plugin (facebook#9205)
Browse files Browse the repository at this point in the history
Co-authored-by: Ian Schmitz <[email protected]>
  • Loading branch information
jeffposnick and ianschmitz committed Jul 22, 2020
1 parent 759696d commit 8e720ae
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 39 deletions.
107 changes: 87 additions & 20 deletions docusaurus/docs/making-a-progressive-web-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions packages/cra-template-typescript/template/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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))
Expand Down
4 changes: 2 additions & 2 deletions packages/cra-template/template/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions packages/react-scripts/config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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('.'),
Expand Down Expand Up @@ -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('.'),
Expand Down
22 changes: 8 additions & 14 deletions packages/react-scripts/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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$/;
Expand Down Expand Up @@ -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 &&
Expand Down
2 changes: 1 addition & 1 deletion packages/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 8e720ae

Please sign in to comment.