-
Notifications
You must be signed in to change notification settings - Fork 34
/
service-worker.js
120 lines (85 loc) · 3.38 KB
/
service-worker.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
const CACHE_NAME = 'my-csr-app'
const allAssets = self.__WB_MANIFEST.map(({ url }) => url)
const createPromiseResolve = () => {
let resolve
const promise = new Promise(res => (resolve = res))
return [promise, resolve]
}
const [precacheAssetsPromise, precacheAssetsResolve] = createPromiseResolve()
const getCache = () => caches.open(CACHE_NAME)
const getCachedAssets = async cache => {
const keys = await cache.keys()
return keys.map(({ url }) => `/${url.replace(self.registration.scope, '')}`)
}
const getRequestHeaders = responseHeaders => {
const requestHeaders = { 'X-Cached': JSON.stringify(allAssets) }
if (responseHeaders) {
etag = responseHeaders.get('ETag') || responseHeaders.get('X-ETag')
requestHeaders = { 'If-None-Match': etag, ...requestHeaders }
}
return requestHeaders
}
const cacheInlineAssets = async assets => {
const cache = await getCache()
assets.forEach(({ url, source }) => {
const response = new Response(source, {
headers: {
'Cache-Control': 'public, max-age=31536000, immutable',
'Content-Type': 'application/javascript'
}
})
cache.put(url, response)
console.log(`Cached %c${url}`, 'color: yellow; font-style: italic;')
})
}
const precacheAssets = async ({ ignoreAssets }) => {
const cache = await getCache()
const cachedAssets = await getCachedAssets(cache)
const assetsToPrecache = allAssets.filter(asset => !cachedAssets.includes(asset) && !ignoreAssets.includes(asset))
await cache.addAll(assetsToPrecache)
await removeUnusedAssets()
await fetchDocument({ url: '/' })
}
const removeUnusedAssets = async () => {
const cache = await getCache()
const cachedAssets = await getCachedAssets(cache)
cachedAssets.forEach(asset => {
if (!allAssets.includes(asset)) cache.delete(asset)
})
}
const fetchDocument = async ({ url, preloadResponse }) => {
const cache = await getCache()
const cachedDocument = await cache.match('/')
const requestHeaders = getRequestHeaders(cachedDocument?.headers)
try {
const response = await (preloadResponse && cachedDocument ? preloadResponse : fetch(url, { headers: requestHeaders }))
if (response.status === 304) return cachedDocument
cache.put('/', response.clone())
const [client] = await self.clients.matchAll({ includeUncontrolled: true })
client?.postMessage({ navigationPreloadHeader: JSON.stringify(getRequestHeaders(response.headers)) })
return response
} catch (err) {
return cachedDocument
}
}
const fetchAsset = async request => {
const cache = await getCache()
const cachedResponse = await cache.match(request)
return cachedResponse || fetch(request)
}
self.addEventListener('install', event => {
event.waitUntil(precacheAssetsPromise)
self.skipWaiting()
})
self.addEventListener('activate', event => event.waitUntil(self.registration.navigationPreload?.enable()))
self.addEventListener('message', async event => {
const { inlineAssets } = event.data
await cacheInlineAssets(inlineAssets)
await precacheAssets({ ignoreAssets: inlineAssets.map(({ url }) => url) })
precacheAssetsResolve()
})
self.addEventListener('fetch', event => {
const { request, preloadResponse } = event
if (request.destination === 'document') return event.respondWith(fetchDocument({ url: request.url, preloadResponse }))
if (['font', 'script'].includes(request.destination)) event.respondWith(fetchAsset(request))
})