Rework PWA to precache less files
Use network first strategy by default Show the prompt after 3 days without reloadpull/2254/head
parent
d8acd234cb
commit
f108a6d7d1
|
@ -1,4 +1,5 @@
|
|||
import { useEffect } from 'react'
|
||||
import { getItem, removeItem, setItem } from '@/storage/safeLocalStorage'
|
||||
import { useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
import { useRegisterSW } from 'virtual:pwa-register/react'
|
||||
|
@ -36,35 +37,57 @@ const StyledHideButton = styled.div`
|
|||
right: 0.375rem;
|
||||
`
|
||||
|
||||
const pwaPromptDelayKey = 'update-pwa-prompt-delay'
|
||||
|
||||
export const ServiceWorker = () => {
|
||||
const { t } = useTranslation()
|
||||
const [showPrompt, setShowPrompt] = useState(false)
|
||||
|
||||
const {
|
||||
offlineReady: [offlineReady, setOfflineReady],
|
||||
needRefresh: [needRefresh, setNeedRefresh],
|
||||
updateServiceWorker,
|
||||
} = useRegisterSW({
|
||||
immediate: true,
|
||||
onRegistered: (r) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('=> SW Registered: ', r)
|
||||
|
||||
// If no service worker is waiting, delete the old prompt delay
|
||||
if (!r?.waiting) {
|
||||
removeItem(pwaPromptDelayKey)
|
||||
}
|
||||
},
|
||||
|
||||
onNeedRefresh() {
|
||||
const promptDelay = parseInt(getItem(pwaPromptDelayKey) ?? '0')
|
||||
|
||||
// If we need a refresh and there is no prompt delay, create one in 3 days
|
||||
if (promptDelay === 0) {
|
||||
setItem(
|
||||
pwaPromptDelayKey,
|
||||
(Date.now() + 3 * 24 * 60 * 60 * 1000).toString()
|
||||
)
|
||||
}
|
||||
// If we need a refresh and the prompt delay has passed, show the prompt
|
||||
if (promptDelay > 0 && promptDelay < Date.now()) {
|
||||
setShowPrompt(true)
|
||||
}
|
||||
},
|
||||
|
||||
onOfflineReady() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('App is ready to work offline.')
|
||||
},
|
||||
|
||||
onRegisterError: (error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('SW registration error', error)
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (offlineReady) {
|
||||
setOfflineReady(false)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('App is ready to work offline.')
|
||||
}
|
||||
}, [offlineReady, setOfflineReady])
|
||||
|
||||
return (
|
||||
<PromptContainer>
|
||||
{needRefresh && (
|
||||
{needRefresh && showPrompt && (
|
||||
<StyledMessage type="info">
|
||||
<StyledSmallBody>
|
||||
<Trans>
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import { ExpirationPlugin } from 'workbox-expiration'
|
||||
import {
|
||||
cleanupOutdatedCaches,
|
||||
createHandlerBoundToURL,
|
||||
precacheAndRoute,
|
||||
} from 'workbox-precaching'
|
||||
import { NavigationRoute, registerRoute, Route } from 'workbox-routing'
|
||||
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching'
|
||||
import { offlineFallback } from 'workbox-recipes'
|
||||
import { registerRoute, Route, setDefaultHandler } from 'workbox-routing'
|
||||
import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'
|
||||
|
||||
declare let self: ServiceWorkerGlobalScope
|
||||
|
||||
const HOUR = 60 * 60
|
||||
const DAY = 24 * HOUR
|
||||
const YEAR = 365 * DAY
|
||||
const MONTH = YEAR / 12
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
|
@ -18,23 +20,41 @@ self.addEventListener('message', (event) => {
|
|||
|
||||
cleanupOutdatedCaches()
|
||||
|
||||
precacheAndRoute(self.__WB_MANIFEST)
|
||||
|
||||
// Allow work offline
|
||||
registerRoute(
|
||||
new NavigationRoute(
|
||||
createHandlerBoundToURL(
|
||||
// Filter EN files on the FR app and vice versa
|
||||
const precache = self.__WB_MANIFEST.filter(
|
||||
(entry) =>
|
||||
typeof entry !== 'string' &&
|
||||
!entry.url.includes(
|
||||
location.href.startsWith(import.meta.env.VITE_FR_BASE_URL)
|
||||
? 'mon-entreprise.html'
|
||||
: 'infrance.html'
|
||||
),
|
||||
{ denylist: [/^\/api\/.*/, /^\/twemoji\/.*/, /^\/dev\/storybook\/.*/] }
|
||||
)
|
||||
? 'infrance'
|
||||
: 'mon-entreprise'
|
||||
)
|
||||
)
|
||||
|
||||
const HOUR = 60 * 60
|
||||
const DAY = HOUR * 24
|
||||
const YEAR = DAY * 365
|
||||
precacheAndRoute(precache)
|
||||
|
||||
// Allow work offline
|
||||
offlineFallback({
|
||||
// When the user is offline and loads a page that is not cached, we return the French/English index file.
|
||||
pageFallback: location.href.startsWith(import.meta.env.VITE_FR_BASE_URL)
|
||||
? '/mon-entreprise.html'
|
||||
: '/infrance.html',
|
||||
})
|
||||
|
||||
// Set the default handler with network first so that every requests (css, js, html, image, etc.)
|
||||
// goes through the service worker and will be cache
|
||||
setDefaultHandler(
|
||||
new NetworkFirst({
|
||||
cacheName: 'default-network-first',
|
||||
networkTimeoutSeconds: 1,
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxAgeSeconds: 3 * MONTH,
|
||||
maxEntries: 40,
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
const networkFirstJS = new Route(
|
||||
({ sameOrigin, url }) => {
|
||||
|
@ -44,11 +64,10 @@ const networkFirstJS = new Route(
|
|||
cacheName: 'js-cache',
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxAgeSeconds: 30 * DAY,
|
||||
maxAgeSeconds: 1 * MONTH,
|
||||
maxEntries: 40,
|
||||
}),
|
||||
],
|
||||
fetchOptions: {},
|
||||
})
|
||||
)
|
||||
|
||||
|
@ -58,11 +77,13 @@ const staleWhileRevalidate = new Route(
|
|||
({ request, sameOrigin, url }) => {
|
||||
return (
|
||||
sameOrigin &&
|
||||
(url.pathname.startsWith('/twemoji/') || request.destination === 'image')
|
||||
(url.pathname.startsWith('/twemoji/') ||
|
||||
request.destination === 'image' ||
|
||||
request.destination === 'font')
|
||||
)
|
||||
},
|
||||
new StaleWhileRevalidate({
|
||||
cacheName: 'images',
|
||||
cacheName: 'media',
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxAgeSeconds: 1 * YEAR,
|
||||
|
@ -83,7 +104,7 @@ const networkFirstPolyfill = new Route(
|
|||
cacheName: 'external-polyfill',
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxAgeSeconds: 1 * YEAR,
|
||||
maxAgeSeconds: 3 * MONTH,
|
||||
maxEntries: 5,
|
||||
}),
|
||||
],
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Options } from 'vite-plugin-pwa'
|
||||
|
||||
export const pwaOptions: Partial<Options> = {
|
||||
selfDestroying: true, // Unregister PWA
|
||||
// selfDestroying: true, // Unregister PWA
|
||||
registerType: 'prompt',
|
||||
strategies: 'injectManifest',
|
||||
srcDir: 'source',
|
||||
|
@ -11,18 +11,18 @@ export const pwaOptions: Partial<Options> = {
|
|||
manifestTransforms: [
|
||||
(entries) => {
|
||||
const manifest = entries.filter(
|
||||
(entry) => !/assets\/.*(-legacy|lazy_)/.test(entry.url)
|
||||
(entry) =>
|
||||
!/assets\/.*(-legacy|lazy_)/.test(entry.url) &&
|
||||
(entry.url.endsWith('.html')
|
||||
? /(infrance|mon-entreprise)\.html/.test(entry.url)
|
||||
: true)
|
||||
)
|
||||
|
||||
return { manifest }
|
||||
},
|
||||
],
|
||||
},
|
||||
includeAssets: [
|
||||
'logo-*.png',
|
||||
'fonts/*.{woff,woff2}',
|
||||
'références-images/*.{jpg,png,svg}',
|
||||
],
|
||||
includeAssets: ['logo-*.png'],
|
||||
manifest: {
|
||||
start_url: '/',
|
||||
name: 'Mon entreprise',
|
||||
|
|
Loading…
Reference in New Issue