Skip to content

Commit d8b01a1

Browse files
feat(pwa): add default PWA support using CRA
1 parent 220de3a commit d8b01a1

8 files changed

+268
-15
lines changed

package.json

+13-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,19 @@
3232
"typeface-montserrat": "^1.1.13",
3333
"typeface-poppins": "^1.1.13",
3434
"typescript": "^4.1.2",
35-
"web-vitals": "^1.0.1"
35+
"web-vitals": "^1.0.1",
36+
"workbox-background-sync": "^5.1.3",
37+
"workbox-broadcast-update": "^5.1.3",
38+
"workbox-cacheable-response": "^5.1.3",
39+
"workbox-core": "^5.1.3",
40+
"workbox-expiration": "^5.1.3",
41+
"workbox-google-analytics": "^5.1.3",
42+
"workbox-navigation-preload": "^5.1.3",
43+
"workbox-precaching": "^5.1.3",
44+
"workbox-range-requests": "^5.1.3",
45+
"workbox-routing": "^5.1.3",
46+
"workbox-strategies": "^5.1.3",
47+
"workbox-streams": "^5.1.3"
3648
},
3749
"scripts": {
3850
"start": "craco start",

public/logo192.png

5.22 KB
Loading

public/logo512.png

9.44 KB
Loading

public/manifest.json

+19-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
{
2-
"short_name": "React App",
3-
"name": "Create React App Sample",
2+
"short_name": "CubeX",
3+
"name": "CubeX Admin Template",
4+
"icons": [
5+
{
6+
"src": "favicon.ico",
7+
"sizes": "64x64 32x32 24x24 16x16",
8+
"type": "image/x-icon"
9+
},
10+
{
11+
"src": "logo192.png",
12+
"type": "image/png",
13+
"sizes": "192x192"
14+
},
15+
{
16+
"src": "logo512.png",
17+
"type": "image/png",
18+
"sizes": "512x512"
19+
}
20+
],
421
"start_url": ".",
522
"display": "standalone",
623
"theme_color": "#000000",

src/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import App from './App';
44
import reportWebVitals from './reportWebVitals';
55
import './i18n';
66
import './styles/main.less';
7+
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
78

89
ReactDOM.render(
910
<React.StrictMode>
@@ -12,6 +13,8 @@ ReactDOM.render(
1213
document.getElementById('root'),
1314
);
1415

16+
serviceWorkerRegistration.register();
17+
1518
// If you want to start measuring performance in your app, pass a function
1619
// to log results (for example: reportWebVitals(console.log))
1720
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals

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/serviceWorkerRegistration.ts

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// This optional code is used to register a service worker.
2+
// register() is not called by default.
3+
4+
// This lets the app load faster on subsequent visits in production, and gives
5+
// it offline capabilities. However, it also means that developers (and users)
6+
// will only see deployed updates on subsequent visits to a page, after all the
7+
// existing tabs open on the page have been closed, since previously cached
8+
// resources are updated in the background.
9+
10+
// To learn more about the benefits of this model and instructions on how to
11+
// opt-in, read https://cra.link/PWA
12+
13+
const isLocalhost = Boolean(
14+
window.location.hostname === 'localhost' ||
15+
// [::1] is the IPv6 localhost address.
16+
window.location.hostname === '[::1]' ||
17+
// 127.0.0.0/8 are considered localhost for IPv4.
18+
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/),
19+
);
20+
21+
type Config = {
22+
onSuccess?: (registration: ServiceWorkerRegistration) => void;
23+
onUpdate?: (registration: ServiceWorkerRegistration) => void;
24+
};
25+
26+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
27+
export function register(config?: Config) {
28+
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
29+
// The URL constructor is available in all browsers that support SW.
30+
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
31+
if (publicUrl.origin !== window.location.origin) {
32+
// Our service worker won't work if PUBLIC_URL is on a different origin
33+
// from what our page is served on. This might happen if a CDN is used to
34+
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
35+
return;
36+
}
37+
38+
window.addEventListener('load', () => {
39+
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
40+
41+
if (isLocalhost) {
42+
// This is running on localhost. Let's check if a service worker still exists or not.
43+
checkValidServiceWorker(swUrl, config);
44+
45+
// Add some additional logging to localhost, pointing developers to the
46+
// service worker/PWA documentation.
47+
navigator.serviceWorker.ready.then(() => {
48+
console.log(
49+
'This web app is being served cache-first by a service ' +
50+
'worker. To learn more, visit https://cra.link/PWA',
51+
);
52+
});
53+
} else {
54+
// Is not localhost. Just register service worker
55+
registerValidSW(swUrl, config);
56+
}
57+
});
58+
}
59+
}
60+
61+
function registerValidSW(swUrl: string, config?: Config) {
62+
navigator.serviceWorker
63+
.register(swUrl)
64+
.then((registration) => {
65+
registration.onupdatefound = () => {
66+
const installingWorker = registration.installing;
67+
if (installingWorker == null) {
68+
return;
69+
}
70+
installingWorker.onstatechange = () => {
71+
if (installingWorker.state === 'installed') {
72+
if (navigator.serviceWorker.controller) {
73+
// At this point, the updated precached content has been fetched,
74+
// but the previous service worker will still serve the older
75+
// content until all client tabs are closed.
76+
console.log(
77+
'New content is available and will be used when all ' +
78+
'tabs for this page are closed. See https://cra.link/PWA.',
79+
);
80+
81+
// Execute callback
82+
if (config && config.onUpdate) {
83+
config.onUpdate(registration);
84+
}
85+
} else {
86+
// At this point, everything has been precached.
87+
// It's the perfect time to display a
88+
// "Content is cached for offline use." message.
89+
console.log('Content is cached for offline use.');
90+
91+
// Execute callback
92+
if (config && config.onSuccess) {
93+
config.onSuccess(registration);
94+
}
95+
}
96+
}
97+
};
98+
};
99+
})
100+
.catch((error) => {
101+
console.error('Error during service worker registration:', error);
102+
});
103+
}
104+
105+
function checkValidServiceWorker(swUrl: string, config?: Config) {
106+
// Check if the service worker can be found. If it can't reload the page.
107+
fetch(swUrl, {
108+
headers: { 'Service-Worker': 'script' },
109+
})
110+
.then((response) => {
111+
// Ensure service worker exists, and that we really are getting a JS file.
112+
const contentType = response.headers.get('content-type');
113+
if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) {
114+
// No service worker found. Probably a different app. Reload the page.
115+
navigator.serviceWorker.ready.then((registration) => {
116+
registration.unregister().then(() => {
117+
window.location.reload();
118+
});
119+
});
120+
} else {
121+
// Service worker found. Proceed as normal.
122+
registerValidSW(swUrl, config);
123+
}
124+
})
125+
.catch(() => {
126+
console.log('No internet connection found. App is running in offline mode.');
127+
});
128+
}
129+
130+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
131+
export function unregister() {
132+
if ('serviceWorker' in navigator) {
133+
navigator.serviceWorker.ready
134+
.then((registration) => {
135+
registration.unregister();
136+
})
137+
.catch((error) => {
138+
console.error(error.message);
139+
});
140+
}
141+
}

