Skip to content

Commit a2e0247

Browse files
committed
fix(pwa): explicitly add workbox for PWA support
facebook/create-react-app#9776 (comment)
1 parent 99f71a2 commit a2e0247

7 files changed

+168
-37
lines changed

package.json

+13-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@
2626
"typeface-roboto": "^1.1.13",
2727
"typescript": "^4.1.2",
2828
"vec-la-fp": "^1.9.0",
29+
"workbox-background-sync": "^5.1.3",
30+
"workbox-broadcast-update": "^5.1.3",
31+
"workbox-cacheable-response": "^5.1.3",
32+
"workbox-core": "^5.1.3",
33+
"workbox-expiration": "^5.1.3",
34+
"workbox-google-analytics": "^5.1.3",
35+
"workbox-navigation-preload": "^5.1.3",
36+
"workbox-precaching": "^5.1.3",
37+
"workbox-range-requests": "^5.1.3",
38+
"workbox-routing": "^5.1.3",
39+
"workbox-strategies": "^5.1.3",
40+
"workbox-streams": "^5.1.3",
2941
"wouter": "^2.6.0"
3042
},
3143
"devDependencies": {
@@ -86,4 +98,4 @@
8698
"lint-staged": {
8799
"./src/**/*.{js,jsx,ts,tsx}": "pretty-quick"
88100
}
89-
}
101+
}

src/components/ServiceWorkerWrapper.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import { Button, Snackbar } from '@material-ui/core';
44
import Alert from '@material-ui/lab/Alert';
55
import React, { FC, useEffect } from 'react';
6-
import * as serviceWorker from '../serviceWorker';
6+
import * as serviceWorker from '../serviceWorkerRegistration';
7+
// service worker config has been removed from CRA / react-scripts 4.0
8+
// https://github.com/facebook/create-react-app/issues/9776#issuecomment-728945921
9+
// files sourced from https://github.com/cra-template/pwa/tree/master/packages/cra-template-pwa-typescript
710

