@baukit/pwa-web 0.2.1 → 0.4.0
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/README.md +77 -45
- package/dist/cache-cleanup.d.ts +14 -0
- package/dist/cache-cleanup.d.ts.map +1 -0
- package/dist/cache-cleanup.js +10 -0
- package/dist/cache-cleanup.js.map +1 -0
- package/dist/cjs/cache-cleanup.js +12 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/worker-entry.js +17 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/worker-entry.d.ts +2 -0
- package/dist/worker-entry.d.ts.map +1 -0
- package/dist/worker-entry.js +2 -0
- package/dist/worker-entry.js.map +1 -0
- package/dist/worker.js +155 -0
- package/package.json +17 -11
package/README.md
CHANGED
|
@@ -118,67 +118,99 @@ Behavior per strategy:
|
|
|
118
118
|
`putCache` receives the strategy that produced the response, which is how the snippet above
|
|
119
119
|
routes static assets and shell responses into different caches.
|
|
120
120
|
|
|
121
|
-
##
|
|
121
|
+
## Use the worker artifact
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
`@baukit/pwa-web/worker` resolves to a bundled classic script. It has no imports and assigns the
|
|
124
|
+
public API to `globalThis.BaukitPwa`. A build script can copy it without parsing or rewriting
|
|
125
|
+
package source:
|
|
125
126
|
|
|
126
127
|
```js
|
|
127
|
-
|
|
128
|
+
import { copyFile } from 'node:fs/promises';
|
|
129
|
+
import { fileURLToPath } from 'node:url';
|
|
130
|
+
|
|
131
|
+
const source = fileURLToPath(import.meta.resolve('@baukit/pwa-web/worker'));
|
|
132
|
+
await copyFile(source, 'public/baukit-pwa-worker.js');
|
|
128
133
|
```
|
|
129
134
|
|
|
130
|
-
|
|
131
|
-
`scripts/build-sw.mjs` reads the installed ESM files, removes their module syntax, and writes one
|
|
132
|
-
classic script. It exposes the package through `globalThis.BaukitPwa`, so a classic worker can use
|
|
133
|
-
`importScripts('/sw-strategy.js')`.
|
|
135
|
+
Load the copy before product worker code:
|
|
134
136
|
|
|
135
137
|
```js
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
// public/sw.js
|
|
139
|
+
importScripts('/baukit-pwa-worker.js');
|
|
140
|
+
|
|
141
|
+
const handleFetch = globalThis.BaukitPwa.createFetchHandler({
|
|
142
|
+
// Product cache policy and ports go here.
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Register `sw.js` as a normal classic service worker:
|
|
139
147
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
async function readDistFile(name) {
|
|
145
|
-
const source = await readFile(join(distDir, name), 'utf8');
|
|
146
|
-
return source
|
|
147
|
-
.replace(/import\s*\{[^}]+\}\s*from\s*['"]\.\/cache-strategy\.js['"];\s*/s, '')
|
|
148
|
-
.replace(/^export /gm, '')
|
|
149
|
-
.replace(/^\/\/# sourceMappingURL=.*$/gm, '')
|
|
150
|
-
.trim();
|
|
148
|
+
```js
|
|
149
|
+
if ('serviceWorker' in navigator) {
|
|
150
|
+
await navigator.serviceWorker.register('/sw.js');
|
|
151
151
|
}
|
|
152
|
+
```
|
|
152
153
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
createFetchHandler,
|
|
160
|
-
decideCacheStrategy,
|
|
161
|
-
});
|
|
162
|
-
`;
|
|
154
|
+
The IIFE format is deliberate. Vite copies its `public/` directory into `dist/`, and Expo web does
|
|
155
|
+
the same during static export. Both paths can copy this artifact byte for byte. Neither path needs
|
|
156
|
+
bare module imports in a browser, module-worker registration, or package-specific bundler setup.
|
|
157
|
+
The packed npm tarball test loads the artifact with no `window`, `document`, or `self` global.
|
|
158
|
+
|
|
159
|
+
### Migrate a source-rewriting build
|
|
163
160
|
|
|
164
|
-
|
|
161
|
+
Replace code that reads `dist/cache-strategy.js`, removes imports or exports, and concatenates files
|
|
162
|
+
with one copy from `@baukit/pwa-web/worker`. Keep the product-owned `sw.js` and its cache policy.
|
|
163
|
+
Imports from `@baukit/pwa-web` and CommonJS `require('@baukit/pwa-web')` remain supported.
|
|
164
|
+
|
|
165
|
+
## Cache cleanup
|
|
166
|
+
|
|
167
|
+
Products decide which exact cache names belong to an old version or identity. `cleanupCaches`
|
|
168
|
+
provides the list-and-delete operation and returns counts without returning or logging cache names.
|
|
169
|
+
This activate handler removes old application cache versions while preserving the current version
|
|
170
|
+
and caches owned by other code:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
const CURRENT_APP_CACHE = 'notes-app-v4';
|
|
174
|
+
|
|
175
|
+
self.addEventListener('activate', (event) => {
|
|
176
|
+
event.waitUntil(
|
|
177
|
+
globalThis.BaukitPwa.cleanupCaches({
|
|
178
|
+
ports: {
|
|
179
|
+
listCacheNames: () => caches.keys(),
|
|
180
|
+
deleteCache: (name) => caches.delete(name),
|
|
181
|
+
},
|
|
182
|
+
shouldDelete: (name) => name.startsWith('notes-app-') && name !== CURRENT_APP_CACHE,
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
});
|
|
165
186
|
```
|
|
166
187
|
|
|
167
|
-
|
|
188
|
+
Private caches need a product-owned, opaque partition key. On an identity change, delete the exact
|
|
189
|
+
previous partition before exposing the new identity's data. On logout, delete every private cache
|
|
190
|
+
for the outgoing partition. Do not put access tokens, email addresses, or raw identity-provider
|
|
191
|
+
subjects in cache names.
|
|
168
192
|
|
|
169
|
-
```
|
|
170
|
-
{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
193
|
+
```ts
|
|
194
|
+
import { cleanupCaches } from '@baukit/pwa-web';
|
|
195
|
+
|
|
196
|
+
await cleanupCaches({
|
|
197
|
+
ports: {
|
|
198
|
+
listCacheNames: () => caches.keys(),
|
|
199
|
+
deleteCache: (name) => caches.delete(name),
|
|
200
|
+
},
|
|
201
|
+
shouldDelete: (name) => name === `notes-private-${outgoingPartitionKey}`,
|
|
202
|
+
});
|
|
176
203
|
```
|
|
177
204
|
|
|
178
|
-
|
|
179
|
-
|
|
205
|
+
`cleanupCaches` selects all names before starting deletes, then runs the deletes together. It counts
|
|
206
|
+
a cache as deleted only when `CacheStorage.delete` resolves to `true`. A list, predicate, or delete
|
|
207
|
+
failure rejects the operation. Some deletes may already have completed when another delete rejects,
|
|
208
|
+
so retry cleanup before completing logout or an identity switch.
|
|
180
209
|
|
|
181
210
|
## What this package does not do
|
|
182
211
|
|
|
183
|
-
It does not register the service worker, precache an app shell,
|
|
184
|
-
|
|
212
|
+
It does not register the service worker, precache an app shell, choose cache names, define routes,
|
|
213
|
+
create a web manifest or icons, prompt for updates, choose an offline route, or derive identity
|
|
214
|
+
partition keys. Those depend on the product's build output, privacy model, and copy. Baukit's CLI
|
|
215
|
+
still requires the web capability for PWA use. Publishing this artifact does not add PWA generation
|
|
216
|
+
to Expo web projects.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CacheCleanupPorts {
|
|
2
|
+
readonly listCacheNames: () => Promise<readonly string[]>;
|
|
3
|
+
readonly deleteCache: (name: string) => Promise<boolean>;
|
|
4
|
+
}
|
|
5
|
+
export interface CacheCleanupOptions {
|
|
6
|
+
readonly ports: CacheCleanupPorts;
|
|
7
|
+
readonly shouldDelete: (name: string) => boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface CacheCleanupResult {
|
|
10
|
+
readonly matchedCount: number;
|
|
11
|
+
readonly deletedCount: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function cleanupCaches(options: CacheCleanupOptions): Promise<CacheCleanupResult>;
|
|
14
|
+
//# sourceMappingURL=cache-cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-cleanup.d.ts","sourceRoot":"","sources":["../src/cache-cleanup.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAC1D,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAClD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAS7F"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export async function cleanupCaches(options) {
|
|
2
|
+
const names = await options.ports.listCacheNames();
|
|
3
|
+
const selected = names.filter(options.shouldDelete);
|
|
4
|
+
const deleted = await Promise.all(selected.map((name) => options.ports.deleteCache(name)));
|
|
5
|
+
return {
|
|
6
|
+
matchedCount: selected.length,
|
|
7
|
+
deletedCount: deleted.filter(Boolean).length,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=cache-cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-cleanup.js","sourceRoot":"","sources":["../src/cache-cleanup.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA4B;IAC9D,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;IACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE3F,OAAO;QACL,YAAY,EAAE,QAAQ,CAAC,MAAM;QAC7B,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM;KAC7C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cleanupCaches = cleanupCaches;
|
|
4
|
+
async function cleanupCaches(options) {
|
|
5
|
+
const names = await options.ports.listCacheNames();
|
|
6
|
+
const selected = names.filter(options.shouldDelete);
|
|
7
|
+
const deleted = await Promise.all(selected.map((name) => options.ports.deleteCache(name)));
|
|
8
|
+
return {
|
|
9
|
+
matchedCount: selected.length,
|
|
10
|
+
deletedCount: deleted.filter(Boolean).length,
|
|
11
|
+
};
|
|
12
|
+
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createFetchHandler = exports.decideCacheStrategy = exports.createCacheStrategyDecider = exports.CACHE_STRATEGIES = void 0;
|
|
3
|
+
exports.createFetchHandler = exports.decideCacheStrategy = exports.createCacheStrategyDecider = exports.CACHE_STRATEGIES = exports.cleanupCaches = void 0;
|
|
4
|
+
var cache_cleanup_js_1 = require("./cache-cleanup.js");
|
|
5
|
+
Object.defineProperty(exports, "cleanupCaches", { enumerable: true, get: function () { return cache_cleanup_js_1.cleanupCaches; } });
|
|
4
6
|
var cache_strategy_js_1 = require("./cache-strategy.js");
|
|
5
7
|
Object.defineProperty(exports, "CACHE_STRATEGIES", { enumerable: true, get: function () { return cache_strategy_js_1.CACHE_STRATEGIES; } });
|
|
6
8
|
Object.defineProperty(exports, "createCacheStrategyDecider", { enumerable: true, get: function () { return cache_strategy_js_1.createCacheStrategyDecider; } });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./index.js"), exports);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { cleanupCaches, type CacheCleanupOptions, type CacheCleanupPorts, type CacheCleanupResult, } from './cache-cleanup.js';
|
|
1
2
|
export { CACHE_STRATEGIES, createCacheStrategyDecider, decideCacheStrategy, type CacheStrategy, type CacheStrategyOptions, type CacheStrategyRequest, } from './cache-strategy.js';
|
|
2
3
|
export { createFetchHandler, type FetchHandlerOptions, type FetchHandlerPorts, type FetchLikeRequest, } from './fetch-handler.js';
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,mBAAmB,GAIpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,GAInB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,GAId,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,mBAAmB,GAIpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,GAInB,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-entry.d.ts","sourceRoot":"","sources":["../src/worker-entry.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-entry.js","sourceRoot":"","sources":["../src/worker-entry.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC"}
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
this.globalThis = this.globalThis || {};
|
|
2
|
+
this.globalThis.BaukitPwa = (function(exports) {
|
|
3
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
|
+
//#region src/cache-cleanup.ts
|
|
5
|
+
async function cleanupCaches(options) {
|
|
6
|
+
const selected = (await options.ports.listCacheNames()).filter(options.shouldDelete);
|
|
7
|
+
const deleted = await Promise.all(selected.map((name) => options.ports.deleteCache(name)));
|
|
8
|
+
return {
|
|
9
|
+
matchedCount: selected.length,
|
|
10
|
+
deletedCount: deleted.filter(Boolean).length
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/cache-strategy.ts
|
|
15
|
+
const CACHE_STRATEGIES = {
|
|
16
|
+
cacheFirst: "cache-first",
|
|
17
|
+
networkFirst: "network-first",
|
|
18
|
+
networkOnly: "network-only",
|
|
19
|
+
staleWhileRevalidate: "stale-while-revalidate"
|
|
20
|
+
};
|
|
21
|
+
const DEFAULT_API_PATH_PREFIXES = ["/api"];
|
|
22
|
+
const DEFAULT_STATIC_DESTINATIONS = [
|
|
23
|
+
"audio",
|
|
24
|
+
"font",
|
|
25
|
+
"image",
|
|
26
|
+
"script",
|
|
27
|
+
"style",
|
|
28
|
+
"video",
|
|
29
|
+
"worker"
|
|
30
|
+
];
|
|
31
|
+
const DEFAULT_STATIC_EXTENSIONS = [
|
|
32
|
+
"avif",
|
|
33
|
+
"css",
|
|
34
|
+
"gif",
|
|
35
|
+
"ico",
|
|
36
|
+
"jpeg",
|
|
37
|
+
"jpg",
|
|
38
|
+
"js",
|
|
39
|
+
"json",
|
|
40
|
+
"mjs",
|
|
41
|
+
"mp3",
|
|
42
|
+
"mp4",
|
|
43
|
+
"ogg",
|
|
44
|
+
"otf",
|
|
45
|
+
"png",
|
|
46
|
+
"svg",
|
|
47
|
+
"ttf",
|
|
48
|
+
"webmanifest",
|
|
49
|
+
"webp",
|
|
50
|
+
"woff",
|
|
51
|
+
"woff2"
|
|
52
|
+
];
|
|
53
|
+
function matchesPathPrefix(pathname, prefix) {
|
|
54
|
+
if (prefix.length === 0) return false;
|
|
55
|
+
const normalized = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
56
|
+
return pathname === normalized || pathname.startsWith(`${normalized}/`);
|
|
57
|
+
}
|
|
58
|
+
function matchesAnyPathPrefix(pathname, prefixes) {
|
|
59
|
+
return prefixes.some((prefix) => matchesPathPrefix(pathname, prefix));
|
|
60
|
+
}
|
|
61
|
+
function hasStaticExtension(pathname, extensions) {
|
|
62
|
+
const lastSegment = pathname.slice(pathname.lastIndexOf("/") + 1);
|
|
63
|
+
const dotIndex = lastSegment.lastIndexOf(".");
|
|
64
|
+
if (dotIndex <= 0) return false;
|
|
65
|
+
const extension = lastSegment.slice(dotIndex + 1).toLowerCase();
|
|
66
|
+
return extensions.includes(extension);
|
|
67
|
+
}
|
|
68
|
+
function startsWithAny(pathname, prefixes) {
|
|
69
|
+
return prefixes.some((prefix) => prefix.length > 0 && pathname.startsWith(prefix));
|
|
70
|
+
}
|
|
71
|
+
function decideCacheStrategyDecision(request, options) {
|
|
72
|
+
const { appOrigin, apiPathPrefixes = DEFAULT_API_PATH_PREFIXES, navigationFallback, neverCachedPathPrefixes = [], staticPathPrefixes = [], staticDestinations = DEFAULT_STATIC_DESTINATIONS, staticExtensions = DEFAULT_STATIC_EXTENSIONS, apiStrategy = CACHE_STRATEGIES.networkFirst, staticStrategy = CACHE_STRATEGIES.cacheFirst, defaultStrategy = CACHE_STRATEGIES.networkFirst } = options;
|
|
73
|
+
const parsed = new URL(request.url, appOrigin);
|
|
74
|
+
const { pathname } = parsed;
|
|
75
|
+
const sameOrigin = parsed.origin === appOrigin;
|
|
76
|
+
if (matchesAnyPathPrefix(pathname, neverCachedPathPrefixes)) return { strategy: CACHE_STRATEGIES.networkOnly };
|
|
77
|
+
if ((request.method ?? "GET").toUpperCase() !== "GET") return { strategy: CACHE_STRATEGIES.networkOnly };
|
|
78
|
+
if (matchesAnyPathPrefix(pathname, apiPathPrefixes)) return { strategy: apiStrategy };
|
|
79
|
+
if (request.mode === "navigate") {
|
|
80
|
+
if (navigationFallback !== void 0 && sameOrigin) return {
|
|
81
|
+
strategy: defaultStrategy,
|
|
82
|
+
navigationFallback
|
|
83
|
+
};
|
|
84
|
+
return { strategy: defaultStrategy };
|
|
85
|
+
}
|
|
86
|
+
const staticAsset = request.destination !== void 0 && staticDestinations.includes(request.destination) || hasStaticExtension(pathname, staticExtensions) || startsWithAny(pathname, staticPathPrefixes);
|
|
87
|
+
return { strategy: sameOrigin && staticAsset ? staticStrategy : defaultStrategy };
|
|
88
|
+
}
|
|
89
|
+
function decideCacheStrategy(request, options) {
|
|
90
|
+
return decideCacheStrategyDecision(request, options).strategy;
|
|
91
|
+
}
|
|
92
|
+
function createCacheStrategyDecider(options) {
|
|
93
|
+
return (request) => decideCacheStrategy(request, options);
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/fetch-handler.ts
|
|
97
|
+
async function cacheFirst(request, ports) {
|
|
98
|
+
const cached = await ports.matchCache(request);
|
|
99
|
+
if (cached !== void 0) return cached;
|
|
100
|
+
const response = await ports.fetch(request);
|
|
101
|
+
if (ports.isCacheable(response)) await ports.putCache(CACHE_STRATEGIES.cacheFirst, request, ports.cloneResponse(response));
|
|
102
|
+
return response;
|
|
103
|
+
}
|
|
104
|
+
async function networkFirst(request, ports) {
|
|
105
|
+
try {
|
|
106
|
+
const response = await ports.fetch(request);
|
|
107
|
+
if (ports.isCacheable(response)) await ports.putCache(CACHE_STRATEGIES.networkFirst, request, ports.cloneResponse(response));
|
|
108
|
+
return response;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const cached = await ports.matchCache(request);
|
|
111
|
+
if (cached !== void 0) return cached;
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function staleWhileRevalidate(request, ports, onRevalidateError) {
|
|
116
|
+
const cached = await ports.matchCache(request);
|
|
117
|
+
const revalidate = (async () => {
|
|
118
|
+
const response = await ports.fetch(request);
|
|
119
|
+
if (ports.isCacheable(response)) await ports.putCache(CACHE_STRATEGIES.staleWhileRevalidate, request, ports.cloneResponse(response));
|
|
120
|
+
return response;
|
|
121
|
+
})();
|
|
122
|
+
if (cached === void 0) return revalidate;
|
|
123
|
+
revalidate.catch((error) => {
|
|
124
|
+
onRevalidateError?.(error);
|
|
125
|
+
});
|
|
126
|
+
return cached;
|
|
127
|
+
}
|
|
128
|
+
function createFetchHandler(options) {
|
|
129
|
+
const { ports, onRevalidateError, ...strategyOptions } = options;
|
|
130
|
+
return async (request) => {
|
|
131
|
+
const { strategy, navigationFallback } = decideCacheStrategyDecision(request, strategyOptions);
|
|
132
|
+
try {
|
|
133
|
+
switch (strategy) {
|
|
134
|
+
case CACHE_STRATEGIES.networkOnly: return await ports.fetch(request, { cache: "no-store" });
|
|
135
|
+
case CACHE_STRATEGIES.cacheFirst: return await cacheFirst(request, ports);
|
|
136
|
+
case CACHE_STRATEGIES.staleWhileRevalidate: return await staleWhileRevalidate(request, ports, onRevalidateError);
|
|
137
|
+
case CACHE_STRATEGIES.networkFirst: return await networkFirst(request, ports);
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (navigationFallback !== void 0) {
|
|
141
|
+
const fallback = await ports.matchCache(navigationFallback);
|
|
142
|
+
if (fallback !== void 0) return fallback;
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
exports.CACHE_STRATEGIES = CACHE_STRATEGIES;
|
|
150
|
+
exports.cleanupCaches = cleanupCaches;
|
|
151
|
+
exports.createCacheStrategyDecider = createCacheStrategyDecider;
|
|
152
|
+
exports.createFetchHandler = createFetchHandler;
|
|
153
|
+
exports.decideCacheStrategy = decideCacheStrategy;
|
|
154
|
+
return exports;
|
|
155
|
+
})({});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@baukit/pwa-web",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Cache strategy, cleanup, and a classic worker artifact for web PWAs.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Patrick Koss",
|
|
7
7
|
"homepage": "https://github.com/PatrickKoss/baukit#readme",
|
|
@@ -21,27 +21,33 @@
|
|
|
21
21
|
"node": ">=24.0.0"
|
|
22
22
|
},
|
|
23
23
|
"types": "./dist/index.d.ts",
|
|
24
|
+
"sideEffects": [
|
|
25
|
+
"./dist/worker.js"
|
|
26
|
+
],
|
|
24
27
|
"exports": {
|
|
25
28
|
".": {
|
|
26
29
|
"types": "./dist/index.d.ts",
|
|
27
30
|
"import": "./dist/index.js",
|
|
28
31
|
"require": "./dist/cjs/index.js"
|
|
29
|
-
}
|
|
32
|
+
},
|
|
33
|
+
"./worker": "./dist/worker.js"
|
|
30
34
|
},
|
|
31
35
|
"files": [
|
|
32
36
|
"dist",
|
|
33
37
|
"README.md",
|
|
34
38
|
"LICENSE"
|
|
35
39
|
],
|
|
36
|
-
"scripts": {
|
|
37
|
-
"build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node --input-type=module -e \"import { writeFileSync } from 'node:fs'; writeFileSync('dist/cjs/package.json', '{\\\"type\\\":\\\"commonjs\\\"}\\\\n')\"",
|
|
38
|
-
"prepare": "pnpm run build",
|
|
39
|
-
"test": "vitest run --config ../../vitest.config.ts",
|
|
40
|
-
"lint": "eslint src --max-warnings=0",
|
|
41
|
-
"format:check": "prettier --check src package.json tsconfig.json tsconfig.cjs.json"
|
|
42
|
-
},
|
|
43
40
|
"devDependencies": {
|
|
41
|
+
"rolldown": "1.2.3",
|
|
44
42
|
"typescript": "6.0.3",
|
|
45
43
|
"vitest": "4.1.10"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node scripts/build-worker.mjs && node --input-type=module -e \"import { writeFileSync } from 'node:fs'; writeFileSync('dist/cjs/package.json', '{\\\"type\\\":\\\"commonjs\\\"}\\\\n')\"",
|
|
47
|
+
"test": "pnpm run build && vitest run --config ../../vitest.config.ts && pnpm run test:artifact && pnpm run test:package",
|
|
48
|
+
"test:artifact": "node scripts/test-artifact.mjs",
|
|
49
|
+
"test:package": "node scripts/test-package.mjs",
|
|
50
|
+
"lint": "eslint src --max-warnings=0",
|
|
51
|
+
"format:check": "prettier --check src scripts package.json tsconfig.json tsconfig.cjs.json"
|
|
46
52
|
}
|
|
47
|
-
}
|
|
53
|
+
}
|