yarn.lock

+12-12
Original file line numberDiff line numberDiff line change
@@ -13380,14 +13380,14 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
1338013380
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
1338113381
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
1338213382

13383-
workbox-background-sync@^5.1.4:
13383+
workbox-background-sync@^5.1.3, workbox-background-sync@^5.1.4:
1338413384
version "5.1.4"
1338513385
resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-5.1.4.tgz#5ae0bbd455f4e9c319e8d827c055bb86c894fd12"
1338613386
integrity sha512-AH6x5pYq4vwQvfRDWH+vfOePfPIYQ00nCEB7dJRU1e0n9+9HMRyvI63FlDvtFT2AvXVRsXvUt7DNMEToyJLpSA==
1338713387
dependencies:
1338813388
workbox-core "^5.1.4"
1338913389

13390-
workbox-broadcast-update@^5.1.4:
13390+
workbox-broadcast-update@^5.1.3, workbox-broadcast-update@^5.1.4:
1339113391
version "5.1.4"
1339213392
resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-5.1.4.tgz#0eeb89170ddca7f6914fa3523fb14462891f2cfc"
1339313393
integrity sha512-HTyTWkqXvHRuqY73XrwvXPud/FN6x3ROzkfFPsRjtw/kGZuZkPzfeH531qdUGfhtwjmtO/ZzXcWErqVzJNdXaA==
@@ -13436,26 +13436,26 @@ workbox-build@^5.1.4:
1343613436
workbox-sw "^5.1.4"
1343713437
workbox-window "^5.1.4"
1343813438

13439-
workbox-cacheable-response@^5.1.4:
13439+
workbox-cacheable-response@^5.1.3, workbox-cacheable-response@^5.1.4:
1344013440
version "5.1.4"
1344113441
resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-5.1.4.tgz#9ff26e1366214bdd05cf5a43da9305b274078a54"
1344213442
integrity sha512-0bfvMZs0Of1S5cdswfQK0BXt6ulU5kVD4lwer2CeI+03czHprXR3V4Y8lPTooamn7eHP8Iywi5QjyAMjw0qauA==
1344313443
dependencies:
1344413444
workbox-core "^5.1.4"
1344513445

13446-
workbox-core@^5.1.4:
13446+
workbox-core@^5.1.3, workbox-core@^5.1.4:
1344713447
version "5.1.4"
1344813448
resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-5.1.4.tgz#8bbfb2362ecdff30e25d123c82c79ac65d9264f4"
1344913449
integrity sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg==
1345013450

