Skip to content

Commit ce47a1f

Browse files
authored
Merge pull request #170 from halfzebra/add-pwa-support
feat($build): Added the support for PWA
2 parents 895f0b3 + e206b77 commit ce47a1f

File tree

9 files changed

+603
-41
lines changed

9 files changed

+603
-41
lines changed

README.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,23 @@ Create a new `my-app` folder with files for your future project.
4848

4949
```
5050
my-app/
51-
.gitignore
52-
README.md
53-
elm-package.json
54-
public/
55-
favicon.ico
56-
index.html
57-
src/
58-
Main.elm
59-
index.js
60-
main.css
61-
tests/
62-
elm-package.json
63-
Main.elm
64-
Tests.elm
51+
├── .gitignore
52+
├── README.md
53+
├── elm-package.json
54+
├── elm-stuff
55+
├── public
56+
│ ├── favicon.ico
57+
│ ├── index.html
58+
│ ├── logo.svg
59+
│ └── manifest.json
60+
├── src
61+
│ ├── Main.elm
62+
│ ├── index.js
63+
│ ├── main.css
64+
│ └── registerServiceWorker.js
65+
└── tests
66+
├── Tests.elm
67+
└── elm-package.json
6568
```
6669

6770
You are ready to employ the full power of Create Elm App!

config/webpack.config.prod.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const autoprefixer = require('autoprefixer');
4+
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
45
const DefinePlugin = require('webpack/lib/DefinePlugin');
56
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
67
const HtmlWebpackPlugin = require('html-webpack-plugin');
@@ -195,6 +196,37 @@ module.exports = {
195196
// Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
196197
new ExtractTextPlugin({
197198
filename: cssFilename
198-
})
199+
}),
200+
201+
// Generate a service worker script that will precache, and keep up to date,
202+
// the HTML & assets that are part of the Webpack build.
203+
new SWPrecacheWebpackPlugin({
204+
// By default, a cache-busting query parameter is appended to requests
205+
// used to populate the caches, to ensure the responses are fresh.
206+
// If a URL is already hashed by Webpack, then there is no concern
207+
// about it being stale, and the cache-busting can be skipped.
208+
dontCacheBustUrlsMatching: /\.\w{8}\./,
209+
filename: 'service-worker.js',
210+
logger(message) {
211+
if (message.indexOf('Total precache size is') === 0) {
212+
// This message occurs for every build and is a bit too noisy.
213+
return;
214+
}
215+
if (message.indexOf('Skipping static resource') === 0) {
216+
// This message obscures real errors so we ignore it.
217+
// https://github.com/facebookincubator/create-react-app/issues/2612
218+
return;
219+
}
220+
console.log(message);
221+
},
222+
minify: true,
223+
// For unknown URLs, fallback to the index page
224+
navigateFallback: publicUrl + '/index.html',
225+
// Ignores URLs starting from /__ (useful for Firebase):
226+
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
227+
navigateFallbackWhitelist: [/^(?!\/__).*/],
228+
// Don't precache sourcemaps (they're large) and build asset manifest:
229+
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
230+
}),
199231
]
200232
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"react-error-overlay": "^1.0.10",
4949
"string-replace-loader": "^1.3.0",
5050
"style-loader": "^0.18.1",
51+
"sw-precache-webpack-plugin": "^0.11.4",
5152
"url-loader": "^0.5.9",
5253
"webpack": "^3.5.5",
5354
"webpack-dev-server": "^2.7.1"

template/README.md

