@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 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,2 @@
1
+ export * from 'serwist'
2
+ export * from './runtimeCaching'
@@ -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
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "exclude": ["**/node_modules", "**/.*/"],
3
+ "include": ["**/*.ts", "**/*.tsx"],
4
+ "extends": "@graphcommerce/typescript-config-pwa/nextjs.json",
5
+ "compilerOptions": {
6
+ "lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"],
7
+ },
8
+ }