diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fbfaf08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# .gitignore +node_modules +dist + diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..689d5d3 --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +# .npmignore +src +examples +.babelrc +.gitignore +webpack.config.js diff --git a/package.json b/package.json new file mode 100644 index 0000000..88dac7c --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "react-pwa", + "version": "0.1.0", + "private": true, + "dependencies": { + "react": "^16.6.0", + "react-dom": "^16.6.0", + "react-router-dom": "^4.3.1", + "react-scripts": "2.0.5" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/public/ service-worker.js b/public/ service-worker.js new file mode 100644 index 0000000..372d2db --- /dev/null +++ b/public/ service-worker.js @@ -0,0 +1,47 @@ +var doCache = true; + +var CACHE_NAME = "my-pwa-cache-v1"; + +self.addEventListener("activate", event => { + const cacheWhitelist = [CACHE_NAME]; + event.waitUntil( + caches.keys().then(keyList => + Promise.all( + keyList.map(key => { + if (!cacheWhitelist.includes(key)) { + console.log("Deleting cache: " + key); + return caches.delete(key); + } + }) + ) + ) + ); +}); + +self.addEventListener("install", function(event) { + if (doCache) { + event.waitUntil( + caches.open(CACHE_NAME).then(function(cache) { + fetch("manifest.json") + .then(response => { + response.json(); + }) + .then(assets => { + const urlsToCache = ["/", assets["main.js"]]; + cache.addAll(urlsToCache); + console.log("cached"); + }); + }) + ); + } +}); + +self.addEventListener("fetch", function(event) { + if (doCache) { + event.respondWith( + caches.match(event.request).then(function(response) { + return response || fetch(event.request); + }) + ); + } +}); \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..a11777c Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..e464515 --- /dev/null +++ b/public/index.html @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + React App + + + + +
+ + + + diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..c99d095 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,20 @@ +{ + "short_name": "React App", + "name": "Progressive React App Example", + "icons": [ + { + "src": "logo.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "logo-512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "start_url": "/", + "display": "standalone", + "theme_color": "#000", + "background_color": "#000" +} \ No newline at end of file diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..e3351fa --- /dev/null +++ b/src/App.js @@ -0,0 +1,26 @@ +import React, { Component } from "react"; +import { BrowserRouter, Route, Link } from "react-router-dom"; +import logo from "./logo.svg"; + +import Home from "./components/Home"; +import About from "./components/About"; + +class App extends Component { + render() { + return ( +
+
+ logo +

React App

+
+ +
+ + +
+
+
+ ); + } +} +export default App; \ No newline at end of file diff --git a/src/App.test.js b/src/App.test.js new file mode 100644 index 0000000..a754b20 --- /dev/null +++ b/src/App.test.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/src/components/About.js b/src/components/About.js new file mode 100644 index 0000000..9d981ae --- /dev/null +++ b/src/components/About.js @@ -0,0 +1,15 @@ +import React from "react"; +import { Link } from "react-router-dom"; + +const about = () => { + return ( +
+

About Page

+

+ Home +

+
+ ); +}; + +export default about; \ No newline at end of file diff --git a/src/components/Home.js b/src/components/Home.js new file mode 100644 index 0000000..f7cc73e --- /dev/null +++ b/src/components/Home.js @@ -0,0 +1,15 @@ +import React from "react"; +import { Link } from "react-router-dom"; + +const home = () => { + return ( +
+

Home Page

+

+ About +

+
+ ); +}; + +export default home; \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..19d475e --- /dev/null +++ b/src/index.js @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import App from './App'; +import * as serviceWorker from './serviceWorker'; + +ReactDOM.render(, document.getElementById('root')); + +// 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: http://bit.ly/CRA-PWA +serviceWorker.unregister(); diff --git a/src/logo.svg b/src/logo.svg new file mode 100644 index 0000000..6b60c10 --- /dev/null +++ b/src/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/serviceWorker.js b/src/serviceWorker.js new file mode 100644 index 0000000..012c322 --- /dev/null +++ b/src/serviceWorker.js @@ -0,0 +1,131 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// 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 subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read http://bit.ly/CRA-PWA. + +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 function register(config) { + 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/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit http://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + 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 updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } 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.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // 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, config); + } + }) + .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(); + }); + } +}