811
const ServiceWorkerWrapper: FC = () => {
912
const [showReload, setShowReload] = React.useState(false);

src/components/render/WebGLCanvas.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const WebGLCanvas = React.forwardRef<HTMLCanvasElement, WebGLCanvasProps>(
5050
// https://www.khronos.org/webgl/wiki/HandlingContextLost
5151
canvasRef.current.addEventListener(
5252
'webglcontextlost',
53-
(event) => {
53+
(event: Event) => {
5454
console.error('WebGL context lost!');
5555
event.preventDefault();
5656
// trigger an error alert in future?
@@ -59,7 +59,7 @@ const WebGLCanvas = React.forwardRef<HTMLCanvasElement, WebGLCanvasProps>(
5959
);
6060
canvasRef.current.addEventListener(
6161
'webglcontextrestored',
62-
(event) => {
62+
(event: Event) => {
6363
console.error('WebGL context restored! Setting up...');
6464
setupCanvas();
6565
},

src/reportWebVitals.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ReportHandler } from 'web-vitals';
2+
3+
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4+
if (onPerfEntry && onPerfEntry instanceof Function) {
5+
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6+
getCLS(onPerfEntry);
7+
getFID(onPerfEntry);
8+
getFCP(onPerfEntry);
9+
getLCP(onPerfEntry);
10+
getTTFB(onPerfEntry);
11+
});
12+
}
13+
};
14+
15+
export default reportWebVitals;

src/service-worker.ts

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/// <reference lib="webworker" />
2+
/* eslint-disable no-restricted-globals */
3+
4+
// This service worker can be customized!
5+
// See https://developers.google.com/web/tools/workbox/modules
6+
// for the list of available Workbox modules, or add any other
7+
// code you'd like.
8+
// You can also remove this file if you'd prefer not to use a
9+
// service worker, and the Workbox build step will be skipped.
10+
11+
import { clientsClaim } from 'workbox-core';
12+
import { ExpirationPlugin } from 'workbox-expiration';
13+
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
14+
import { registerRoute } from 'workbox-routing';
15+
import { StaleWhileRevalidate } from 'workbox-strategies';
16+
17+
declare const self: ServiceWorkerGlobalScope;
18+
19+
clientsClaim();
20+
21+
// Precache all of the assets generated by your build process.
22+
// Their URLs are injected into the manifest variable below.
23+
// This variable must be present somewhere in your service worker file,
24+
// even if you decide not to use precaching. See https://cra.link/PWA
25+
precacheAndRoute(self.__WB_MANIFEST);
26+
27+
// Set up App Shell-style routing, so that all navigation requests
28+
// are fulfilled with your index.html shell. Learn more at
29+
// https://developers.google.com/web/fundamentals/architecture/app-shell
30+
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
31+
registerRoute(
32+
// Return false to exempt requests from being fulfilled by index.html.
33+
({ request, url }: { request: Request; url: URL }) => {
34+
// If this isn't a navigation, skip.
35+
if (request.mode !== 'navigate') {
36+
return false;
37+
}
38+
39+
// If this is a URL that starts with /_, skip.
40+
if (url.pathname.startsWith('/_')) {
41+
return false;
42+
}
43+
44+
// If this looks like a URL for a resource, because it contains
45+
// a file extension, skip.
46+
if (url.pathname.match(fileExtensionRegexp)) {
47+
return false;
48+
}
49+
50+
// Return true to signal that we want to use the handler.
51+
return true;
52+
},
53+
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'),
54+
);
55+
56+
// An example runtime caching route for requests that aren't handled by the
57+
// precache, in this case same-origin .png requests like those from in public/
58+
registerRoute(
59+
// Add in any other file extensions or routing criteria as needed.
60+
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'),
61+
// Customize this strategy as needed, e.g., by changing to CacheFirst.
62+
new StaleWhileRevalidate({
63+
cacheName: 'images',
64+
plugins: [
65+
// Ensure that once this runtime cache reaches a maximum size the
66+
// least-recently used images are removed.
67+
new ExpirationPlugin({ maxEntries: 50 }),
68+
],
69+
}),
70+
);
71+
72+
// This allows the web app to trigger skipWaiting via
73+
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
74+
self.addEventListener('message', (event) => {
75+
if (event.data && event.data.type === 'SKIP_WAITING') {
76+
self.skipWaiting();
77+
}
78+
});
79+
80+
// Any other custom service worker logic can go here.

src/serviceWorker.js renamed to src/serviceWorkerRegistration.ts

+30-21
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,24 @@
88
// resources are updated in the background.
99

1010
// To learn more about the benefits of this model and instructions on how to
11-
// opt-in, read https://bit.ly/CRA-PWA
11+
// opt-in, read https://cra.link/PWA
1212

1313
const isLocalhost = Boolean(
1414
window.location.hostname === 'localhost' ||
1515
// [::1] is the IPv6 localhost address.
1616
window.location.hostname === '[::1]' ||
17-
// 127.0.0.1/8 is considered localhost for IPv4.
17+
// 127.0.0.0/8 are considered localhost for IPv4.
1818
window.location.hostname.match(
19-
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20-
)
19+
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
20+
),
2121
);
2222

23-
export function register(config) {
23+
type Config = {
24+
onSuccess?: (registration: ServiceWorkerRegistration) => void;
25+
onUpdate?: (registration: ServiceWorkerRegistration) => void;
26+
};
27+
28+
export function register(config?: Config): void {
2429
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
2530
// The URL constructor is available in all browsers that support SW.
2631
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
@@ -43,7 +48,7 @@ export function register(config) {
4348
navigator.serviceWorker.ready.then(() => {
4449
console.log(
4550
'This web app is being served cache-first by a service ' +
46-
'worker. To learn more, visit https://bit.ly/CRA-PWA'
51+
'worker. To learn more, visit https://cra.link/PWA',
4752
);
4853
});
4954
} else {
@@ -54,10 +59,10 @@ export function register(config) {
5459
}
5560
}
5661

57-
function registerValidSW(swUrl, config) {
62+
function registerValidSW(swUrl: string, config?: Config) {
5863
navigator.serviceWorker
5964
.register(swUrl)
60-
.then(registration => {
65+
.then((registration) => {
6166
registration.onupdatefound = () => {
6267
const installingWorker = registration.installing;
6368
if (installingWorker == null) {
@@ -71,7 +76,7 @@ function registerValidSW(swUrl, config) {
7176
// content until all client tabs are closed.
7277
console.log(
7378
'New content is available and will be used when all ' +
74-
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
79+
'tabs for this page are closed. See https://cra.link/PWA.',
7580
);
7681

7782
// Execute callback
@@ -93,23 +98,25 @@ function registerValidSW(swUrl, config) {
9398
};
9499
};
95100
})
96-
.catch(error => {
101+
.catch((error) => {
97102
console.error('Error during service worker registration:', error);
98103
});
99104
}
100105

101-
function checkValidServiceWorker(swUrl, config) {
106+
function checkValidServiceWorker(swUrl: string, config?: Config) {
102107
// Check if the service worker can be found. If it can't reload the page.
103-
fetch(swUrl)
104-
.then(response => {
108+
fetch(swUrl, {
109+
headers: { 'Service-Worker': 'script' },
110+
})
111+
.then((response) => {
105112
// Ensure service worker exists, and that we really are getting a JS file.
106113
const contentType = response.headers.get('content-type');
107114
if (
108115
response.status === 404 ||
109116
(contentType != null && contentType.indexOf('javascript') === -1)
110117
) {
111118
// No service worker found. Probably a different app. Reload the page.
112-
navigator.serviceWorker.ready.then(registration => {
119+
navigator.serviceWorker.ready.then((registration) => {
113120
registration.unregister().then(() => {
114121
window.location.reload();
115122
});
@@ -120,16 +127,18 @@ function checkValidServiceWorker(swUrl, config) {
120127
}
121128
})
122129
.catch(() => {
123-
console.log(
124-
'No internet connection found. App is running in offline mode.'
125-
);
130+
console.log('No internet connection found. App is running in offline mode.');
126131
});
127132
}
128133

129-
export function unregister() {
134+
export function unregister(): void {
130135
if ('serviceWorker' in navigator) {
131-
navigator.serviceWorker.ready.then(registration => {
132-
registration.unregister();
133-
});
136+
navigator.serviceWorker.ready
137+
.then((registration) => {
138+
registration.unregister();
139+
})
140+
.catch((error) => {
141+
console.error(error.message);
142+
});
134143
}
135144
}

yarn.lock

+24-12
Original file line numberDiff line numberDiff line change
@@ -11363,6 +11363,18 @@ fsevents@~2.1.2:
1136311363
typescript: ^4.1.2
1136411364
vec-la-fp: ^1.9.0
1136511365
web-vitals: ^1.0.1
11366+
workbox-background-sync: ^5.1.3
11367+
workbox-broadcast-update: ^5.1.3
11368+
workbox-cacheable-response: ^5.1.3
11369+
workbox-core: ^5.1.3
11370+
workbox-expiration: ^5.1.3
11371+
workbox-google-analytics: ^5.1.3
11372+
workbox-navigation-preload: ^5.1.3
11373+
workbox-precaching: ^5.1.3
11374+
workbox-range-requests: ^5.1.3
11375+
workbox-routing: ^5.1.3
11376+
workbox-strategies: ^5.1.3
11377+
workbox-streams: ^5.1.3
1136611378
wouter: ^2.6.0
1136711379
languageName: unknown
1136811380
linkType: soft
@@ -17764,7 +17776,7 @@ typescript@^4.1.2:
1776417776
languageName: node
1776517777
linkType: hard
1776617778

17767-
"workbox-background-sync@npm:^5.1.4":
17779+
"workbox-background-sync@npm:^5.1.3, workbox-background-sync@npm:^5.1.4":
1776817780
version: 5.1.4
1776917781
resolution: "workbox-background-sync@npm:5.1.4"
1777017782
dependencies:
@@ -17773,7 +17785,7 @@ typescript@^4.1.2:
1777317785
languageName: node
1777417786
linkType: hard
1777517787

17776-
"workbox-broadcast-update@npm:^5.1.4":
17788+
"workbox-broadcast-update@npm:^5.1.3, workbox-broadcast-update@npm:^5.1.4":
1777717789
version: 5.1.4
1777817790
resolution: "workbox-broadcast-update@npm:5.1.4"
1777917791
dependencies:
@@ -17826,7 +17838,7 @@ typescript@^4.1.2:
1782617838
languageName: node
1782717839
linkType: hard
1782817840

17829-
"workbox-cacheable-response@npm:^5.1.4":
17841+
"workbox-cacheable-response@npm:^5.1.3, workbox-cacheable-response@npm:^5.1.4":
1783017842
version: 5.1.4
1783117843
resolution: "workbox-cacheable-response@npm:5.1.4"
1783217844
dependencies:
@@ -17835,14 +17847,14 @@ typescript@^4.1.2:
1783517847
languageName: node
1783617848
linkType: hard
1783717849

17838-
"workbox-core@npm:^5.1.4":
17850+
"workbox-core@npm:^5.1.3, workbox-core@npm:^5.1.4":
1783917851
version: 5.1.4
1784017852
resolution: "workbox-core@npm:5.1.4"
1784117853
checksum: eaa6021ef9d7f3046619d09904171121286dee8c20299b00f4a06d85e8a549c0345f70cf5a5cb47c270641ffd3e319b40479c7a7bd4025c651f9ee215ee27014
1784217854
languageName: node
1784317855
linkType: hard
1784417856

17845-
"workbox-expiration@npm:^5.1.4":
17857+
"workbox-expiration@npm:^5.1.3, workbox-expiration@npm:^5.1.4":
1784617858
version: 5.1.4
1784717859
resolution: "workbox-expiration@npm:5.1.4"
1784817860
dependencies:
@@ -17851,7 +17863,7 @@ typescript@^4.1.2:
1785117863
languageName: node
1785217864
linkType: hard
1785317865

17854-
"workbox-google-analytics@npm:^5.1.4":
17866+
"workbox-google-analytics@npm:^5.1.3, workbox-google-analytics@npm:^5.1.4":
1785517867
version: 5.1.4
1785617868
resolution: "workbox-google-analytics@npm:5.1.4"
1785717869
dependencies:
@@ -17863,7 +17875,7 @@ typescript@^4.1.2:
1786317875
languageName: node
1786417876
linkType: hard
1786517877

17866-
"workbox-navigation-preload@npm:^5.1.4":
17878+
"workbox-navigation-preload@npm:^5.1.3, workbox-navigation-preload@npm:^5.1.4":
1786717879
version: 5.1.4
1786817880
resolution: "workbox-navigation-preload@npm:5.1.4"
1786917881
dependencies:
@@ -17872,7 +17884,7 @@ typescript@^4.1.2:
1787217884
languageName: node
1787317885
linkType: hard
1787417886

17875-
"workbox-precaching@npm:^5.1.4":
17887+
"workbox-precaching@npm:^5.1.3, workbox-precaching@npm:^5.1.4":
1787617888
version: 5.1.4
1787717889
resolution: "workbox-precaching@npm:5.1.4"
1787817890
dependencies:
@@ -17881,7 +17893,7 @@ typescript@^4.1.2:
1788117893
languageName: node
1788217894
linkType: hard
1788317895

17884-
"workbox-range-requests@npm:^5.1.4":
17896+
"workbox-range-requests@npm:^5.1.3, workbox-range-requests@npm:^5.1.4":
1788517897
version: 5.1.4
1788617898
resolution: "workbox-range-requests@npm:5.1.4"
1788717899
dependencies:
@@ -17890,7 +17902,7 @@ typescript@^4.1.2:
1789017902
languageName: node
1789117903
linkType: hard
1789217904

17893-
"workbox-routing@npm:^5.1.4":
17905+
"workbox-routing@npm:^5.1.3, workbox-routing@npm:^5.1.4":
1789417906
version: 5.1.4
1789517907
resolution: "workbox-routing@npm:5.1.4"
1789617908
dependencies:
@@ -17899,7 +17911,7 @@ typescript@^4.1.2:
1789917911
languageName: node
1790017912
linkType: hard
1790117913

17902-
"workbox-strategies@npm:^5.1.4":
17914+
"workbox-strategies@npm:^5.1.3, workbox-strategies@npm:^5.1.4":
1790317915
version: 5.1.4
1790417916
resolution: "workbox-strategies@npm:5.1.4"
1790517917
dependencies:
@@ -17909,7 +17921,7 @@ typescript@^4.1.2:
1790917921
languageName: node
1791017922
linkType: hard
1791117923

17912-
"workbox-streams@npm:^5.1.4":
17924+
"workbox-streams@npm:^5.1.3, workbox-streams@npm:^5.1.4":
1791317925
version: 5.1.4
1791417926
resolution: "workbox-streams@npm:5.1.4"
1791517927
dependencies:

0 commit comments

Comments
 (0)