From aecf926519f852a6b087f17c79c1b5d08687cb72 Mon Sep 17 00:00:00 2001 From: hole-thu Date: Mon, 8 Aug 2022 23:53:01 +0800 Subject: [PATCH] fix PWA --- .pnp.cjs | 28 +++++- package.json | 14 ++- public/index.html | 2 +- public/{static => }/manifest.json | 2 +- src/index.js | 4 +- src/service-worker.js | 73 +++++++++++++++ ...Worker.js => serviceWorkerRegistration.js} | 88 ++++++++++++------- yarn.lock | 36 +++++--- 8 files changed, 196 insertions(+), 51 deletions(-) rename public/{static => }/manifest.json (94%) create mode 100644 src/service-worker.js rename src/{registerServiceWorker.js => serviceWorkerRegistration.js} (51%) diff --git a/.pnp.cjs b/.pnp.cjs index 754577d..265dce6 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -55,7 +55,19 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["react-lazyload", "virtual:d489c25e2ebeea07458b58e50f8ccfb31c1a2205a90b069d1097b87c37e4691c13f2247338ffb59360c079762786d6bbbc895d4b6a241f03e5878297f15a38ca#npm:3.2.0"],\ ["react-polls", "virtual:d489c25e2ebeea07458b58e50f8ccfb31c1a2205a90b069d1097b87c37e4691c13f2247338ffb59360c079762786d6bbbc895d4b6a241f03e5878297f15a38ca#npm:1.2.0"],\ ["react-scripts", "virtual:d489c25e2ebeea07458b58e50f8ccfb31c1a2205a90b069d1097b87c37e4691c13f2247338ffb59360c079762786d6bbbc895d4b6a241f03e5878297f15a38ca#npm:5.0.1"],\ - ["react-timeago", "virtual:d489c25e2ebeea07458b58e50f8ccfb31c1a2205a90b069d1097b87c37e4691c13f2247338ffb59360c079762786d6bbbc895d4b6a241f03e5878297f15a38ca#npm:7.1.0"]\ + ["react-timeago", "virtual:d489c25e2ebeea07458b58e50f8ccfb31c1a2205a90b069d1097b87c37e4691c13f2247338ffb59360c079762786d6bbbc895d4b6a241f03e5878297f15a38ca#npm:7.1.0"],\ + ["workbox-background-sync", "npm:6.5.4"],\ + ["workbox-broadcast-update", "npm:6.5.4"],\ + ["workbox-cacheable-response", "npm:6.5.4"],\ + ["workbox-core", "npm:6.5.4"],\ + ["workbox-expiration", "npm:6.5.4"],\ + ["workbox-google-analytics", "npm:6.5.4"],\ + ["workbox-navigation-preload", "npm:6.5.4"],\ + ["workbox-precaching", "npm:6.5.4"],\ + ["workbox-range-requests", "npm:6.5.4"],\ + ["workbox-routing", "npm:6.5.4"],\ + ["workbox-strategies", "npm:6.5.4"],\ + ["workbox-streams", "npm:6.5.4"]\ ],\ "linkType": "SOFT"\ }]\ @@ -16989,7 +17001,19 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["react-lazyload", "virtual:d489c25e2ebeea07458b58e50f8ccfb31c1a2205a90b069d1097b87c37e4691c13f2247338ffb59360c079762786d6bbbc895d4b6a241f03e5878297f15a38ca#npm:3.2.0"],\ ["react-polls", "virtual:d489c25e2ebeea07458b58e50f8ccfb31c1a2205a90b069d1097b87c37e4691c13f2247338ffb59360c079762786d6bbbc895d4b6a241f03e5878297f15a38ca#npm:1.2.0"],\ ["react-scripts", "virtual:d489c25e2ebeea07458b58e50f8ccfb31c1a2205a90b069d1097b87c37e4691c13f2247338ffb59360c079762786d6bbbc895d4b6a241f03e5878297f15a38ca#npm:5.0.1"],\ - ["react-timeago", "virtual:d489c25e2ebeea07458b58e50f8ccfb31c1a2205a90b069d1097b87c37e4691c13f2247338ffb59360c079762786d6bbbc895d4b6a241f03e5878297f15a38ca#npm:7.1.0"]\ + ["react-timeago", "virtual:d489c25e2ebeea07458b58e50f8ccfb31c1a2205a90b069d1097b87c37e4691c13f2247338ffb59360c079762786d6bbbc895d4b6a241f03e5878297f15a38ca#npm:7.1.0"],\ + ["workbox-background-sync", "npm:6.5.4"],\ + ["workbox-broadcast-update", "npm:6.5.4"],\ + ["workbox-cacheable-response", "npm:6.5.4"],\ + ["workbox-core", "npm:6.5.4"],\ + ["workbox-expiration", "npm:6.5.4"],\ + ["workbox-google-analytics", "npm:6.5.4"],\ + ["workbox-navigation-preload", "npm:6.5.4"],\ + ["workbox-precaching", "npm:6.5.4"],\ + ["workbox-range-requests", "npm:6.5.4"],\ + ["workbox-routing", "npm:6.5.4"],\ + ["workbox-strategies", "npm:6.5.4"],\ + ["workbox-streams", "npm:6.5.4"]\ ],\ "linkType": "SOFT"\ }]\ diff --git a/package.json b/package.json index 97a5e46..71690d7 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,19 @@ "react-lazyload": "^3.2.0", "react-polls": "^1.2.0", "react-scripts": "5.0.1", - "react-timeago": "^7.1.0" + "react-timeago": "^7.1.0", + "workbox-background-sync": "^6.4.2", + "workbox-broadcast-update": "^6.4.2", + "workbox-cacheable-response": "^6.4.2", + "workbox-core": "^6.4.2", + "workbox-expiration": "^6.4.2", + "workbox-google-analytics": "^6.4.2", + "workbox-navigation-preload": "^6.4.2", + "workbox-precaching": "^6.4.2", + "workbox-range-requests": "^6.4.2", + "workbox-routing": "^6.4.2", + "workbox-strategies": "^6.4.2", + "workbox-streams": "^6.4.2" }, "scripts": { "start": "react-scripts start", diff --git a/public/index.html b/public/index.html index 1cf46af..7d89479 100644 --- a/public/index.html +++ b/public/index.html @@ -10,7 +10,7 @@ - + diff --git a/public/static/manifest.json b/public/manifest.json similarity index 94% rename from public/static/manifest.json rename to public/manifest.json index f5ec232..34645a4 100644 --- a/public/static/manifest.json +++ b/public/manifest.json @@ -13,7 +13,7 @@ "type": "image/png" } ], - "start_url": "../", + "start_url": ".", "display": "standalone", "theme_color": "#333333", "background_color": "#333333" diff --git a/src/index.js b/src/index.js index 67a2c2e..d648868 100644 --- a/src/index.js +++ b/src/index.js @@ -3,11 +3,11 @@ import './index.css'; import './fonts_7/icomoon.css'; import App from './App'; //import {elevate} from './infrastructure/elevator'; -import registerServiceWorker from './registerServiceWorker'; +import * as serviceWorkerRegistration from './serviceWorkerRegistration'; //elevate(); const container = document.getElementById('root'); const root = createRoot(container); // createRoot(container!) if you use TypeScript root.render(); -registerServiceWorker(); +serviceWorkerRegistration.register(); diff --git a/src/service-worker.js b/src/service-worker.js new file mode 100644 index 0000000..c1566b0 --- /dev/null +++ b/src/service-worker.js @@ -0,0 +1,73 @@ +/* eslint-disable no-restricted-globals */ + +// This service worker can be customized! +// See https://developers.google.com/web/tools/workbox/modules +// for the list of available Workbox modules, or add any other +// code you'd like. +// You can also remove this file if you'd prefer not to use a +// service worker, and the Workbox build step will be skipped. + +import { clientsClaim } from 'workbox-core'; +import { ExpirationPlugin } from 'workbox-expiration'; +import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; +import { registerRoute } from 'workbox-routing'; +import { StaleWhileRevalidate } from 'workbox-strategies'; + +clientsClaim(); + +// Precache all of the assets generated by your build process. +// Their URLs are injected into the manifest variable below. +// This variable must be present somewhere in your service worker file, +// even if you decide not to use precaching. See https://cra.link/PWA +precacheAndRoute(self.__WB_MANIFEST); + +// Set up App Shell-style routing, so that all navigation requests +// are fulfilled with your index.html shell. Learn more at +// https://developers.google.com/web/fundamentals/architecture/app-shell +const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); +registerRoute( + // Return false to exempt requests from being fulfilled by index.html. + ({ request, url }) => { + // If this isn't a navigation, skip. + if (request.mode !== 'navigate') { + return false; + } // If this is a URL that starts with /_, skip. + + if (url.pathname.startsWith('/_')) { + return false; + } // If this looks like a URL for a resource, because it contains // a file extension, skip. + + if (url.pathname.match(fileExtensionRegexp)) { + return false; + } // Return true to signal that we want to use the handler. + + return true; + }, + createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'), +); + +// An example runtime caching route for requests that aren't handled by the +// precache, in this case same-origin .png requests like those from in public/ +registerRoute( + // Add in any other file extensions or routing criteria as needed. + ({ url }) => + url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst. + new StaleWhileRevalidate({ + cacheName: 'images', + plugins: [ + // Ensure that once this runtime cache reaches a maximum size the + // least-recently used images are removed. + new ExpirationPlugin({ maxEntries: 50 }), + ], + }), +); + +// This allows the web app to trigger skipWaiting via +// registration.waiting.postMessage({type: 'SKIP_WAITING'}) +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); + +// Any other custom service worker logic can go here. diff --git a/src/registerServiceWorker.js b/src/serviceWorkerRegistration.js similarity index 51% rename from src/registerServiceWorker.js rename to src/serviceWorkerRegistration.js index b8ef96e..52a02fe 100644 --- a/src/registerServiceWorker.js +++ b/src/serviceWorkerRegistration.js @@ -1,76 +1,93 @@ -// In production, we register a service worker to serve assets from local cache. +// 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 the "N+1" visit to a page, since previously -// cached resources are updated in the background. +// 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, read https://goo.gl/KwvDNy. -// This link also includes instructions on opting out of this behavior. +// To learn more about the benefits of this model and instructions on how to +// opt-in, read https://cra.link/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. + // 127.0.0.0/8 are 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() { +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/facebookincubator/create-react-app/issues/2374 - // return; - // } + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + 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. Lets check if a service worker still exists or not. - checkValidServiceWorker(swUrl); + // 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 https://goo.gl/SC7cgQ', + 'worker. To learn more, visit https://cra.link/PWA', ); }); } else { - // Is not local host. Just register service worker - registerValidSW(swUrl); + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); } }); } } -function registerValidSW(swUrl) { +function registerValidSW(swUrl, config) { navigator.serviceWorker .register(swUrl) .then((registration) => { registration.onupdatefound = () => { const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } 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.'); + // 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 https://cra.link/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); + } } } }; @@ -81,14 +98,17 @@ function registerValidSW(swUrl) { }); } -function checkValidServiceWorker(swUrl) { +function checkValidServiceWorker(swUrl, config) { // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) + fetch(swUrl, { + headers: { 'Service-Worker': 'script' }, + }) .then((response) => { // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); if ( response.status === 404 || - response.headers.get('content-type').indexOf('javascript') === -1 + (contentType != null && contentType.indexOf('javascript') === -1) ) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then((registration) => { @@ -98,7 +118,7 @@ function checkValidServiceWorker(swUrl) { }); } else { // Service worker found. Proceed as normal. - registerValidSW(swUrl); + registerValidSW(swUrl, config); } }) .catch(() => { @@ -110,8 +130,12 @@ function checkValidServiceWorker(swUrl) { export function unregister() { if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then((registration) => { - registration.unregister(); - }); + navigator.serviceWorker.ready + .then((registration) => { + registration.unregister(); + }) + .catch((error) => { + console.error(error.message); + }); } } diff --git a/yarn.lock b/yarn.lock index e688bee..6520904 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12703,6 +12703,18 @@ __metadata: react-polls: ^1.2.0 react-scripts: 5.0.1 react-timeago: ^7.1.0 + workbox-background-sync: ^6.4.2 + workbox-broadcast-update: ^6.4.2 + workbox-cacheable-response: ^6.4.2 + workbox-core: ^6.4.2 + workbox-expiration: ^6.4.2 + workbox-google-analytics: ^6.4.2 + workbox-navigation-preload: ^6.4.2 + workbox-precaching: ^6.4.2 + workbox-range-requests: ^6.4.2 + workbox-routing: ^6.4.2 + workbox-strategies: ^6.4.2 + workbox-streams: ^6.4.2 languageName: unknown linkType: soft @@ -12976,7 +12988,7 @@ __metadata: languageName: node linkType: hard -"workbox-background-sync@npm:6.5.4": +"workbox-background-sync@npm:6.5.4, workbox-background-sync@npm:^6.4.2": version: 6.5.4 resolution: "workbox-background-sync@npm:6.5.4" dependencies: @@ -12986,7 +12998,7 @@ __metadata: languageName: node linkType: hard -"workbox-broadcast-update@npm:6.5.4": +"workbox-broadcast-update@npm:6.5.4, workbox-broadcast-update@npm:^6.4.2": version: 6.5.4 resolution: "workbox-broadcast-update@npm:6.5.4" dependencies: @@ -13040,7 +13052,7 @@ __metadata: languageName: node linkType: hard -"workbox-cacheable-response@npm:6.5.4": +"workbox-cacheable-response@npm:6.5.4, workbox-cacheable-response@npm:^6.4.2": version: 6.5.4 resolution: "workbox-cacheable-response@npm:6.5.4" dependencies: @@ -13049,14 +13061,14 @@ __metadata: languageName: node linkType: hard -"workbox-core@npm:6.5.4": +"workbox-core@npm:6.5.4, workbox-core@npm:^6.4.2": version: 6.5.4 resolution: "workbox-core@npm:6.5.4" checksum: d973cc6c1c5fdbde7f6642632384c2e0de48f08228eb234db2c97a18a7e5422b483005767e7b447ea774abc0772dfc1edef2ef2b5df174df4d40ae61d4c49719 languageName: node linkType: hard -"workbox-expiration@npm:6.5.4": +"workbox-expiration@npm:6.5.4, workbox-expiration@npm:^6.4.2": version: 6.5.4 resolution: "workbox-expiration@npm:6.5.4" dependencies: @@ -13066,7 +13078,7 @@ __metadata: languageName: node linkType: hard -"workbox-google-analytics@npm:6.5.4": +"workbox-google-analytics@npm:6.5.4, workbox-google-analytics@npm:^6.4.2": version: 6.5.4 resolution: "workbox-google-analytics@npm:6.5.4" dependencies: @@ -13078,7 +13090,7 @@ __metadata: languageName: node linkType: hard -"workbox-navigation-preload@npm:6.5.4": +"workbox-navigation-preload@npm:6.5.4, workbox-navigation-preload@npm:^6.4.2": version: 6.5.4 resolution: "workbox-navigation-preload@npm:6.5.4" dependencies: @@ -13087,7 +13099,7 @@ __metadata: languageName: node linkType: hard -"workbox-precaching@npm:6.5.4": +"workbox-precaching@npm:6.5.4, workbox-precaching@npm:^6.4.2": version: 6.5.4 resolution: "workbox-precaching@npm:6.5.4" dependencies: @@ -13098,7 +13110,7 @@ __metadata: languageName: node linkType: hard -"workbox-range-requests@npm:6.5.4": +"workbox-range-requests@npm:6.5.4, workbox-range-requests@npm:^6.4.2": version: 6.5.4 resolution: "workbox-range-requests@npm:6.5.4" dependencies: @@ -13121,7 +13133,7 @@ __metadata: languageName: node linkType: hard -"workbox-routing@npm:6.5.4": +"workbox-routing@npm:6.5.4, workbox-routing@npm:^6.4.2": version: 6.5.4 resolution: "workbox-routing@npm:6.5.4" dependencies: @@ -13130,7 +13142,7 @@ __metadata: languageName: node linkType: hard -"workbox-strategies@npm:6.5.4": +"workbox-strategies@npm:6.5.4, workbox-strategies@npm:^6.4.2": version: 6.5.4 resolution: "workbox-strategies@npm:6.5.4" dependencies: @@ -13139,7 +13151,7 @@ __metadata: languageName: node linkType: hard -"workbox-streams@npm:6.5.4": +"workbox-streams@npm:6.5.4, workbox-streams@npm:^6.4.2": version: 6.5.4 resolution: "workbox-streams@npm:6.5.4" dependencies: