@graphcommerce/service-worker 9.0.0-canary.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/index.ts +2 -0
- package/nextImagePlugin.ts +69 -0
- package/package.json +25 -0
- package/runtimeCaching.ts +197 -0
- package/tsconfig.json +8 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @graphcommerce/service-worker
|
|
2
|
+
|
|
3
|
+
## 9.0.0-canary.108
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#2439](https://github.com/graphcommerce-org/graphcommerce/pull/2439) [`6061226`](https://github.com/graphcommerce-org/graphcommerce/commit/60612265466e4c508a2d3f478ff679251e7819de) - Moved to serwist for service workers ([@paales](https://github.com/paales))
|
package/index.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { filterNonNullableKeys } from '@graphcommerce/next-ui/RenderType/filterNonNullableKeys'
|
|
2
|
+
import type { SerwistPlugin } from 'serwist'
|
|
3
|
+
|
|
4
|
+
function extractImageProps(request: Request): {
|
|
5
|
+
request: Request
|
|
6
|
+
url?: string | null
|
|
7
|
+
w?: number | null
|
|
8
|
+
q?: number | null
|
|
9
|
+
} {
|
|
10
|
+
const url = new URL(request.url)
|
|
11
|
+
return {
|
|
12
|
+
request,
|
|
13
|
+
url: url.searchParams.get('url'),
|
|
14
|
+
w: parseInt(url.searchParams.get('w') ?? '0', 10),
|
|
15
|
+
q: parseInt(url.searchParams.get('q') ?? '0', 10),
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function findRelatedImages(request: Request, cacheName: string) {
|
|
20
|
+
const { w, url, q } = extractImageProps(request)
|
|
21
|
+
if (!url) return undefined
|
|
22
|
+
if (!w) return undefined
|
|
23
|
+
if (!q) return undefined
|
|
24
|
+
|
|
25
|
+
const cache = await caches.open(cacheName)
|
|
26
|
+
const requests = (await cache.keys()).map(extractImageProps)
|
|
27
|
+
const cached = filterNonNullableKeys(requests, ['w', 'url', 'q'])
|
|
28
|
+
.filter((c) => c.url === url && c.q === q)
|
|
29
|
+
.sort((a, b) => a.w - b.w)
|
|
30
|
+
return { w, url, q, cached }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function nextImagePlugin(cacheName: string): SerwistPlugin {
|
|
34
|
+
return {
|
|
35
|
+
// cacheDidUpdate: async ({ request }) => {
|
|
36
|
+
// const result = await findRelatedImages(request, cacheName)
|
|
37
|
+
// if (!result || result.cached.length <= 1) return
|
|
38
|
+
|
|
39
|
+
// console.log(
|
|
40
|
+
// result.cached.map((c) => c.request.url),
|
|
41
|
+
// 'are the cached versions',
|
|
42
|
+
// result.url,
|
|
43
|
+
// 'should be the same as',
|
|
44
|
+
// request.url,
|
|
45
|
+
// )
|
|
46
|
+
// const cache = await caches.open(cacheName)
|
|
47
|
+
// const toDelete = result.cached.slice(0, -1).map(async (c) => {
|
|
48
|
+
// await cache.delete(c.request)
|
|
49
|
+
// return c.request.url
|
|
50
|
+
// })
|
|
51
|
+
// console.log('Deleted smaller images from cache', await Promise.all(toDelete))
|
|
52
|
+
// },
|
|
53
|
+
cacheKeyWillBeUsed: async ({ mode, request }) => {
|
|
54
|
+
if (mode !== 'read') return request
|
|
55
|
+
|
|
56
|
+
const result = await findRelatedImages(request, cacheName)
|
|
57
|
+
if (!result) return request
|
|
58
|
+
|
|
59
|
+
const found = result.cached.find((c) => c.w > result.w)
|
|
60
|
+
|
|
61
|
+
if (found) {
|
|
62
|
+
// console.log('Same image, but bigger found in the cache, using that one', found.request.url)
|
|
63
|
+
return found.request
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return request
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@graphcommerce/service-worker",
|
|
3
|
+
"homepage": "https://www.graphcommerce.org/",
|
|
4
|
+
"repository": "github:graphcommerce-org/graphcommerce",
|
|
5
|
+
"version": "9.0.0-canary.108",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
8
|
+
"eslintConfig": {
|
|
9
|
+
"extends": "@graphcommerce/eslint-config-pwa",
|
|
10
|
+
"parserOptions": {
|
|
11
|
+
"project": "./tsconfig.json"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@serwist/next": "^9.0.10",
|
|
16
|
+
"serwist": "^9.0.10"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@graphcommerce/eslint-config-pwa": "^9.0.0-canary.108",
|
|
20
|
+
"@graphcommerce/prettier-config-pwa": "^9.0.0-canary.108",
|
|
21
|
+
"@graphcommerce/typescript-config-pwa": "^9.0.0-canary.108",
|
|
22
|
+
"next": "*",
|
|
23
|
+
"webpack": "^5.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { PAGES_CACHE_NAME } from '@serwist/next/worker'
|
|
2
|
+
import type { RuntimeCaching } from 'serwist'
|
|
3
|
+
import {
|
|
4
|
+
CacheFirst,
|
|
5
|
+
ExpirationPlugin,
|
|
6
|
+
NetworkFirst,
|
|
7
|
+
NetworkOnly,
|
|
8
|
+
RangeRequestsPlugin,
|
|
9
|
+
StaleWhileRevalidate,
|
|
10
|
+
} from 'serwist'
|
|
11
|
+
import { nextImagePlugin } from './nextImagePlugin'
|
|
12
|
+
|
|
13
|
+
const devCaching = [{ matcher: /.*/i, handler: new NetworkOnly() }]
|
|
14
|
+
|
|
15
|
+
const handlers: RuntimeCaching[] = [
|
|
16
|
+
{
|
|
17
|
+
matcher: /\/_next\/image\?url=.+$/i,
|
|
18
|
+
handler: new StaleWhileRevalidate({
|
|
19
|
+
cacheName: 'next-image',
|
|
20
|
+
plugins: [
|
|
21
|
+
nextImagePlugin('next-image'),
|
|
22
|
+
new ExpirationPlugin({
|
|
23
|
+
maxEntries: 1000,
|
|
24
|
+
maxAgeSeconds: 168 * 60 * 60,
|
|
25
|
+
matchOptions: { ignoreVary: true },
|
|
26
|
+
purgeOnQuotaError: true,
|
|
27
|
+
maxAgeFrom: 'last-used',
|
|
28
|
+
}),
|
|
29
|
+
],
|
|
30
|
+
}),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
matcher: /\/_next\/data\/[^/]+\/.+\.json(\?.*)?$/i,
|
|
34
|
+
handler: new NetworkFirst({
|
|
35
|
+
cacheName: 'next-data',
|
|
36
|
+
plugins: [
|
|
37
|
+
new ExpirationPlugin({
|
|
38
|
+
maxEntries: 32,
|
|
39
|
+
maxAgeSeconds: 24 * 60 * 60,
|
|
40
|
+
maxAgeFrom: 'last-used',
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
matcher: ({ sameOrigin, url: { pathname } }) => sameOrigin && pathname.startsWith('/api/'),
|
|
47
|
+
method: 'GET',
|
|
48
|
+
handler: new NetworkFirst({
|
|
49
|
+
cacheName: 'apis',
|
|
50
|
+
plugins: [
|
|
51
|
+
new ExpirationPlugin({
|
|
52
|
+
maxEntries: 16,
|
|
53
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
54
|
+
maxAgeFrom: 'last-used',
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
57
|
+
networkTimeoutSeconds: 10, // fallback to cache if API does not response within 10 seconds
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
matcher: ({ request, url: { pathname }, sameOrigin }) =>
|
|
62
|
+
request.headers.get('RSC') === '1' &&
|
|
63
|
+
request.headers.get('Next-Router-Prefetch') === '1' &&
|
|
64
|
+
sameOrigin &&
|
|
65
|
+
!pathname.startsWith('/api/'),
|
|
66
|
+
handler: new NetworkFirst({
|
|
67
|
+
cacheName: PAGES_CACHE_NAME.rscPrefetch,
|
|
68
|
+
plugins: [
|
|
69
|
+
new ExpirationPlugin({
|
|
70
|
+
maxEntries: 32,
|
|
71
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
72
|
+
}),
|
|
73
|
+
],
|
|
74
|
+
}),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
matcher: ({ request, url: { pathname }, sameOrigin }) =>
|
|
78
|
+
request.headers.get('RSC') === '1' && sameOrigin && !pathname.startsWith('/api/'),
|
|
79
|
+
handler: new NetworkFirst({
|
|
80
|
+
cacheName: PAGES_CACHE_NAME.rsc,
|
|
81
|
+
plugins: [
|
|
82
|
+
new ExpirationPlugin({
|
|
83
|
+
maxEntries: 32,
|
|
84
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
85
|
+
}),
|
|
86
|
+
],
|
|
87
|
+
}),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
matcher: ({ request, url: { pathname }, sameOrigin }) =>
|
|
91
|
+
(!request.headers.get('Content-Type') ||
|
|
92
|
+
request.headers.get('Content-Type')?.includes('text/html')) &&
|
|
93
|
+
sameOrigin &&
|
|
94
|
+
!pathname.startsWith('/api/'),
|
|
95
|
+
handler: new NetworkFirst({
|
|
96
|
+
cacheName: PAGES_CACHE_NAME.html,
|
|
97
|
+
plugins: [
|
|
98
|
+
new ExpirationPlugin({
|
|
99
|
+
maxEntries: 32,
|
|
100
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
101
|
+
}),
|
|
102
|
+
],
|
|
103
|
+
}),
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
{
|
|
107
|
+
matcher: /\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,
|
|
108
|
+
handler: new StaleWhileRevalidate({
|
|
109
|
+
cacheName: 'runtime-font',
|
|
110
|
+
plugins: [
|
|
111
|
+
new ExpirationPlugin({
|
|
112
|
+
maxEntries: 4,
|
|
113
|
+
maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days
|
|
114
|
+
maxAgeFrom: 'last-used',
|
|
115
|
+
}),
|
|
116
|
+
],
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
matcher: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
|
|
121
|
+
handler: new StaleWhileRevalidate({
|
|
122
|
+
cacheName: 'runtime-image',
|
|
123
|
+
plugins: [
|
|
124
|
+
new ExpirationPlugin({
|
|
125
|
+
maxEntries: 64,
|
|
126
|
+
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
|
|
127
|
+
maxAgeFrom: 'last-used',
|
|
128
|
+
}),
|
|
129
|
+
],
|
|
130
|
+
}),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
matcher: /\.(?:mp3|wav|ogg)$/i,
|
|
134
|
+
handler: new CacheFirst({
|
|
135
|
+
cacheName: 'runtime-audio',
|
|
136
|
+
plugins: [
|
|
137
|
+
new ExpirationPlugin({
|
|
138
|
+
maxEntries: 32,
|
|
139
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
140
|
+
maxAgeFrom: 'last-used',
|
|
141
|
+
}),
|
|
142
|
+
new RangeRequestsPlugin(),
|
|
143
|
+
],
|
|
144
|
+
}),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
matcher: /\.(?:mp4|webm)$/i,
|
|
148
|
+
handler: new CacheFirst({
|
|
149
|
+
cacheName: 'runtime-video',
|
|
150
|
+
plugins: [
|
|
151
|
+
new ExpirationPlugin({
|
|
152
|
+
maxEntries: 32,
|
|
153
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
154
|
+
maxAgeFrom: 'last-used',
|
|
155
|
+
}),
|
|
156
|
+
new RangeRequestsPlugin(),
|
|
157
|
+
],
|
|
158
|
+
}),
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
matcher: ({ url: { pathname }, sameOrigin }) =>
|
|
162
|
+
/\.(?:js)$/i.test(pathname) && !pathname.includes('/_next/static/') && sameOrigin,
|
|
163
|
+
handler: new StaleWhileRevalidate({
|
|
164
|
+
cacheName: 'runtime-js',
|
|
165
|
+
plugins: [
|
|
166
|
+
new ExpirationPlugin({
|
|
167
|
+
maxEntries: 48,
|
|
168
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
169
|
+
maxAgeFrom: 'last-used',
|
|
170
|
+
}),
|
|
171
|
+
],
|
|
172
|
+
}),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
matcher: ({ url: { pathname }, sameOrigin }) =>
|
|
176
|
+
/\.(?:css)$/i.test(pathname) && !pathname.includes('/_next/static/') && sameOrigin,
|
|
177
|
+
handler: new StaleWhileRevalidate({
|
|
178
|
+
cacheName: 'runtime-css',
|
|
179
|
+
plugins: [
|
|
180
|
+
new ExpirationPlugin({
|
|
181
|
+
maxEntries: 32,
|
|
182
|
+
maxAgeSeconds: 24 * 60 * 60, // 24 hours
|
|
183
|
+
maxAgeFrom: 'last-used',
|
|
184
|
+
}),
|
|
185
|
+
],
|
|
186
|
+
}),
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
matcher: ({ sameOrigin }) => !sameOrigin,
|
|
190
|
+
handler: new NetworkOnly(),
|
|
191
|
+
},
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
export const productionCaching = handlers
|
|
195
|
+
|
|
196
|
+
export const runtimeCaching: RuntimeCaching[] =
|
|
197
|
+
process.env.NODE_ENV !== 'production' ? devCaching : productionCaching
|