Skip to content

Commit c7c3a52

Browse files
jeffposnickgaearon
authored andcommitted
PWA-ification, via SWPrecacheWebpackPlugin + manifest.json (#1728)
* sw-precache-webpack-plugin, SW registration, manifest.json * Documentation + a few SW tweaks. * Added an unregister method, too, just in case. * More info for the READMEs. * Add minify to SWPrecacheWebpackPlugin config * Fix SWPrecacheWebpackPlugin typo * Fix file references in READMEmd * Add instructions for testing service-worker locally * Review feedback * Some additional PWA metadata * Use sw-precache-webpack-plugin v0.9.1 for node >=4.0.0 support * Review feedback. * Add manifest.json context in a comment. * Fix typo * Downgrade to sw-precache-webpack-plugin 0.9.1 * Hide changes in README until 1.0 * Hide changes in User Guide until 1.0 * Hide more docs
1 parent e7c113e commit c7c3a52

File tree

8 files changed

+198
-1
lines changed

8 files changed

+198
-1
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ It correctly bundles React in production mode and optimizes the build for the be
101101

102102
The build is minified and the filenames include the hashes.<br>
103103
Your app is ready to be deployed!
104+
<!--TODO: uncoment and maybe revise
105+
A [service worker](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers) using an [offline-first caching strategy](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network) is automatically generated.<br>
106+
Your ([progressive web](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app)) app is ready to be deployed!-->
104107

105108
## User Guide
106109

@@ -162,6 +165,9 @@ Please refer to the [User Guide](https://github.com/facebookincubator/create-rea
162165
* Import CSS and image files directly from JavaScript.
163166
* Autoprefixed CSS, so you don’t need `-webkit` or other prefixes.
164167
* A `build` script to bundle JS, CSS, and images for production, with sourcemaps.
168+
<!--TODO: uncomment
169+
* An offline-first [service worker](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers) and a [web app manifest](https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/), meeting all the [Progressive Web App](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app) criteria.
170+
-->
165171

166172
**The feature set is intentionally limited**. It doesn’t support advanced features such as server rendering or CSS modules. The tool is also **non-configurable** because it is hard to provide a cohesive experience and easy updates across a set of tools when the user can tweak anything.
167173

packages/react-scripts/config/webpack.config.prod.js

+14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
1717
const ExtractTextPlugin = require('extract-text-webpack-plugin');
1818
const ManifestPlugin = require('webpack-manifest-plugin');
1919
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
20+
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
2021
const eslintFormatter = require('react-dev-utils/eslintFormatter');
2122
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
2223
const paths = require('./paths');
@@ -295,6 +296,19 @@ module.exports = {
295296
new ManifestPlugin({
296297
fileName: 'asset-manifest.json',
297298
}),
299+
// Generate a service worker script that will precache, and keep up to date,
300+
// the HTML & assets that are part of the Webpack build.
301+
new SWPrecacheWebpackPlugin({
302+
// By default, a cache-busting query parameter is appended to requests
303+
// used to populate the caches, to ensure the responses are fresh.
304+
// If a URL is already hashed by Webpack, then there is no concern
305+
// about it being stale, and the cache-busting can be skipped.
306+
dontCacheBustUrlsMatching: /\.\w{8}\./,
307+
filename: 'service-worker.js',
308+
minify: true,
309+
navigateFallback: publicUrl + '/index.html',
310+
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
311+
}),
298312
// Moment.js is an extremely popular library that bundles large locale files
299313
// by default due to how Webpack interprets its code. This is a practical
300314
// solution that requires the user to opt into importing specific locales.

packages/react-scripts/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"react-dev-utils": "^1.0.0",
5757
"react-error-overlay": "^1.0.0",
5858
"style-loader": "0.17.0",
59+
"sw-precache-webpack-plugin": "0.9.1",
5960
"url-loader": "0.5.8",
6061
"webpack": "2.5.1",
6162
"webpack-dev-server": "2.4.5",

packages/react-scripts/template/README.md

+114
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ You can find the most recent version of this guide [here](https://github.com/fac
6161
- [Editor Integration](#editor-integration)
6262
- [Developing Components in Isolation](#developing-components-in-isolation)
6363
- [Making a Progressive Web App](#making-a-progressive-web-app)
64+
<!-- todo: uncomment
65+
- [Offline-First Considerations](#offline-first-considerations)
66+
- [Progressive Web App Metadata](#progressive-web-app-metadata)
67+
-->
6468
- [Deployment](#deployment)
6569
- [Static Server](#static-server)
6670
- [Other Solutions](#other-solutions)
@@ -1216,6 +1220,103 @@ Learn more about React Storybook:
12161220

12171221
You can turn your React app into a [Progressive Web App](https://developers.google.com/web/progressive-web-apps/) by following the steps in [this repository](https://github.com/jeffposnick/create-react-pwa).
12181222

1223+
<!--
1224+
TODO: uncomment
1225+
By default, the production build is a fully functional, offline-first
1226+
[Progressive Web App](https://developers.google.com/web/progressive-web-apps/).
1227+
1228+
The [`sw-precache-webpack-plugin`](https://github.com/goldhand/sw-precache-webpack-plugin)
1229+
is integrated into [`webpack.config.prod.js`](../config/webpack.config.prod.js),
1230+
and it will take care of generating a service worker file that will automatically
1231+
precache all of your local assets and keep them up to date as you deploy updates.
1232+
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)
1233+
for handling all requests for local assets, including the initial HTML, ensuring
1234+
that you web app is reliably fast, even on a slow or unreliable network.
1235+
1236+
If you would prefer not to enable service workers prior to your initial
1237+
production deployment, then remove the call to `serviceWorkerRegistration.register()`
1238+
from [`src/index.js`](src/index.js).
1239+
1240+
If you had previously enabled service workers in your production deployment and
1241+
have decided that you would like to disable them for all your existing users,
1242+
you can swap out the call to `serviceWorkerRegistration.register()` in
1243+
[`src/index.js`](src/index.js) with a call to `serviceWorkerRegistration.unregister()`.
1244+
After the user visits a page that has `serviceWorkerRegistration.unregister()`,
1245+
the service worker will be uninstalled.
1246+
1247+
### Offline-First Considerations
1248+
1249+
1. Service workers [require HTTPS](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers#you_need_https),
1250+
although to facilitate local testing, that policy
1251+
[does not apply to `localhost`](http://stackoverflow.com/questions/34160509/options-for-testing-service-workers-via-http/34161385#34161385).
1252+
If your production web server does not support HTTPS, then the service worker
1253+
registration will fail, but the rest of your web app will remain functional.
1254+
1255+
1. Service workers are [not currently supported](https://jakearchibald.github.io/isserviceworkerready/)
1256+
in all web browsers. Service worker registration [won't be attempted](src/service-worker-registration.js)
1257+
on browsers that lack support.
1258+
1259+
1. The service worker is only enabled in the [production environment](#deployment),
1260+
e.g. the output of `npm run build`. It's recommended that you do not enable an
1261+
offline-first service worker in a development environment, as it can lead to
1262+
frustration when previously cached assets are used and do not include the latest
1263+
changes you've made locally.
1264+
1265+
1. If you *need* to test your offline-first service worker locally, build
1266+
the application (using `npm run build`) and run a simple http server from your
1267+
build directory. After running the build script, `create-react-app` will give
1268+
instructions for one way to test your production build locally using
1269+
`pushstate-server` and the [deployment instructions](#deployment) have
1270+
instructions for using the python `SimpleHTTPServer`. *Be sure to always use an
1271+
incognito window to avoid complications with your browser cache.*
1272+
1273+
1. If possible,configure your production environment to serve the generated
1274+
`service-worker.js` [with HTTP caching disabled](http://stackoverflow.com/questions/38843970/service-worker-javascript-update-frequency-every-24-hours).
1275+
If that's not possible—[GitHub Pages](#github-pages), for instance, does not
1276+
allow you to change the default 10 minute HTTP cache lifetime—then be aware
1277+
that if you visit your production site, and then revisit again before
1278+
`service-worker.js` has expired from your HTTP cache, you'll continue to get
1279+
the previously cached assets from the service worker. If you have an immediate
1280+
need to view your updated production deployment, performing a shift-refresh
1281+
will temporarily disable the service worker and retrieve all assets from the
1282+
network.
1283+
1284+
1. Users aren't always familiar with offline-first web apps. It can be useful to
1285+
[let the user know](https://developers.google.com/web/fundamentals/instant-and-offline/offline-ux#inform_the_user_when_the_app_is_ready_for_offline_consumption)
1286+
when the service worker has finished populating your caches (showing a "This web
1287+
app works offline!" message) and also let them know when the service worker has
1288+
fetched the latest updates that will be available the next time they load the
1289+
page (showing a "New content is available; please refresh." message). Showing
1290+
this messages is currently left as an exercise to the developer, but as a
1291+
starting point, you can make use of the logic included in [`src/service-worker-registration.js`](src/service-worker-registration.js), which
1292+
demonstrates which service worker lifecycle events to listen for to detect each
1293+
scenario, and which as a default, just logs appropriate messages to the
1294+
JavaScript console.
1295+
1296+
1. By default, the generated service worker file will not intercept or cache any
1297+
cross-origin traffic, like HTTP [API requests](#integrating-with-an-api-backend),
1298+
images, or embeds loaded from a different domain. If you would like to use a
1299+
runtime caching strategy for those requests, you can [`eject`](#npm-run-eject)
1300+
and then configure the
1301+
[`runtimeCaching`](https://github.com/GoogleChrome/sw-precache#runtimecaching-arrayobject)
1302+
option in the `SWPrecacheWebpackPlugin` section of
1303+
[`webpack.config.prod.js`](../config/webpack.config.prod.js).
1304+
1305+
### Progressive Web App Metadata
1306+
1307+
The default configuration includes a web app manifest located at
1308+
[`public/manifest.json`](public/manifest.json), that you can customize with
1309+
details specific to your web application.
1310+
1311+
When a user adds a web app to their homescreen using Chrome or Firefox on
1312+
Android, the metadata in [`manifest.json`](public/manifest.json) determines what
1313+
icons, names, and branding colors to use when the web app is displayed.
1314+
[The Web App Manifest guide](https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/)
1315+
provides more context about what each field means, and how your customizations
1316+
will affect your users' experience.
1317+
1318+
-->
1319+
12191320
## Deployment
12201321

12211322
`npm run build` creates a `build` directory with a production build of your app. Set up your favourite HTTP server so that a visitor to your site is served `index.html`, and requests to static paths like `/static/js/main.<hash>.js` are served with the contents of the `/static/js/main.<hash>.js` file.
@@ -1291,6 +1392,19 @@ It will get copied to the `build` folder when you run `npm run build`.
12911392

12921393
Now requests to `/todos/42` will be handled correctly both in development and in production.
12931394

1395+
<!--
1396+
TODO: uncomment for 1.0
1397+
1398+
On a production build, and in a browser that supports [service workers](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers),
1399+
the service worker will automatically handle all navigation requests, like for
1400+
`/todos/42`, by serving the cached copy of your `index.html`. This
1401+
service worker navigation routing can be configured or disabled by
1402+
[`eject`ing](#npm-run-eject) and then modifying the
1403+
[`navigateFallback`](https://github.com/GoogleChrome/sw-precache#navigatefallback-string)
1404+
and [`navigateFallbackWhitelist`](https://github.com/GoogleChrome/sw-precache#navigatefallbackwhitelist-arrayregexp)
1405+
options of the `SWPreachePlugin` [configuration](../config/webpack.config.prod.js).
1406+
-->
1407+
12941408
### Building for Relative Paths
12951409

12961410
By default, Create React App produces a build assuming your app is hosted at the server root.<br>

packages/react-scripts/template/public/index.html

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@
33
<head>
44
<meta charset="utf-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6+
<meta name="theme-color" content="#000000">
7+
<!--
8+
manifest.json provides metadata used when your web app is added to the
9+
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10+
-->
11+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
612
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
713
<!--
8-
Notice the use of %PUBLIC_URL% in the tag above.
14+
Notice the use of %PUBLIC_URL% in the tags above.
915
It will be replaced with the URL of the `public` folder during the build.
1016
Only files inside the `public` folder can be referenced from the HTML.
1117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"short_name": "React App",
3+
"name": "Create React App Sample",
4+
"icons": [
5+
{
6+
"src": "favicon.ico",
7+
"sizes": "192x192",
8+
"type": "image/png"
9+
}
10+
],
11+
"start_url": "./index.html",
12+
"display": "standalone",
13+
"theme_color": "#000000",
14+
"background_color": "#ffffff"
15+
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom';
33
import App from './App';
4+
import registerServiceWorker from './service-worker-registration';
45
import './index.css';
56

67
ReactDOM.render(<App />, document.getElementById('root'));
8+
9+
registerServiceWorker();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export default function register() {
2+
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
3+
window.addEventListener('load', () => {
4+
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
5+
navigator.serviceWorker.register(swUrl).then(registration => {
6+
registration.onupdatefound = () => {
7+
const installingWorker = registration.installing;
8+
installingWorker.onstatechange = () => {
9+
if (installingWorker.state === 'installed') {
10+
if (navigator.serviceWorker.controller) {
11+
// At this point, the old content will have been purged and
12+
// the fresh content will have been added to the cache.
13+
// It's the perfect time to display a "New content is
14+
// available; please refresh." message in your web app.
15+
console.log('New content is available; please refresh.');
16+
} else {
17+
// At this point, everything has been precached.
18+
// It's the perfect time to display a
19+
// "Content is cached for offline use." message.
20+
console.log('Content is cached for offline use.');
21+
}
22+
}
23+
};
24+
};
25+
}).catch(error => {
26+
console.error('Error during service worker registration:', error);
27+
});
28+
});
29+
}
30+
}
31+
32+
export function unregister() {
33+
if ('serviceWorker' in navigator) {
34+
navigator.serviceWorker.ready.then(registration => {
35+
registration.unregister();
36+
});
37+
}
38+
}

0 commit comments

Comments
 (0)