Lines changed: 126 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ You can find the most recent version of this guide [here](https://github.com/hal
3030
- [Setting up API Proxy](#setting-up-api-proxy)
3131
- [Running tests](#running-tests)
3232
- [Dependencies in Tests](#dependencies-in-tests)
33+
- [Making a Progressive Web App](#making-a-progressive-web-app)
34+
- [Opting Out of Caching](#opting-out-of-caching)
35+
- [Offline-First Considerations](#offline-first-considerations)
36+
- [Progressive Web App Metadata](#progressive-web-app-metadata)
3337
- [Deployment](#deployment)
3438
- [Static Server](#static-server)
3539
- [GitHub Pages](#github-pages)
@@ -63,23 +67,28 @@ const db = new PouchDB('mydb');
6367
```
6468

6569
## Folder structure
70+
6671
```
6772
my-app/
68-
.gitignore
69-
README.md
70-
elm-package.json
71-
public/
72-
favicon.ico
73-
index.html
74-
src/
75-
Main.elm
76-
index.js
77-
main.css
78-
tests/
79-
elm-package.json
80-
Main.elm
81-
Tests.elm
73+
├── .gitignore
74+
├── README.md
75+
├── elm-package.json
76+
├── elm-stuff
77+
├── public
78+
│ ├── favicon.ico
79+
│ ├── index.html
80+
│ ├── logo.svg
81+
│ └── manifest.json
82+
├── src
83+
│ ├── Main.elm
84+
│ ├── index.js
85+
│ ├── main.css
86+
│ └── registerServiceWorker.js
87+
└── tests
88+
├── Tests.elm
89+
└── elm-package.json
8290
```
91+
8392
For the project to build, these files must exist with exact filenames:
8493

8594
- `public/index.html` is the page template;
@@ -317,6 +326,109 @@ To use packages in tests, you also need to install them in `tests` directory.
317326
elm-app test --add-dependencies elm-package.json
318327
```
319328

329+
## Making a Progressive Web App
330+
331+
By default, the production build is a fully functional, offline-first
332+
[Progressive Web App](https://developers.google.com/web/progressive-web-apps/).
333+
334+
Progressive Web Apps are faster and more reliable than traditional web pages, and provide an engaging mobile experience:
335+
336+
* 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.
337+
* 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.
338+
* On mobile devices, your app can be added directly to the user's home screen, app icon and all. You can also re-engage users using web **push notifications**. This eliminates the need for the app store.
339+
340+
The [`sw-precache-webpack-plugin`](https://github.com/goldhand/sw-precache-webpack-plugin)
341+
is integrated into production configuration,
342+
and it will take care of generating a service worker file that will automatically
343+
precache all of your local assets and keep them up to date as you deploy updates.
344+
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)
345+
for handling all requests for local assets, including the initial HTML, ensuring
346+
that your web app is reliably fast, even on a slow or unreliable network.
347+
348+
### Opting Out of Caching
349+
350+
If you would prefer not to enable service workers prior to your initial
351+
production deployment, then remove the call to `serviceWorkerRegistration.register()`
352+
from [`src/index.js`](src/index.js).
353+
354+
If you had previously enabled service workers in your production deployment and
355+
have decided that you would like to disable them for all your existing users,
356+
you can swap out the call to `serviceWorkerRegistration.register()` in
357+
[`src/index.js`](src/index.js) with a call to `serviceWorkerRegistration.unregister()`.
358+
After the user visits a page that has `serviceWorkerRegistration.unregister()`,
359+
the service worker will be uninstalled. Note that depending on how `/service-worker.js` is served,
360+
it may take up to 24 hours for the cache to be invalidated.
361+
362+
### Offline-First Considerations
363+
364+
1. Service workers [require HTTPS](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers#you_need_https),
365+
although to facilitate local testing, that policy
366+
[does not apply to `localhost`](http://stackoverflow.com/questions/34160509/options-for-testing-service-workers-via-http/34161385#34161385).
367+
If your production web server does not support HTTPS, then the service worker
368+
registration will fail, but the rest of your web app will remain functional.
369+
370+
1. Service workers are [not currently supported](https://jakearchibald.github.io/isserviceworkerready/)
371+
in all web browsers. Service worker registration [won't be attempted](src/registerServiceWorker.js)
372+
on browsers that lack support.
373+
374+
1. The service worker is only enabled in the [production environment](#deployment),
375+
e.g. the output of `npm run build`. It's recommended that you do not enable an
376+
offline-first service worker in a development environment, as it can lead to
377+
frustration when previously cached assets are used and do not include the latest
378+
changes you've made locally.
379+
380+
1. If you *need* to test your offline-first service worker locally, build
381+
the application (using `npm run build`) and run a simple http server from your
382+
build directory. After running the build script, `create-react-app` will give
383+
instructions for one way to test your production build locally and the [deployment instructions](#deployment) have
384+
instructions for using other methods. *Be sure to always use an
385+
incognito window to avoid complications with your browser cache.*
386+
387+
1. If possible, configure your production environment to serve the generated
388+
`service-worker.js` [with HTTP caching disabled](http://stackoverflow.com/questions/38843970/service-worker-javascript-update-frequency-every-24-hours).
389+
If that's not possible—[GitHub Pages](#github-pages), for instance, does not
390+
allow you to change the default 10 minute HTTP cache lifetime—then be aware
391+
that if you visit your production site, and then revisit again before
392+
`service-worker.js` has expired from your HTTP cache, you'll continue to get
393+
the previously cached assets from the service worker. If you have an immediate
394+
need to view your updated production deployment, performing a shift-refresh
395+
will temporarily disable the service worker and retrieve all assets from the
396+
network.
397+
398+
1. Users aren't always familiar with offline-first web apps. It can be useful to
399+
[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)
400+
when the service worker has finished populating your caches (showing a "This web
401+
app works offline!" message) and also let them know when the service worker has
402+
fetched the latest updates that will be available the next time they load the
403+
page (showing a "New content is available; please refresh." message). Showing
404+
this messages is currently left as an exercise to the developer, but as a
405+
starting point, you can make use of the logic included in [`src/registerServiceWorker.js`](src/registerServiceWorker.js), which
406+
demonstrates which service worker lifecycle events to listen for to detect each
407+
scenario, and which as a default, just logs appropriate messages to the
408+
JavaScript console.
409+
410+
1. By default, the generated service worker file will not intercept or cache any
411+
cross-origin traffic, like HTTP [API requests](#integrating-with-an-api-backend),
412+
images, or embeds loaded from a different domain. If you would like to use a
413+
runtime caching strategy for those requests, you can [`eject`](#npm-run-eject)
414+
and then configure the
415+
[`runtimeCaching`](https://github.com/GoogleChrome/sw-precache#runtimecaching-arrayobject)
416+
option in the `SWPrecacheWebpackPlugin` section of
417+
[`webpack.config.prod.js`](../config/webpack.config.prod.js).
418+
419+
### Progressive Web App Metadata
420+
421+
The default configuration includes a web app manifest located at
422+
[`public/manifest.json`](public/manifest.json), that you can customize with
423+
details specific to your web application.
424+
425+
When a user adds a web app to their homescreen using Chrome or Firefox on
426+
Android, the metadata in [`manifest.json`](public/manifest.json) determines what
427+
icons, names, and branding colors to use when the web app is displayed.
428+
[The Web App Manifest guide](https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/)
429+
provides more context about what each field means, and how your customizations
430+
will affect your users' experience.
431+
320432
## Deployment
321433

322434

template/public/index.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
<meta charset="utf-8">
55
<meta http-equiv="x-ua-compatible" content="ie=edge">
66
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7+
<meta name="theme-color" content="#000000">
8+
<!--
9+
manifest.json provides metadata used when your web app is added to the
10+
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
11+
-->
12+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
713
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
814
<title>Elm App</title>
915
</head>

template/public/manifest.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"short_name": "Elm App",
3+
"name": "Create Elm 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+
}

template/src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import './main.css';
22
import { Main } from './Main.elm';
3+
import registerServiceWorker from './registerServiceWorker';
34

45
Main.embed(document.getElementById('root'));
6+
7+
registerServiceWorker();

0 commit comments

Comments
 (0)