From d4737bd29eaa6e105917347f8d58c3a145272ee8 Mon Sep 17 00:00:00 2001 From: Eduard Kyvenko Date: Thu, 7 Sep 2017 00:38:21 +0200 Subject: [PATCH 1/3] feat($build): Added the support for PWA Added SWPrecacheWebpackPlugin and a service worker to serve assets from local cache --- config/webpack.config.prod.js | 34 ++- package.json | 1 + template/public/index.html | 6 + template/public/manifest.json | 15 ++ template/src/index.js | 3 + template/src/registerServiceWorker.js | 108 +++++++++ yarn.lock | 306 +++++++++++++++++++++++++- 7 files changed, 460 insertions(+), 13 deletions(-) create mode 100644 template/public/manifest.json create mode 100644 template/src/registerServiceWorker.js diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js index 5ba08c4b..af3adca3 100644 --- a/config/webpack.config.prod.js +++ b/config/webpack.config.prod.js @@ -1,6 +1,7 @@ 'use strict'; const autoprefixer = require('autoprefixer'); +const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); const DefinePlugin = require('webpack/lib/DefinePlugin'); const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); @@ -195,6 +196,37 @@ module.exports = { // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`. new ExtractTextPlugin({ filename: cssFilename - }) + }), + + // Generate a service worker script that will precache, and keep up to date, + // the HTML & assets that are part of the Webpack build. + new SWPrecacheWebpackPlugin({ + // By default, a cache-busting query parameter is appended to requests + // used to populate the caches, to ensure the responses are fresh. + // If a URL is already hashed by Webpack, then there is no concern + // about it being stale, and the cache-busting can be skipped. + dontCacheBustUrlsMatching: /\.\w{8}\./, + filename: 'service-worker.js', + logger(message) { + if (message.indexOf('Total precache size is') === 0) { + // This message occurs for every build and is a bit too noisy. + return; + } + if (message.indexOf('Skipping static resource') === 0) { + // This message obscures real errors so we ignore it. + // https://github.com/facebookincubator/create-react-app/issues/2612 + return; + } + console.log(message); + }, + minify: true, + // For unknown URLs, fallback to the index page + navigateFallback: publicUrl + '/index.html', + // Ignores URLs starting from /__ (useful for Firebase): + // https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219 + navigateFallbackWhitelist: [/^(?!\/__).*/], + // Don't precache sourcemaps (they're large) and build asset manifest: + staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/], + }), ] }; diff --git a/package.json b/package.json index 89a5d7fd..abf24af9 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "react-error-overlay": "^1.0.10", "string-replace-loader": "^1.3.0", "style-loader": "^0.18.1", + "sw-precache-webpack-plugin": "^0.11.4", "url-loader": "^0.5.9", "webpack": "^3.5.5", "webpack-dev-server": "^2.7.1" diff --git a/template/public/index.html b/template/public/index.html index f9844d97..40cb71b8 100644 --- a/template/public/index.html +++ b/template/public/index.html @@ -4,6 +4,12 @@ + + + Elm App diff --git a/template/public/manifest.json b/template/public/manifest.json new file mode 100644 index 00000000..9b7dc41e --- /dev/null +++ b/template/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Elm App", + "name": "Create Elm App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "192x192", + "type": "image/png" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/template/src/index.js b/template/src/index.js index 90379015..3f79cf93 100644 --- a/template/src/index.js +++ b/template/src/index.js @@ -1,4 +1,7 @@ import './main.css'; import { Main } from './Main.elm'; +import registerServiceWorker from './registerServiceWorker'; Main.embed(document.getElementById('root')); + +registerServiceWorker(); diff --git a/template/src/registerServiceWorker.js b/template/src/registerServiceWorker.js new file mode 100644 index 00000000..4e7625b2 --- /dev/null +++ b/template/src/registerServiceWorker.js @@ -0,0 +1,108 @@ +// In production, we register a service worker to serve assets from local cache. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on the "N+1" visit to a page, since previously +// cached resources are updated in the background. + +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. +// This link also includes instructions on opting out of this behavior. + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export default function register() { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (!isLocalhost) { + // Is not local host. Just register service worker + registerValidSW(swUrl); + } else { + // This is running on localhost. Lets check if a service worker still exists or not. + checkValidServiceWorker(swUrl); + } + }); + } +} + +function registerValidSW(swUrl) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a "New content is + // available; please refresh." message in your web app. + console.log('New content is available; please refresh.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if ( + response.status === 404 || + response.headers.get('content-type').indexOf('javascript') === -1 + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} diff --git a/yarn.lock b/yarn.lock index 476c4862..f912e6b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -125,6 +125,12 @@ anser@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.1.tgz#c3641863a962cebef941ea2c8706f2cb4f0716bd" +ansi-align@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-1.1.0.tgz#2f0c1658829739add5ebb15e6b0c6e3423f016ba" + dependencies: + string-width "^1.0.1" + ansi-escapes@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" @@ -995,6 +1001,20 @@ boom@2.x.x: dependencies: hoek "2.x.x" +boxen@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-0.6.0.tgz#8364d4248ac34ff0ef1b2f2bf49a6c60ce0d81b6" + dependencies: + ansi-align "^1.1.0" + camelcase "^2.1.0" + chalk "^1.1.1" + cli-boxes "^1.0.0" + filled-array "^1.0.0" + object-assign "^4.0.1" + repeating "^2.0.0" + string-width "^1.0.1" + widest-line "^1.0.0" + brace-expansion@^1.0.0, brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -1149,7 +1169,7 @@ camelcase@^1.0.2, camelcase@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" -camelcase@^2.0.0: +camelcase@^2.0.0, camelcase@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" @@ -1178,6 +1198,10 @@ caniuse-lite@^1.0.30000697, caniuse-lite@^1.0.30000701: version "1.0.30000702" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000702.tgz#bd66e40345528fe0c001917d1d3f55454df634f1" +capture-stack-trace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" + caseless@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" @@ -1297,6 +1321,10 @@ clean-webpack-plugin@^0.1.16: dependencies: rimraf "~2.5.1" +cli-boxes@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" + cli-cursor@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" @@ -1411,7 +1439,7 @@ commander@2.9.0, commander@2.9.x, commander@~2.9.0: dependencies: graceful-readlink ">= 1.0.0" -commander@^2.11.0, commander@^2.9.0: +commander@^2.11.0, commander@^2.9.0, commander@~2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" @@ -1476,6 +1504,20 @@ config-chain@~1.1.8: ini "^1.3.4" proto-list "~1.2.1" +configstore@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1" + dependencies: + dot-prop "^3.0.0" + graceful-fs "^4.1.2" + mkdirp "^0.5.0" + object-assign "^4.0.1" + os-tmpdir "^1.0.0" + osenv "^0.1.0" + uuid "^2.0.1" + write-file-atomic "^1.1.2" + xdg-basedir "^2.0.0" + connect-history-api-fallback@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz#e51d17f8f0ef0db90a64fdb47de3051556e9f169" @@ -1559,6 +1601,12 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" +create-error-class@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" + dependencies: + capture-stack-trace "^1.0.0" + create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" @@ -1826,7 +1874,7 @@ defined@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" -del@^2.0.2: +del@^2.0.2, del@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" dependencies: @@ -1961,6 +2009,12 @@ dom-serializer@0: domelementtype "~1.1.1" entities "~1.1.1" +dom-urls@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dom-urls/-/dom-urls-1.1.0.tgz#001ddf81628cd1e706125c7176f53ccec55d918e" + dependencies: + urijs "^1.16.1" + domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" @@ -1992,10 +2046,22 @@ domutils@1.5.1: dom-serializer "0" domelementtype "1" +dot-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" + dependencies: + is-obj "^1.0.0" + dotenv@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" +duplexer2@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + dependencies: + readable-stream "^2.0.2" + duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -2541,6 +2607,10 @@ fill-range@^2.1.0: repeat-element "^1.1.2" repeat-string "^1.5.2" +filled-array@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/filled-array/-/filled-array-1.1.0.tgz#c3c4f6c663b923459a9aa29912d2d031f1507f84" + finalhandler@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89" @@ -2937,7 +3007,27 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.1.9: +got@^5.0.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" + dependencies: + create-error-class "^3.0.1" + duplexer2 "^0.1.4" + is-redirect "^1.0.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + lowercase-keys "^1.0.0" + node-status-codes "^1.0.0" + object-assign "^4.0.1" + parse-json "^2.1.0" + pinkie-promise "^2.0.0" + read-all-stream "^3.0.0" + readable-stream "^2.0.5" + timed-out "^3.0.0" + unzip-response "^1.0.2" + url-parse-lax "^1.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -3410,6 +3500,10 @@ is-my-json-valid@^2.12.4: jsonpointer "^4.0.0" xtend "^4.0.0" +is-npm@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -3422,6 +3516,10 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" @@ -3458,17 +3556,25 @@ is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" +is-redirect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" + is-resolvable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" dependencies: tryit "^1.0.1" +is-retry-allowed@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + is-root@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-root/-/is-root-1.0.0.tgz#07b6c233bc394cd9d02ba15c966bd6660d6342d5" -is-stream@^1.1.0: +is-stream@^1.0.0, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -3643,10 +3749,20 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" +latest-version@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b" + dependencies: + package-json "^2.0.0" + lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" +lazy-req@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac" + lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -3754,6 +3870,10 @@ lodash._isiterateecall@^3.0.0: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" +lodash._reinterpolate@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + lodash.assign@^3.0.0, lodash.assign@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-3.2.0.tgz#3ce9f0234b4b2223e296b8fa0ac1fee8ebca64fa" @@ -3774,6 +3894,10 @@ lodash.create@3.1.1: lodash._basecreate "^3.0.0" lodash._isiterateecall "^3.0.0" +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -3849,6 +3973,19 @@ lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" +lodash.template@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" + dependencies: + lodash._reinterpolate "~3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316" + dependencies: + lodash._reinterpolate "~3.0.0" + lodash.toplainobject@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz#28790ad942d293d78aa663a07ecf7f52ca04198d" @@ -3913,6 +4050,10 @@ lower-case@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" +lowercase-keys@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" + lru-cache@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" @@ -3959,7 +4100,7 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.1.0, meow@^3.3.0: +meow@^3.1.0, meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" dependencies: @@ -4241,6 +4382,10 @@ node-pre-gyp@^0.6.36: tar "^2.2.1" tar-pack "^3.4.0" +node-status-codes@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" + node-uuid@~1.4.7: version "1.4.8" resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" @@ -4532,6 +4677,15 @@ p-map@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a" +package-json@^2.0.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb" + dependencies: + got "^5.0.0" + registry-auth-token "^3.0.1" + registry-url "^3.0.3" + semver "^5.1.0" + pad-right@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/pad-right/-/pad-right-0.2.2.tgz#6fbc924045d244f2a2a244503060d3bfc6009774" @@ -4571,7 +4725,7 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" -parse-json@^2.2.0: +parse-json@^2.1.0, parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" dependencies: @@ -4626,6 +4780,12 @@ path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" +path-to-regexp@^1.0.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" + dependencies: + isarray "0.0.1" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -4994,7 +5154,7 @@ prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" -prepend-http@^1.0.0: +prepend-http@^1.0.0, prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -5013,6 +5173,10 @@ pretty-bytes@^1.0.2: get-stdin "^4.0.1" meow "^3.1.0" +pretty-bytes@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" + pretty-error@^2.0.2: version "2.1.1" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" @@ -5153,7 +5317,7 @@ range-parser@^1.0.3, range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" -rc@^1.1.2, rc@^1.1.7: +rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.1.7: version "1.2.1" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" dependencies: @@ -5197,6 +5361,13 @@ react-error-overlay@^1.0.10: settle-promise "1.0.0" source-map "0.5.6" +read-all-stream@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" + dependencies: + pinkie-promise "^2.0.0" + readable-stream "^2.0.0" + read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -5242,7 +5413,7 @@ readable-stream@1.0: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.2.9: +readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.2.9: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -5359,6 +5530,19 @@ regexpu-core@^2.0.0: regjsgen "^0.2.0" regjsparser "^0.1.4" +registry-auth-token@^3.0.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.1.tgz#fb0d3289ee0d9ada2cbb52af5dfe66cb070d3006" + dependencies: + rc "^1.1.6" + safe-buffer "^5.0.1" + +registry-url@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" + dependencies: + rc "^1.0.1" + regjsgen@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" @@ -5677,6 +5861,12 @@ semantic-release@^6.3.2: run-series "^1.1.3" semver "^5.2.0" +semver-diff@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" + dependencies: + semver "^5.0.3" + "semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.2.0, semver@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -5728,6 +5918,10 @@ serve-static@1.12.3: parseurl "~1.3.1" send "0.15.3" +serviceworker-cache-polyfill@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serviceworker-cache-polyfill/-/serviceworker-cache-polyfill-4.0.0.tgz#de19ee73bef21ab3c0740a37b33db62464babdeb" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -5807,7 +6001,7 @@ sliced@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" -slide@^1.1.3: +slide@^1.1.3, slide@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" @@ -6106,6 +6300,36 @@ svgo@^0.7.0: sax "~1.2.1" whet.extend "~0.9.9" +sw-precache-webpack-plugin@^0.11.4: + version "0.11.4" + resolved "https://registry.yarnpkg.com/sw-precache-webpack-plugin/-/sw-precache-webpack-plugin-0.11.4.tgz#a695017e54eed575551493a519dc1da8da2dc5e0" + dependencies: + del "^2.2.2" + sw-precache "^5.1.1" + uglify-js "^3.0.13" + +sw-precache@^5.1.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/sw-precache/-/sw-precache-5.2.0.tgz#eb6225ce580ceaae148194578a0ad01ab7ea199c" + dependencies: + dom-urls "^1.1.0" + es6-promise "^4.0.5" + glob "^7.1.1" + lodash.defaults "^4.2.0" + lodash.template "^4.4.0" + meow "^3.7.0" + mkdirp "^0.5.1" + pretty-bytes "^4.0.2" + sw-toolbox "^3.4.0" + update-notifier "^1.0.3" + +sw-toolbox@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/sw-toolbox/-/sw-toolbox-3.6.0.tgz#26df1d1c70348658e4dea2884319149b7b3183b5" + dependencies: + path-to-regexp "^1.0.1" + serviceworker-cache-polyfill "^4.0.0" + table@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/table/-/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435" @@ -6179,6 +6403,10 @@ thunky@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" +timed-out@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" + timers-browserify@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86" @@ -6305,6 +6533,13 @@ uglify-js@^2.8.29: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^3.0.13: + version "3.0.28" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.0.28.tgz#96b8495f0272944787b5843a1679aa326640d5f7" + dependencies: + commander "~2.11.0" + source-map "~0.5.1" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -6351,10 +6586,31 @@ unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" +unzip-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" + +update-notifier@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-1.0.3.tgz#8f92c515482bd6831b7c93013e70f87552c7cf5a" + dependencies: + boxen "^0.6.0" + chalk "^1.0.0" + configstore "^2.0.0" + is-npm "^1.0.0" + latest-version "^2.0.0" + lazy-req "^1.1.0" + semver-diff "^2.0.0" + xdg-basedir "^2.0.0" + upper-case@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" +urijs@^1.16.1: + version "1.18.12" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.18.12.tgz#f04d91e1fabb29c16fc842f9a14ee8ddc3fda64e" + url-loader@^0.5.9: version "0.5.9" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.9.tgz#cc8fea82c7b906e7777019250869e569e995c295" @@ -6362,6 +6618,12 @@ url-loader@^0.5.9: loader-utils "^1.0.2" mime "1.3.x" +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + dependencies: + prepend-http "^1.0.1" + url-parse@1.0.x: version "1.0.5" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b" @@ -6420,7 +6682,7 @@ utils-merge@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" -uuid@^2.0.2: +uuid@^2.0.1, uuid@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" @@ -6592,6 +6854,12 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2" +widest-line@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c" + dependencies: + string-width "^1.0.1" + window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" @@ -6631,12 +6899,26 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" +write-file-atomic@^1.1.2: + version "1.3.4" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + slide "^1.1.5" + write@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" dependencies: mkdirp "^0.5.1" +xdg-basedir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2" + dependencies: + os-homedir "^1.0.0" + xml-char-classes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d" From 811b4c8470138f5a80c96c92d5dbb6429e8cfebd Mon Sep 17 00:00:00 2001 From: Eduard Kyvenko Date: Sun, 10 Sep 2017 23:58:52 +0200 Subject: [PATCH 2/3] docs: Added documentation for PWA setup --- template/README.md | 107 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/template/README.md b/template/README.md index 1c95887e..d98bed28 100644 --- a/template/README.md +++ b/template/README.md @@ -30,6 +30,10 @@ You can find the most recent version of this guide [here](https://github.com/hal - [Setting up API Proxy](#setting-up-api-proxy) - [Running tests](#running-tests) - [Dependencies in Tests](#dependencies-in-tests) +- [Making a Progressive Web App](#making-a-progressive-web-app) + - [Opting Out of Caching](#opting-out-of-caching) + - [Offline-First Considerations](#offline-first-considerations) + - [Progressive Web App Metadata](#progressive-web-app-metadata) - [Deployment](#deployment) - [Static Server](#static-server) - [GitHub Pages](#github-pages) @@ -317,6 +321,109 @@ To use packages in tests, you also need to install them in `tests` directory. elm-app test --add-dependencies elm-package.json ``` +## Making a Progressive Web App + +By default, the production build is a fully functional, offline-first +[Progressive Web App](https://developers.google.com/web/progressive-web-apps/). + +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. + * 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. You can also re-engage users using web **push notifications**. This eliminates the need for the app store. + +The [`sw-precache-webpack-plugin`](https://github.com/goldhand/sw-precache-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 the initial HTML, ensuring +that your web app is reliably fast, even on a slow or unreliable network. + +### Opting Out of Caching + +If you would prefer not to enable service workers prior to your initial +production deployment, then remove the call to `serviceWorkerRegistration.register()` +from [`src/index.js`](src/index.js). + +If you had previously enabled service workers in your production deployment and +have decided that you would like to disable them for all your existing users, +you can swap out the call to `serviceWorkerRegistration.register()` in +[`src/index.js`](src/index.js) with a call to `serviceWorkerRegistration.unregister()`. +After the user visits a page that has `serviceWorkerRegistration.unregister()`, +the service worker will be uninstalled. Note that depending on how `/service-worker.js` is served, +it may take up to 24 hours for the cache to be invalidated. + +### Offline-First Considerations + +1. Service workers [require HTTPS](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers#you_need_https), +although to facilitate local testing, that policy +[does not apply to `localhost`](http://stackoverflow.com/questions/34160509/options-for-testing-service-workers-via-http/34161385#34161385). +If your production web server does not support HTTPS, then the service worker +registration will fail, but the rest of your web app will remain functional. + +1. Service workers are [not currently supported](https://jakearchibald.github.io/isserviceworkerready/) +in all web browsers. Service worker registration [won't be attempted](src/registerServiceWorker.js) +on browsers that lack support. + +1. The service worker is only enabled in the [production environment](#deployment), +e.g. the output of `npm run build`. It's recommended that you do not enable an +offline-first service worker in a development environment, as it can lead to +frustration when previously cached assets are used and do not include the latest +changes you've made locally. + +1. If you *need* to test your offline-first service worker locally, build +the application (using `npm run build`) and run a simple http server from your +build directory. After running the build script, `create-react-app` will give +instructions for one way to test your production build locally and the [deployment instructions](#deployment) have +instructions for using other methods. *Be sure to always use an +incognito window to avoid complications with your browser cache.* + +1. If possible, configure your production environment to serve the generated +`service-worker.js` [with HTTP caching disabled](http://stackoverflow.com/questions/38843970/service-worker-javascript-update-frequency-every-24-hours). +If that's not possible—[GitHub Pages](#github-pages), for instance, does not +allow you to change the default 10 minute HTTP cache lifetime—then be aware +that if you visit your production site, and then revisit again before +`service-worker.js` has expired from your HTTP cache, you'll continue to get +the previously cached assets from the service worker. If you have an immediate +need to view your updated production deployment, performing a shift-refresh +will temporarily disable the service worker and retrieve all assets from the +network. + +1. Users aren't always familiar with offline-first web apps. It can be useful to +[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) +when the service worker has finished populating your caches (showing a "This web +app works offline!" message) and also let them know when the service worker has +fetched the latest updates that will be available the next time they load the +page (showing a "New content is available; please refresh." message). Showing +this messages is currently left as an exercise to the developer, but as a +starting point, you can make use of the logic included in [`src/registerServiceWorker.js`](src/registerServiceWorker.js), which +demonstrates which service worker lifecycle events to listen for to detect each +scenario, and which as a default, just logs appropriate messages to the +JavaScript console. + +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), +images, or embeds loaded from a different domain. If you would like to use a +runtime caching strategy for those requests, you can [`eject`](#npm-run-eject) +and then configure the +[`runtimeCaching`](https://github.com/GoogleChrome/sw-precache#runtimecaching-arrayobject) +option in the `SWPrecacheWebpackPlugin` section of +[`webpack.config.prod.js`](../config/webpack.config.prod.js). + +### Progressive Web App Metadata + +The default configuration includes a web app manifest located at +[`public/manifest.json`](public/manifest.json), that you can customize with +details specific to your web application. + +When a user adds a web app to their homescreen using Chrome or Firefox on +Android, the metadata in [`manifest.json`](public/manifest.json) determines what +icons, names, and branding colors to use when the web app is displayed. +[The Web App Manifest guide](https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/) +provides more context about what each field means, and how your customizations +will affect your users' experience. + ## Deployment From e206b77ea0e52935feb37f58b94d625fbc1187aa Mon Sep 17 00:00:00 2001 From: Eduard Kyvenko Date: Tue, 12 Sep 2017 09:18:41 +0200 Subject: [PATCH 3/3] docs($docs): Updated the project structure in the documentation --- README.md | 31 +++++++++++++++++-------------- template/README.md | 33 +++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 36364d73..964c0e9b 100644 --- a/README.md +++ b/README.md @@ -48,20 +48,23 @@ Create a new `my-app` folder with files for your future project. ``` my-app/ - .gitignore - README.md - elm-package.json - public/ - favicon.ico - index.html - src/ - Main.elm - index.js - main.css - tests/ - elm-package.json - Main.elm - Tests.elm +├── .gitignore +├── README.md +├── elm-package.json +├── elm-stuff +├── public +│ ├── favicon.ico +│ ├── index.html +│ ├── logo.svg +│ └── manifest.json +├── src +│ ├── Main.elm +│ ├── index.js +│ ├── main.css +│ └── registerServiceWorker.js +└── tests + ├── Tests.elm + └── elm-package.json ``` You are ready to employ the full power of Create Elm App! diff --git a/template/README.md b/template/README.md index d98bed28..04aa6626 100644 --- a/template/README.md +++ b/template/README.md @@ -67,23 +67,28 @@ const db = new PouchDB('mydb'); ``` ## Folder structure + ``` my-app/ - .gitignore - README.md - elm-package.json - public/ - favicon.ico - index.html - src/ - Main.elm - index.js - main.css - tests/ - elm-package.json - Main.elm - Tests.elm +├── .gitignore +├── README.md +├── elm-package.json +├── elm-stuff +├── public +│ ├── favicon.ico +│ ├── index.html +│ ├── logo.svg +│ └── manifest.json +├── src +│ ├── Main.elm +│ ├── index.js +│ ├── main.css +│ └── registerServiceWorker.js +└── tests + ├── Tests.elm + └── elm-package.json ``` + For the project to build, these files must exist with exact filenames: - `public/index.html` is the page template;