13451-
workbox-expiration@^5.1.4:
13451+
workbox-expiration@^5.1.3, workbox-expiration@^5.1.4:
1345213452
version "5.1.4"
1345313453
resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-5.1.4.tgz#92b5df461e8126114943a3b15c55e4ecb920b163"
1345413454
integrity sha512-oDO/5iC65h2Eq7jctAv858W2+CeRW5e0jZBMNRXpzp0ZPvuT6GblUiHnAsC5W5lANs1QS9atVOm4ifrBiYY7AQ==
1345513455
dependencies:
1345613456
workbox-core "^5.1.4"
1345713457

13458-
workbox-google-analytics@^5.1.4:
13458+
workbox-google-analytics@^5.1.3, workbox-google-analytics@^5.1.4:
1345913459
version "5.1.4"
1346013460
resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-5.1.4.tgz#b3376806b1ac7d7df8418304d379707195fa8517"
1346113461
integrity sha512-0IFhKoEVrreHpKgcOoddV+oIaVXBFKXUzJVBI+nb0bxmcwYuZMdteBTp8AEDJacENtc9xbR0wa9RDCnYsCDLjA==
@@ -13465,43 +13465,43 @@ workbox-google-analytics@^5.1.4:
1346513465
workbox-routing "^5.1.4"
1346613466
workbox-strategies "^5.1.4"
1346713467

13468-
workbox-navigation-preload@^5.1.4:
13468+
workbox-navigation-preload@^5.1.3, workbox-navigation-preload@^5.1.4:
1346913469
version "5.1.4"
1347013470
resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-5.1.4.tgz#30d1b720d26a05efc5fa11503e5cc1ed5a78902a"
1347113471
integrity sha512-Wf03osvK0wTflAfKXba//QmWC5BIaIZARU03JIhAEO2wSB2BDROWI8Q/zmianf54kdV7e1eLaIEZhth4K4MyfQ==
1347213472
dependencies:
1347313473
workbox-core "^5.1.4"
1347413474

13475-
workbox-precaching@^5.1.4:
13475+
workbox-precaching@^5.1.3, workbox-precaching@^5.1.4:
1347613476
version "5.1.4"
1347713477
resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-5.1.4.tgz#874f7ebdd750dd3e04249efae9a1b3f48285fe6b"
1347813478
integrity sha512-gCIFrBXmVQLFwvAzuGLCmkUYGVhBb7D1k/IL7pUJUO5xacjLcFUaLnnsoVepBGAiKw34HU1y/YuqvTKim9qAZA==
1347913479
dependencies:
1348013480
workbox-core "^5.1.4"
1348113481

13482-
workbox-range-requests@^5.1.4:
13482+
workbox-range-requests@^5.1.3, workbox-range-requests@^5.1.4:
1348313483
version "5.1.4"
1348413484
resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-5.1.4.tgz#7066a12c121df65bf76fdf2b0868016aa2bab859"
1348513485
integrity sha512-1HSujLjgTeoxHrMR2muDW2dKdxqCGMc1KbeyGcmjZZAizJTFwu7CWLDmLv6O1ceWYrhfuLFJO+umYMddk2XMhw==
1348613486
dependencies:
1348713487
workbox-core "^5.1.4"
1348813488

13489-
workbox-routing@^5.1.4:
13489+
workbox-routing@^5.1.3, workbox-routing@^5.1.4:
1349013490
version "5.1.4"
1349113491
resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-5.1.4.tgz#3e8cd86bd3b6573488d1a2ce7385e547b547e970"
1349213492
integrity sha512-8ljknRfqE1vEQtnMtzfksL+UXO822jJlHTIR7+BtJuxQ17+WPZfsHqvk1ynR/v0EHik4x2+826Hkwpgh4GKDCw==
1349313493
dependencies:
1349413494
workbox-core "^5.1.4"
1349513495

13496-
workbox-strategies@^5.1.4:
13496+
workbox-strategies@^5.1.3, workbox-strategies@^5.1.4:
1349713497
version "5.1.4"
1349813498
resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-5.1.4.tgz#96b1418ccdfde5354612914964074d466c52d08c"
1349913499
integrity sha512-VVS57LpaJTdjW3RgZvPwX0NlhNmscR7OQ9bP+N/34cYMDzXLyA6kqWffP6QKXSkca1OFo/v6v7hW7zrrguo6EA==
1350013500
dependencies:
1350113501
workbox-core "^5.1.4"
1350213502
workbox-routing "^5.1.4"
1350313503

13504-
workbox-streams@^5.1.4:
13504+
workbox-streams@^5.1.3, workbox-streams@^5.1.4:
1350513505
version "5.1.4"
1350613506
resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-5.1.4.tgz#05754e5e3667bdc078df2c9315b3f41210d8cac0"
1350713507
integrity sha512-xU8yuF1hI/XcVhJUAfbQLa1guQUhdLMPQJkdT0kn6HP5CwiPOGiXnSFq80rAG4b1kJUChQQIGPrq439FQUNVrw==

0 commit comments

Comments
 (0)