@farm.js/pwa 0.1.0-beta.1 → 0.1.0-beta.3
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/dist/build.d.ts +4 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +195 -50
- package/dist/config.d.ts +0 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +67 -7
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/package.json +3 -3
package/dist/build.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export interface PwaBuildInput {
|
|
|
5
5
|
publicDir?: string;
|
|
6
6
|
preset: string;
|
|
7
7
|
basePath: string;
|
|
8
|
+
/** Prefix already present in emitted HTML paths (localized Farm SSG output). */
|
|
9
|
+
htmlBasePath?: string;
|
|
8
10
|
options: ResolvedPwaOptions;
|
|
9
11
|
}
|
|
10
12
|
export interface PwaBuildResult {
|
|
@@ -17,6 +19,8 @@ export interface PwaBuildResult {
|
|
|
17
19
|
}
|
|
18
20
|
export declare function writePwaBuildArtifacts(input: PwaBuildInput): Promise<PwaBuildResult>;
|
|
19
21
|
export interface GenerateServiceWorkerOptions {
|
|
22
|
+
basePath: string;
|
|
23
|
+
htmlBasePath?: string;
|
|
20
24
|
cacheId: string;
|
|
21
25
|
precacheUrls: string[];
|
|
22
26
|
staticRoutes: Record<string, string>;
|
package/dist/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAcD,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAcD,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CA8G1F;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,YAAY,EAAE,MAAM,GAAG,KAAK,CAAC;IAC7B,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC1B,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC/C;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,MAAM,CA0NnF;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK7E;AAiDD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGnE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKpE"}
|
package/dist/build.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { lstat, mkdir, readdir, readFile, realpath, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
const PRECACHE_EXTENSIONS = new Set([
|
|
5
5
|
".css",
|
|
@@ -15,8 +15,10 @@ const PRECACHE_EXTENSIONS = new Set([
|
|
|
15
15
|
export async function writePwaBuildArtifacts(input) {
|
|
16
16
|
const publicDir = input.publicDir ?? resolvePwaPublicDir(input.outputDir, input.preset);
|
|
17
17
|
const basePath = normalizeBasePath(input.basePath);
|
|
18
|
+
const htmlBasePath = normalizeBasePath(input.htmlBasePath);
|
|
18
19
|
const workerRelativePath = path.posix.join(basePath.replace(/^\//, ""), "sw.js");
|
|
19
20
|
const workerPath = path.join(publicDir, ...workerRelativePath.split("/"));
|
|
21
|
+
await assertWorkerPathInsidePublicDir(publicDir, workerPath);
|
|
20
22
|
if (input.options.serviceWorker) {
|
|
21
23
|
const sourcePath = path.resolve(input.root ?? process.cwd(), input.options.serviceWorker.source);
|
|
22
24
|
let source;
|
|
@@ -38,23 +40,25 @@ export async function writePwaBuildArtifacts(input) {
|
|
|
38
40
|
};
|
|
39
41
|
}
|
|
40
42
|
const files = await walkFiles(publicDir);
|
|
41
|
-
const staticRouteFiles = resolveStaticRouteFiles(files);
|
|
43
|
+
const staticRouteFiles = resolveStaticRouteFiles(files, htmlBasePath);
|
|
42
44
|
const selectedRoutes = selectStaticRoutes(staticRouteFiles, input.options.cache.staticRoutes, basePath);
|
|
43
45
|
const configuredOfflineRoute = input.options.offline;
|
|
44
46
|
const offlineRoute = configuredOfflineRoute
|
|
45
47
|
? withBasePath(configuredOfflineRoute, basePath)
|
|
46
48
|
: false;
|
|
47
49
|
if (offlineRoute && configuredOfflineRoute) {
|
|
48
|
-
const emittedOfflineFile = staticRouteFiles[configuredOfflineRoute]
|
|
50
|
+
const emittedOfflineFile = staticRouteFiles[configuredOfflineRoute];
|
|
49
51
|
if (!emittedOfflineFile) {
|
|
50
52
|
throw new Error(`[farm:pwa] Offline route ${JSON.stringify(input.options.offline)} was not emitted as a static page. ` +
|
|
51
53
|
"Create that page as a static Farm route so it can be precached.");
|
|
52
54
|
}
|
|
53
55
|
selectedRoutes[offlineRoute] = emittedOfflineFile;
|
|
54
56
|
}
|
|
55
|
-
const assetFiles = files.filter((file) => file !==
|
|
57
|
+
const assetFiles = files.filter((file) => toPublicUrl(file, basePath) !== withBasePath("/sw.js", basePath) &&
|
|
58
|
+
PRECACHE_EXTENSIONS.has(path.extname(file).toLowerCase()));
|
|
56
59
|
const precacheFiles = [...new Set([...assetFiles, ...Object.values(selectedRoutes)])].sort();
|
|
57
|
-
const
|
|
60
|
+
const htmlFiles = new Set(Object.values(selectedRoutes));
|
|
61
|
+
const precacheUrls = precacheFiles.map((file) => toPublicUrl(file, basePath, htmlFiles.has(file) ? htmlBasePath : basePath));
|
|
58
62
|
const fileHashes = await Promise.all(precacheFiles.map(async (file) => {
|
|
59
63
|
const content = await readFile(path.join(publicDir, file));
|
|
60
64
|
return `${file}:${createHash("sha256").update(content).digest("hex")}`;
|
|
@@ -62,6 +66,7 @@ export async function writePwaBuildArtifacts(input) {
|
|
|
62
66
|
const cacheId = createHash("sha256")
|
|
63
67
|
.update(JSON.stringify({
|
|
64
68
|
files: fileHashes,
|
|
69
|
+
urls: precacheUrls,
|
|
65
70
|
routes: selectedRoutes,
|
|
66
71
|
offlineRoute,
|
|
67
72
|
update: input.options.update,
|
|
@@ -71,6 +76,8 @@ export async function writePwaBuildArtifacts(input) {
|
|
|
71
76
|
.slice(0, 16);
|
|
72
77
|
await mkdir(path.dirname(workerPath), { recursive: true });
|
|
73
78
|
await writeFile(workerPath, generateServiceWorker({
|
|
79
|
+
basePath,
|
|
80
|
+
htmlBasePath,
|
|
74
81
|
cacheId,
|
|
75
82
|
precacheUrls,
|
|
76
83
|
staticRoutes: selectedRoutes,
|
|
@@ -88,11 +95,22 @@ export async function writePwaBuildArtifacts(input) {
|
|
|
88
95
|
};
|
|
89
96
|
}
|
|
90
97
|
export function generateServiceWorker(options) {
|
|
91
|
-
const routeFiles = Object.fromEntries(Object.entries(options.staticRoutes).map(([route, file]) => [
|
|
92
|
-
|
|
98
|
+
const routeFiles = Object.fromEntries(Object.entries(options.staticRoutes).map(([route, file]) => [
|
|
99
|
+
encodeRoutePathname(route),
|
|
100
|
+
toPublicUrl(file, options.basePath, options.htmlBasePath ?? "/"),
|
|
101
|
+
]));
|
|
102
|
+
const offlineFile = options.offlineRoute
|
|
103
|
+
? (routeFiles[encodeRoutePathname(options.offlineRoute)] ?? null)
|
|
104
|
+
: null;
|
|
105
|
+
const cacheScope = createHash("sha256")
|
|
106
|
+
.update(normalizeBasePath(options.basePath))
|
|
107
|
+
.digest("hex")
|
|
108
|
+
.slice(0, 16);
|
|
109
|
+
const precachePrefix = `farm-pwa-precache-${cacheScope}-`;
|
|
93
110
|
return `/* Generated by @farm.js/pwa. */
|
|
94
|
-
const
|
|
95
|
-
const
|
|
111
|
+
const PRECACHE_PREFIX = ${JSON.stringify(precachePrefix)};
|
|
112
|
+
const PRECACHE = ${JSON.stringify(`${precachePrefix}${options.cacheId}`)};
|
|
113
|
+
const IMAGE_CACHE = ${JSON.stringify(`farm-pwa-images-${cacheScope}-v1`)};
|
|
96
114
|
const PRECACHE_URLS = ${JSON.stringify(options.precacheUrls)};
|
|
97
115
|
const PRECACHE_PATHS = new Set(PRECACHE_URLS.map((value) => new URL(value, self.location.origin).pathname));
|
|
98
116
|
const STATIC_ROUTES = ${JSON.stringify(routeFiles)};
|
|
@@ -111,7 +129,7 @@ self.addEventListener("activate", (event) => {
|
|
|
111
129
|
event.waitUntil((async () => {
|
|
112
130
|
const names = await caches.keys();
|
|
113
131
|
await Promise.all(names
|
|
114
|
-
.filter((name) => name.startsWith(
|
|
132
|
+
.filter((name) => name.startsWith(PRECACHE_PREFIX) && name !== PRECACHE)
|
|
115
133
|
.map((name) => caches.delete(name)));
|
|
116
134
|
await self.clients.claim();
|
|
117
135
|
})());
|
|
@@ -139,7 +157,9 @@ self.addEventListener("fetch", (event) => {
|
|
|
139
157
|
}
|
|
140
158
|
|
|
141
159
|
if (IMAGE_OPTIONS && request.destination === "image") {
|
|
142
|
-
|
|
160
|
+
const image = staleWhileRevalidateImage(request);
|
|
161
|
+
event.respondWith(image.response);
|
|
162
|
+
event.waitUntil(image.lifetime);
|
|
143
163
|
}
|
|
144
164
|
});
|
|
145
165
|
|
|
@@ -166,48 +186,107 @@ async function handleNavigation(request, url) {
|
|
|
166
186
|
}
|
|
167
187
|
}
|
|
168
188
|
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
function staleWhileRevalidateImage(request) {
|
|
190
|
+
let update = Promise.resolve();
|
|
191
|
+
const response = (async () => {
|
|
192
|
+
if (request.headers.has("authorization")) return fetch(request);
|
|
171
193
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
194
|
+
let cache;
|
|
195
|
+
let cached;
|
|
196
|
+
try {
|
|
197
|
+
cache = await caches.open(IMAGE_CACHE);
|
|
198
|
+
cached = await cache.match(request);
|
|
199
|
+
} catch {
|
|
200
|
+
// Runtime caching is optional when browser storage is unavailable.
|
|
201
|
+
return fetch(request);
|
|
202
|
+
}
|
|
203
|
+
const cachedAt = Number(cached?.headers.get("x-farm-pwa-cached-at") || 0);
|
|
204
|
+
const fresh = cached && Date.now() - cachedAt <= IMAGE_OPTIONS.ttlMs;
|
|
205
|
+
update = fetchAndCacheImage(request, cache);
|
|
177
206
|
|
|
178
|
-
|
|
179
|
-
void update.catch(() => undefined);
|
|
180
|
-
return cached;
|
|
181
|
-
}
|
|
207
|
+
if (fresh) return cached;
|
|
182
208
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
209
|
+
try {
|
|
210
|
+
return await update;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (cached) return cached;
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
})();
|
|
216
|
+
const lifetime = response
|
|
217
|
+
.then(
|
|
218
|
+
() => update,
|
|
219
|
+
() => update,
|
|
220
|
+
)
|
|
221
|
+
.then(
|
|
222
|
+
() => undefined,
|
|
223
|
+
() => undefined,
|
|
224
|
+
);
|
|
225
|
+
return { response, lifetime };
|
|
189
226
|
}
|
|
190
227
|
|
|
191
228
|
async function fetchAndCacheImage(request, cache) {
|
|
192
229
|
const response = await fetch(request);
|
|
193
230
|
if (!isPublicCacheableImage(response)) return response;
|
|
194
231
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
232
|
+
let stored;
|
|
233
|
+
try {
|
|
234
|
+
const headers = new Headers(response.headers);
|
|
235
|
+
headers.set("x-farm-pwa-cached-at", String(Date.now()));
|
|
236
|
+
stored = new Response(response.clone().body, {
|
|
237
|
+
status: response.status,
|
|
238
|
+
statusText: response.statusText,
|
|
239
|
+
headers,
|
|
240
|
+
});
|
|
241
|
+
await cache.put(request, stored);
|
|
242
|
+
await trimImageCache(cache, IMAGE_OPTIONS.limit);
|
|
243
|
+
} catch {
|
|
244
|
+
// Quota and storage failures must not discard a successful network response.
|
|
245
|
+
// Do not wait for tee cancellation, which can depend on the response consumer.
|
|
246
|
+
if (stored?.body) void stored.body.cancel().catch(() => undefined);
|
|
247
|
+
}
|
|
204
248
|
return response;
|
|
205
249
|
}
|
|
206
250
|
|
|
251
|
+
// Cache Storage is shared by every account that signs in on this browser, and a
|
|
252
|
+
// service worker cannot see the request's cookies to tell those accounts apart.
|
|
253
|
+
// So an image is kept only when the server marked it shareable or it looks like a
|
|
254
|
+
// static file, the same responses the browser's own HTTP cache would reuse.
|
|
207
255
|
function isPublicCacheableImage(response) {
|
|
208
256
|
if (!response.ok || response.type === "opaque") return false;
|
|
209
|
-
const
|
|
210
|
-
|
|
257
|
+
const directives = parseCacheControl(response.headers.get("cache-control"));
|
|
258
|
+
if (directives.has("private") || directives.has("no-store") || directives.has("no-cache")) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
const vary = (response.headers.get("vary") || "")
|
|
262
|
+
.toLowerCase()
|
|
263
|
+
.split(",")
|
|
264
|
+
.map((value) => value.trim());
|
|
265
|
+
// A response that varies on the caller's identity cannot be keyed here.
|
|
266
|
+
if (vary.includes("*") || vary.includes("cookie") || vary.includes("authorization")) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (directives.has("max-age")) return Number(directives.get("max-age")) > 0;
|
|
270
|
+
if (directives.has("public")) return true;
|
|
271
|
+
const expires = Date.parse(response.headers.get("expires") || "");
|
|
272
|
+
if (Number.isFinite(expires)) return expires > Date.now();
|
|
273
|
+
// Heuristic freshness (RFC 9111 section 4.2.2) needs a Last-Modified validator.
|
|
274
|
+
// Static files carry one. A dynamic response with no caching headers at all, such
|
|
275
|
+
// as an API route returning the signed-in account's avatar, does not, and keeping
|
|
276
|
+
// it would show that image to whoever uses this browser next.
|
|
277
|
+
return response.headers.has("last-modified");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function parseCacheControl(value) {
|
|
281
|
+
const directives = new Map();
|
|
282
|
+
for (const part of (value || "").toLowerCase().split(",")) {
|
|
283
|
+
const separator = part.indexOf("=");
|
|
284
|
+
const name = (separator === -1 ? part : part.slice(0, separator)).trim();
|
|
285
|
+
if (!name) continue;
|
|
286
|
+
const raw = separator === -1 ? "" : part.slice(separator + 1).trim();
|
|
287
|
+
directives.set(name, raw.startsWith('"') && raw.endsWith('"') ? raw.slice(1, -1) : raw);
|
|
288
|
+
}
|
|
289
|
+
return directives;
|
|
211
290
|
}
|
|
212
291
|
|
|
213
292
|
async function trimImageCache(cache, limit) {
|
|
@@ -234,6 +313,46 @@ function normalizePathname(pathname) {
|
|
|
234
313
|
export function resolvePwaPublicDir(outputDir, preset) {
|
|
235
314
|
return path.join(outputDir, preset === "vercel" || preset === "vercel-edge" ? "static" : "public");
|
|
236
315
|
}
|
|
316
|
+
async function assertWorkerPathInsidePublicDir(publicDir, workerPath) {
|
|
317
|
+
const prospectivePublicDir = await resolveProspectiveRealPath(publicDir);
|
|
318
|
+
const prospectiveWorkerPath = await resolveProspectiveRealPath(workerPath);
|
|
319
|
+
const relativePath = path.relative(prospectivePublicDir, prospectiveWorkerPath);
|
|
320
|
+
if (relativePath === ".." ||
|
|
321
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
322
|
+
path.isAbsolute(relativePath)) {
|
|
323
|
+
throw new Error("[farm:pwa] Service worker output must stay inside the public output directory, including through symlinks.");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async function resolveProspectiveRealPath(candidate) {
|
|
327
|
+
const missingSegments = [];
|
|
328
|
+
let existingAncestor = path.resolve(candidate);
|
|
329
|
+
while (true) {
|
|
330
|
+
try {
|
|
331
|
+
return path.join(await realpath(existingAncestor), ...missingSegments);
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
if (error.code !== "ENOENT")
|
|
335
|
+
throw error;
|
|
336
|
+
let existingEntry = false;
|
|
337
|
+
try {
|
|
338
|
+
await lstat(existingAncestor);
|
|
339
|
+
existingEntry = true;
|
|
340
|
+
}
|
|
341
|
+
catch (statError) {
|
|
342
|
+
if (statError.code !== "ENOENT")
|
|
343
|
+
throw statError;
|
|
344
|
+
}
|
|
345
|
+
if (existingEntry) {
|
|
346
|
+
throw new Error("[farm:pwa] Service worker output must stay inside the public output directory, including through symlinks.");
|
|
347
|
+
}
|
|
348
|
+
const parent = path.dirname(existingAncestor);
|
|
349
|
+
if (parent === existingAncestor)
|
|
350
|
+
throw error;
|
|
351
|
+
missingSegments.unshift(path.basename(existingAncestor));
|
|
352
|
+
existingAncestor = parent;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
237
356
|
export function normalizeBasePath(value) {
|
|
238
357
|
if (!value || value === "/")
|
|
239
358
|
return "/";
|
|
@@ -244,9 +363,6 @@ export function withBasePath(route, basePath) {
|
|
|
244
363
|
const normalizedRoute = route === "/" ? "/" : `/${route.replace(/^\/+|\/+$/g, "")}`;
|
|
245
364
|
if (normalizedBase === "/")
|
|
246
365
|
return normalizedRoute;
|
|
247
|
-
if (normalizedRoute === normalizedBase || normalizedRoute.startsWith(`${normalizedBase}/`)) {
|
|
248
|
-
return normalizedRoute;
|
|
249
|
-
}
|
|
250
366
|
return normalizedRoute === "/" ? normalizedBase : `${normalizedBase}${normalizedRoute}`;
|
|
251
367
|
}
|
|
252
368
|
async function walkFiles(root) {
|
|
@@ -260,7 +376,7 @@ async function walkFiles(root) {
|
|
|
260
376
|
}));
|
|
261
377
|
return files.flat().sort();
|
|
262
378
|
}
|
|
263
|
-
function resolveStaticRouteFiles(files) {
|
|
379
|
+
function resolveStaticRouteFiles(files, htmlBasePath) {
|
|
264
380
|
const routes = {};
|
|
265
381
|
for (const file of files) {
|
|
266
382
|
if (path.extname(file).toLowerCase() !== ".html")
|
|
@@ -270,7 +386,7 @@ function resolveStaticRouteFiles(files) {
|
|
|
270
386
|
const route = withoutIndex
|
|
271
387
|
? `/${withoutIndex.replace(/\.html$/, "").replace(/^\/+|\/+$/g, "")}`
|
|
272
388
|
: "/";
|
|
273
|
-
routes[route] = normalized;
|
|
389
|
+
routes[stripBasePath(route, htmlBasePath)] = normalized;
|
|
274
390
|
}
|
|
275
391
|
return routes;
|
|
276
392
|
}
|
|
@@ -283,7 +399,7 @@ function selectStaticRoutes(emitted, configured, basePath) {
|
|
|
283
399
|
const selected = {};
|
|
284
400
|
for (const requested of configured) {
|
|
285
401
|
const route = withBasePath(requested, basePath);
|
|
286
|
-
const emittedFile = emitted[requested]
|
|
402
|
+
const emittedFile = emitted[requested];
|
|
287
403
|
if (!emittedFile) {
|
|
288
404
|
throw new Error(`[farm:pwa] Static route ${JSON.stringify(requested)} was not found in the production output.`);
|
|
289
405
|
}
|
|
@@ -291,10 +407,39 @@ function selectStaticRoutes(emitted, configured, basePath) {
|
|
|
291
407
|
}
|
|
292
408
|
return selected;
|
|
293
409
|
}
|
|
294
|
-
function
|
|
295
|
-
|
|
296
|
-
|
|
410
|
+
function stripBasePath(route, basePath) {
|
|
411
|
+
const base = normalizeBasePath(basePath);
|
|
412
|
+
if (base === "/")
|
|
413
|
+
return route;
|
|
414
|
+
if (route === base)
|
|
415
|
+
return "/";
|
|
416
|
+
return route.startsWith(`${base}/`) ? route.slice(base.length) : route;
|
|
417
|
+
}
|
|
418
|
+
function toPublicUrl(file, basePath, fileBasePath = basePath) {
|
|
419
|
+
// Generated assets can already live under basePath. HTML uses the explicit
|
|
420
|
+
// SSG output prefix instead: a folder named like basePath can be a real route.
|
|
421
|
+
const route = stripBasePath(`/${file.replace(/\\/g, "/")}`, fileBasePath);
|
|
422
|
+
return withBasePath(encodePathname(route), basePath);
|
|
423
|
+
}
|
|
424
|
+
// Encode a route path the way browsers encode `location.pathname` so the
|
|
425
|
+
// STATIC_ROUTES keys match the pathname a live navigation produces at runtime.
|
|
426
|
+
// encodeURIComponent (used by toPublicUrl for asset URLs) is wrong for the keys:
|
|
427
|
+
// it percent-encodes ordinary path characters such as @ , = + ; $ that browsers
|
|
428
|
+
// leave untouched, so a route like /@alice would be keyed as /%40alice and never
|
|
429
|
+
// match a navigation to /@alice, silently falling through to the offline page.
|
|
430
|
+
function encodeRoutePathname(pathname) {
|
|
431
|
+
return new URL(pathname, "http://farm.localhost").pathname;
|
|
432
|
+
}
|
|
433
|
+
function encodePathname(pathname) {
|
|
434
|
+
// Match how a browser encodes location.pathname. encodeURIComponent is safe
|
|
435
|
+
// for every character but over-encodes the path sub-delimiters and ":"/"@"
|
|
436
|
+
// that browsers leave literal, so a precached asset like /logo@2x.css would
|
|
437
|
+
// be stored (and its PRECACHE_PATHS key derived) as /logo%402x.css and never
|
|
438
|
+
// match the live request for /logo@2x.css. Restore that path-safe set while
|
|
439
|
+
// leaving "?", "#", spaces and non-ASCII percent-encoded, the same divergence
|
|
440
|
+
// encodeRoutePathname documents for static-route keys.
|
|
441
|
+
return pathname
|
|
297
442
|
.split("/")
|
|
298
|
-
.map((segment) => encodeURIComponent(segment))
|
|
299
|
-
.join("/")
|
|
443
|
+
.map((segment) => encodeURIComponent(segment).replace(/%(24|26|2B|2C|3A|3B|3D|40)/gi, (match) => decodeURIComponent(match)))
|
|
444
|
+
.join("/");
|
|
300
445
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -21,8 +21,6 @@ export interface PwaServiceWorkerOptions {
|
|
|
21
21
|
type?: "classic" | "module";
|
|
22
22
|
}
|
|
23
23
|
export interface PwaPluginOptions {
|
|
24
|
-
/** Set false to keep the plugin registered without emitting or registering a worker. */
|
|
25
|
-
enabled?: boolean;
|
|
26
24
|
/** Static route to serve after an offline navigation misses the cache. */
|
|
27
25
|
offline?: string | false;
|
|
28
26
|
/** How a waiting service worker becomes active. */
|
|
@@ -38,7 +36,6 @@ export interface ResolvedPwaImageCacheOptions {
|
|
|
38
36
|
ttlMs: number;
|
|
39
37
|
}
|
|
40
38
|
export interface ResolvedPwaOptions {
|
|
41
|
-
enabled: boolean;
|
|
42
39
|
offline: string | false;
|
|
43
40
|
update: "prompt" | "auto";
|
|
44
41
|
serviceWorker: Required<PwaServiceWorkerOptions> | false;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,GAAG,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE,CAAC;AAE7E,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,QAAQ,EAAE,KAAK,CAAC;IAChB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,GAAG,CAAC,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,KAAK,GAAG,oBAAoB,CAAC;AAEnE,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,EAAE,CAAC;IAClC,kDAAkD;IAClD,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,GAAG,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE,CAAC;AAE7E,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,QAAQ,EAAE,KAAK,CAAC;IAChB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,GAAG,CAAC,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,KAAK,GAAG,oBAAoB,CAAC;AAEnE,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,EAAE,CAAC;IAClC,kDAAkD;IAClD,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACzB,mDAAmD;IACnD,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,eAAe,GAAG,KAAK,CAAC;IACzD,6EAA6E;IAC7E,aAAa,CAAC,EAAE,uBAAuB,CAAC;CACzC;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,KAAK,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC1B,aAAa,EAAE,QAAQ,CAAC,uBAAuB,CAAC,GAAG,KAAK,CAAC;IACzD,KAAK,EAAE;QACL,YAAY,EAAE,OAAO,GAAG,MAAM,EAAE,CAAC;QACjC,MAAM,EAAE,4BAA4B,GAAG,KAAK,CAAC;KAC9C,CAAC;CACH;AAKD,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,gBAAqB,GAAG,kBAAkB,CA8DpF;AAoBD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CA0B3D"}
|
package/dist/config.js
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
const DEFAULT_IMAGE_LIMIT = 100;
|
|
2
2
|
const DEFAULT_IMAGE_TTL = "30d";
|
|
3
3
|
export function resolvePwaOptions(options = {}) {
|
|
4
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
5
|
+
throw new TypeError("PWA options must be an object");
|
|
6
|
+
}
|
|
7
|
+
if ("enabled" in options) {
|
|
8
|
+
throw new TypeError("PWA no longer accepts enabled; remove pwa() from plugins to disable it");
|
|
9
|
+
}
|
|
10
|
+
if (options.offline !== undefined &&
|
|
11
|
+
options.offline !== false &&
|
|
12
|
+
typeof options.offline !== "string") {
|
|
13
|
+
throw new TypeError('PWA offline must be a route starting with "/" or false');
|
|
14
|
+
}
|
|
15
|
+
if (options.update !== undefined && options.update !== "prompt" && options.update !== "auto") {
|
|
16
|
+
throw new TypeError('PWA update must be "prompt" or "auto"');
|
|
17
|
+
}
|
|
4
18
|
const serviceWorker = resolveServiceWorker(options.serviceWorker);
|
|
5
19
|
if (serviceWorker && (options.offline !== undefined || options.cache !== undefined)) {
|
|
6
20
|
throw new TypeError("PWA serviceWorker cannot be combined with offline or cache because the custom worker owns those behaviors");
|
|
7
21
|
}
|
|
8
|
-
const
|
|
22
|
+
const configuredCache = options.cache;
|
|
23
|
+
if (configuredCache !== undefined &&
|
|
24
|
+
configuredCache !== false &&
|
|
25
|
+
configuredCache !== "auto" &&
|
|
26
|
+
configuredCache !== "recommended" &&
|
|
27
|
+
(!configuredCache || typeof configuredCache !== "object" || Array.isArray(configuredCache))) {
|
|
28
|
+
throw new TypeError('PWA cache must be "auto", "recommended", an options object, or false');
|
|
29
|
+
}
|
|
30
|
+
const cache = configuredCache ?? "auto";
|
|
9
31
|
const usesAutomaticCache = cache === "auto" || cache === "recommended";
|
|
10
32
|
const customCache = typeof cache === "object" ? cache : undefined;
|
|
11
33
|
return {
|
|
12
|
-
enabled: options.enabled !== false,
|
|
13
34
|
offline: serviceWorker ? false : normalizeRoute(options.offline ?? false, "offline"),
|
|
14
35
|
update: options.update ?? "prompt",
|
|
15
36
|
serviceWorker,
|
|
@@ -20,20 +41,26 @@ export function resolvePwaOptions(options = {}) {
|
|
|
20
41
|
? true
|
|
21
42
|
: cache === false
|
|
22
43
|
? false
|
|
23
|
-
: normalizeStaticRoutes(customCache?.staticRoutes
|
|
44
|
+
: normalizeStaticRoutes(customCache?.staticRoutes === undefined ? false : customCache.staticRoutes),
|
|
24
45
|
images: serviceWorker
|
|
25
46
|
? false
|
|
26
47
|
: usesAutomaticCache
|
|
27
48
|
? resolveImageCache("swr")
|
|
28
49
|
: cache === false
|
|
29
50
|
? false
|
|
30
|
-
: resolveImageCache(customCache?.images
|
|
51
|
+
: resolveImageCache(customCache?.images === undefined ? false : customCache.images),
|
|
31
52
|
},
|
|
32
53
|
};
|
|
33
54
|
}
|
|
34
55
|
function resolveServiceWorker(value) {
|
|
35
|
-
if (
|
|
56
|
+
if (value === undefined)
|
|
36
57
|
return false;
|
|
58
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
59
|
+
throw new TypeError("PWA serviceWorker must be an options object");
|
|
60
|
+
}
|
|
61
|
+
if (typeof value.source !== "string") {
|
|
62
|
+
throw new TypeError("PWA serviceWorker source must be a non-empty path");
|
|
63
|
+
}
|
|
37
64
|
const source = value.source.trim();
|
|
38
65
|
if (!source)
|
|
39
66
|
throw new TypeError("PWA serviceWorker source must be a non-empty path");
|
|
@@ -69,7 +96,9 @@ export function parsePwaDuration(value) {
|
|
|
69
96
|
function resolveImageCache(value) {
|
|
70
97
|
if (value === false)
|
|
71
98
|
return false;
|
|
72
|
-
if (value !== true &&
|
|
99
|
+
if (value !== true &&
|
|
100
|
+
value !== "swr" &&
|
|
101
|
+
(!value || typeof value !== "object" || Array.isArray(value) || value.strategy !== "swr")) {
|
|
73
102
|
throw new TypeError('PWA image cache strategy must be "swr"');
|
|
74
103
|
}
|
|
75
104
|
const configured = typeof value === "object" ? value : undefined;
|
|
@@ -84,8 +113,11 @@ function resolveImageCache(value) {
|
|
|
84
113
|
};
|
|
85
114
|
}
|
|
86
115
|
function normalizeStaticRoutes(value) {
|
|
87
|
-
if (
|
|
116
|
+
if (typeof value === "boolean")
|
|
88
117
|
return value;
|
|
118
|
+
if (!Array.isArray(value)) {
|
|
119
|
+
throw new TypeError("PWA cache staticRoutes must be boolean or an array of routes");
|
|
120
|
+
}
|
|
89
121
|
return [...new Set(value.map((route) => normalizeRoute(route, "static route")))];
|
|
90
122
|
}
|
|
91
123
|
function normalizeRoute(value, label) {
|
|
@@ -95,8 +127,36 @@ function normalizeRoute(value, label) {
|
|
|
95
127
|
if (!route.startsWith("/")) {
|
|
96
128
|
throw new TypeError(`PWA ${label} must start with "/"`);
|
|
97
129
|
}
|
|
130
|
+
if (route.startsWith("//")) {
|
|
131
|
+
throw new TypeError(`PWA ${label} must be an application pathname, not a URL`);
|
|
132
|
+
}
|
|
98
133
|
if (route.includes("?") || route.includes("#")) {
|
|
99
134
|
throw new TypeError(`PWA ${label} cannot contain a query string or fragment`);
|
|
100
135
|
}
|
|
136
|
+
if (hasUnstablePathCharacters(route)) {
|
|
137
|
+
throw new TypeError(`PWA ${label} cannot contain backslashes or control characters`);
|
|
138
|
+
}
|
|
139
|
+
for (const segment of route.split("/")) {
|
|
140
|
+
let decoded = segment;
|
|
141
|
+
try {
|
|
142
|
+
decoded = decodeURIComponent(segment);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Malformed escapes remain literal URL pathname segments.
|
|
146
|
+
}
|
|
147
|
+
if (hasUnstablePathCharacters(decoded) || decoded.includes("/")) {
|
|
148
|
+
throw new TypeError(`PWA ${label} cannot contain encoded path separators`);
|
|
149
|
+
}
|
|
150
|
+
if (decoded === "." || decoded === "..") {
|
|
151
|
+
throw new TypeError(`PWA ${label} cannot contain "." or ".." path segments`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
101
154
|
return route === "/" ? route : route.replace(/\/+$/, "");
|
|
102
155
|
}
|
|
156
|
+
function hasUnstablePathCharacters(value) {
|
|
157
|
+
return (value.includes("\\") ||
|
|
158
|
+
Array.from(value).some((character) => {
|
|
159
|
+
const code = character.charCodeAt(0);
|
|
160
|
+
return code <= 31 || (code >= 127 && code <= 159);
|
|
161
|
+
}));
|
|
162
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,10 +22,9 @@ declare global {
|
|
|
22
22
|
export declare function pwa(options?: PwaPluginOptions): import("@farm.js/core/plugin").FarmPlugin<undefined, Record<string, never>, {
|
|
23
23
|
cleanup(): void;
|
|
24
24
|
} | undefined, {
|
|
25
|
-
enabled: boolean;
|
|
26
25
|
workerUrl: string;
|
|
27
26
|
scope: string;
|
|
28
27
|
update: "prompt" | "auto";
|
|
29
28
|
workerType: "classic" | "module";
|
|
30
|
-
}, unknown, false>;
|
|
29
|
+
}, unknown, false, import("@farm.js/core/plugin").PluginRoutes>;
|
|
31
30
|
//# 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":"AAEA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC7B,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,GACxB,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,yBAAyB,CAAC;IACxC,WAAW,IAAI,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,YAAY,EAAE,yBAAyB,CAAC;IACxC,WAAW,IAAI,IAAI,CAAC;CACrB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,mDAAmD;QACnD,YAAY,CAAC,EAAE,qBAAqB,CAAC;KACtC;CACF;AAED;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,OAAO,GAAE,gBAAqB
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC7B,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,GACxB,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,yBAAyB,CAAC;IACxC,WAAW,IAAI,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,YAAY,EAAE,yBAAyB,CAAC;IACxC,WAAW,IAAI,IAAI,CAAC;CACrB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,mDAAmD;QACnD,YAAY,CAAC,EAAE,qBAAqB,CAAC;KACtC;CACF;AAED;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,OAAO,GAAE,gBAAqB;;;;;;;gEAgJjD"}
|
package/dist/index.js
CHANGED
|
@@ -10,9 +10,9 @@ export function pwa(options = {}) {
|
|
|
10
10
|
const resolved = resolvePwaOptions(options);
|
|
11
11
|
let configuredRoot = ".";
|
|
12
12
|
let configuredBasePath = "/";
|
|
13
|
+
let htmlBasePath = "/";
|
|
13
14
|
let configuredOutputDir = ".farm/.output";
|
|
14
15
|
const publicConfig = {
|
|
15
|
-
enabled: resolved.enabled,
|
|
16
16
|
workerUrl: "/sw.js",
|
|
17
17
|
scope: "/",
|
|
18
18
|
update: resolved.update,
|
|
@@ -24,14 +24,16 @@ export function pwa(options = {}) {
|
|
|
24
24
|
configuredRoot = config.root ?? ".";
|
|
25
25
|
const basePath = normalizeBasePath(config.basePath);
|
|
26
26
|
configuredBasePath = basePath;
|
|
27
|
+
// Only localized SSG output already includes the application's basePath.
|
|
28
|
+
const i18n = config.i18n;
|
|
29
|
+
htmlBasePath =
|
|
30
|
+
i18n && typeof i18n === "object" && "enabled" in i18n && i18n.enabled ? basePath : "/";
|
|
27
31
|
configuredOutputDir = `${config.root ?? "."}/.farm/.output`;
|
|
28
32
|
publicConfig.workerUrl = withBasePath("/sw.js", basePath);
|
|
29
33
|
publicConfig.scope = basePath === "/" ? "/" : `${basePath}/`;
|
|
30
34
|
},
|
|
31
35
|
build: {
|
|
32
36
|
configure(nitroConfig) {
|
|
33
|
-
if (!resolved.enabled)
|
|
34
|
-
return nitroConfig;
|
|
35
37
|
let generated = false;
|
|
36
38
|
const hooks = nitroConfig.hooks ?? {};
|
|
37
39
|
const previousPrerenderDone = hooks["prerender:done"];
|
|
@@ -45,6 +47,7 @@ export function pwa(options = {}) {
|
|
|
45
47
|
publicDir: nitroConfig.output?.publicDir,
|
|
46
48
|
preset: nitroConfig.preset ?? "node-server",
|
|
47
49
|
basePath: configuredBasePath,
|
|
50
|
+
htmlBasePath,
|
|
48
51
|
options: resolved,
|
|
49
52
|
});
|
|
50
53
|
generated = true;
|
|
@@ -73,7 +76,7 @@ export function pwa(options = {}) {
|
|
|
73
76
|
client: {
|
|
74
77
|
public: publicConfig,
|
|
75
78
|
async setup({ public: config, isProd }) {
|
|
76
|
-
if (!
|
|
79
|
+
if (!isProd || !("serviceWorker" in navigator))
|
|
77
80
|
return undefined;
|
|
78
81
|
const registration = await navigator.serviceWorker.register(config.workerUrl, config.workerType === "module"
|
|
79
82
|
? { scope: config.scope, type: "module" }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farm.js/pwa",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.3",
|
|
4
4
|
"description": "Installable, offline-aware Progressive Web Apps for Farm.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"farm.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@farm.js/core": "0.1.0-beta.
|
|
35
|
+
"@farm.js/core": "0.1.0-beta.107"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^20.10.5",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"vitest": "^3.2.7"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
|
-
"node": ">=
|
|
43
|
+
"node": ">=22.13.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc",
|