@farm.js/pwa 0.1.0-beta.2 → 0.1.0-beta.4
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 +1 -1
- package/dist/build.d.ts +3 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +105 -33
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Installable, offline-aware Progressive Web Apps for Farm.js. The plugin generate
|
|
|
4
4
|
service worker or copies a custom one during the production build, then registers it through Farm's
|
|
5
5
|
browser plugin lifecycle.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
This package is in beta. See [Stability and Support](https://farmjs.dev/docs/stability).
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
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 {
|
|
@@ -18,6 +20,7 @@ export interface PwaBuildResult {
|
|
|
18
20
|
export declare function writePwaBuildArtifacts(input: PwaBuildInput): Promise<PwaBuildResult>;
|
|
19
21
|
export interface GenerateServiceWorkerOptions {
|
|
20
22
|
basePath: string;
|
|
23
|
+
htmlBasePath?: string;
|
|
21
24
|
cacheId: string;
|
|
22
25
|
precacheUrls: string[];
|
|
23
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
|
@@ -15,6 +15,7 @@ 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("/"));
|
|
20
21
|
await assertWorkerPathInsidePublicDir(publicDir, workerPath);
|
|
@@ -39,14 +40,14 @@ export async function writePwaBuildArtifacts(input) {
|
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
42
|
const files = await walkFiles(publicDir);
|
|
42
|
-
const staticRouteFiles = resolveStaticRouteFiles(files);
|
|
43
|
+
const staticRouteFiles = resolveStaticRouteFiles(files, htmlBasePath);
|
|
43
44
|
const selectedRoutes = selectStaticRoutes(staticRouteFiles, input.options.cache.staticRoutes, basePath);
|
|
44
45
|
const configuredOfflineRoute = input.options.offline;
|
|
45
46
|
const offlineRoute = configuredOfflineRoute
|
|
46
47
|
? withBasePath(configuredOfflineRoute, basePath)
|
|
47
48
|
: false;
|
|
48
49
|
if (offlineRoute && configuredOfflineRoute) {
|
|
49
|
-
const emittedOfflineFile = staticRouteFiles[configuredOfflineRoute]
|
|
50
|
+
const emittedOfflineFile = staticRouteFiles[configuredOfflineRoute];
|
|
50
51
|
if (!emittedOfflineFile) {
|
|
51
52
|
throw new Error(`[farm:pwa] Offline route ${JSON.stringify(input.options.offline)} was not emitted as a static page. ` +
|
|
52
53
|
"Create that page as a static Farm route so it can be precached.");
|
|
@@ -56,7 +57,8 @@ export async function writePwaBuildArtifacts(input) {
|
|
|
56
57
|
const assetFiles = files.filter((file) => toPublicUrl(file, basePath) !== withBasePath("/sw.js", basePath) &&
|
|
57
58
|
PRECACHE_EXTENSIONS.has(path.extname(file).toLowerCase()));
|
|
58
59
|
const precacheFiles = [...new Set([...assetFiles, ...Object.values(selectedRoutes)])].sort();
|
|
59
|
-
const
|
|
60
|
+
const htmlFiles = new Set(Object.values(selectedRoutes));
|
|
61
|
+
const precacheUrls = precacheFiles.map((file) => toPublicUrl(file, basePath, htmlFiles.has(file) ? htmlBasePath : basePath));
|
|
60
62
|
const fileHashes = await Promise.all(precacheFiles.map(async (file) => {
|
|
61
63
|
const content = await readFile(path.join(publicDir, file));
|
|
62
64
|
return `${file}:${createHash("sha256").update(content).digest("hex")}`;
|
|
@@ -64,6 +66,7 @@ export async function writePwaBuildArtifacts(input) {
|
|
|
64
66
|
const cacheId = createHash("sha256")
|
|
65
67
|
.update(JSON.stringify({
|
|
66
68
|
files: fileHashes,
|
|
69
|
+
urls: precacheUrls,
|
|
67
70
|
routes: selectedRoutes,
|
|
68
71
|
offlineRoute,
|
|
69
72
|
update: input.options.update,
|
|
@@ -74,6 +77,7 @@ export async function writePwaBuildArtifacts(input) {
|
|
|
74
77
|
await mkdir(path.dirname(workerPath), { recursive: true });
|
|
75
78
|
await writeFile(workerPath, generateServiceWorker({
|
|
76
79
|
basePath,
|
|
80
|
+
htmlBasePath,
|
|
77
81
|
cacheId,
|
|
78
82
|
precacheUrls,
|
|
79
83
|
staticRoutes: selectedRoutes,
|
|
@@ -92,11 +96,11 @@ export async function writePwaBuildArtifacts(input) {
|
|
|
92
96
|
}
|
|
93
97
|
export function generateServiceWorker(options) {
|
|
94
98
|
const routeFiles = Object.fromEntries(Object.entries(options.staticRoutes).map(([route, file]) => [
|
|
95
|
-
|
|
96
|
-
toPublicUrl(file, options.basePath),
|
|
99
|
+
encodeRoutePathname(route),
|
|
100
|
+
toPublicUrl(file, options.basePath, options.htmlBasePath ?? "/"),
|
|
97
101
|
]));
|
|
98
102
|
const offlineFile = options.offlineRoute
|
|
99
|
-
? (routeFiles[
|
|
103
|
+
? (routeFiles[encodeRoutePathname(options.offlineRoute)] ?? null)
|
|
100
104
|
: null;
|
|
101
105
|
const cacheScope = createHash("sha256")
|
|
102
106
|
.update(normalizeBasePath(options.basePath))
|
|
@@ -187,8 +191,15 @@ function staleWhileRevalidateImage(request) {
|
|
|
187
191
|
const response = (async () => {
|
|
188
192
|
if (request.headers.has("authorization")) return fetch(request);
|
|
189
193
|
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
}
|
|
192
203
|
const cachedAt = Number(cached?.headers.get("x-farm-pwa-cached-at") || 0);
|
|
193
204
|
const fresh = cached && Date.now() - cachedAt <= IMAGE_OPTIONS.ttlMs;
|
|
194
205
|
update = fetchAndCacheImage(request, cache);
|
|
@@ -218,22 +229,64 @@ async function fetchAndCacheImage(request, cache) {
|
|
|
218
229
|
const response = await fetch(request);
|
|
219
230
|
if (!isPublicCacheableImage(response)) return response;
|
|
220
231
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
+
}
|
|
230
248
|
return response;
|
|
231
249
|
}
|
|
232
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.
|
|
233
255
|
function isPublicCacheableImage(response) {
|
|
234
256
|
if (!response.ok || response.type === "opaque") return false;
|
|
235
|
-
const
|
|
236
|
-
|
|
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;
|
|
237
290
|
}
|
|
238
291
|
|
|
239
292
|
async function trimImageCache(cache, limit) {
|
|
@@ -310,9 +363,6 @@ export function withBasePath(route, basePath) {
|
|
|
310
363
|
const normalizedRoute = route === "/" ? "/" : `/${route.replace(/^\/+|\/+$/g, "")}`;
|
|
311
364
|
if (normalizedBase === "/")
|
|
312
365
|
return normalizedRoute;
|
|
313
|
-
if (normalizedRoute === normalizedBase || normalizedRoute.startsWith(`${normalizedBase}/`)) {
|
|
314
|
-
return normalizedRoute;
|
|
315
|
-
}
|
|
316
366
|
return normalizedRoute === "/" ? normalizedBase : `${normalizedBase}${normalizedRoute}`;
|
|
317
367
|
}
|
|
318
368
|
async function walkFiles(root) {
|
|
@@ -326,7 +376,7 @@ async function walkFiles(root) {
|
|
|
326
376
|
}));
|
|
327
377
|
return files.flat().sort();
|
|
328
378
|
}
|
|
329
|
-
function resolveStaticRouteFiles(files) {
|
|
379
|
+
function resolveStaticRouteFiles(files, htmlBasePath) {
|
|
330
380
|
const routes = {};
|
|
331
381
|
for (const file of files) {
|
|
332
382
|
if (path.extname(file).toLowerCase() !== ".html")
|
|
@@ -336,7 +386,7 @@ function resolveStaticRouteFiles(files) {
|
|
|
336
386
|
const route = withoutIndex
|
|
337
387
|
? `/${withoutIndex.replace(/\.html$/, "").replace(/^\/+|\/+$/g, "")}`
|
|
338
388
|
: "/";
|
|
339
|
-
routes[route] = normalized;
|
|
389
|
+
routes[stripBasePath(route, htmlBasePath)] = normalized;
|
|
340
390
|
}
|
|
341
391
|
return routes;
|
|
342
392
|
}
|
|
@@ -349,7 +399,7 @@ function selectStaticRoutes(emitted, configured, basePath) {
|
|
|
349
399
|
const selected = {};
|
|
350
400
|
for (const requested of configured) {
|
|
351
401
|
const route = withBasePath(requested, basePath);
|
|
352
|
-
const emittedFile = emitted[requested]
|
|
402
|
+
const emittedFile = emitted[requested];
|
|
353
403
|
if (!emittedFile) {
|
|
354
404
|
throw new Error(`[farm:pwa] Static route ${JSON.stringify(requested)} was not found in the production output.`);
|
|
355
405
|
}
|
|
@@ -357,17 +407,39 @@ function selectStaticRoutes(emitted, configured, basePath) {
|
|
|
357
407
|
}
|
|
358
408
|
return selected;
|
|
359
409
|
}
|
|
360
|
-
function
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return
|
|
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;
|
|
367
432
|
}
|
|
368
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.
|
|
369
441
|
return pathname
|
|
370
442
|
.split("/")
|
|
371
|
-
.map((segment) => encodeURIComponent(segment))
|
|
443
|
+
.map((segment) => encodeURIComponent(segment).replace(/%(24|26|2B|2C|3A|3B|3D|40)/gi, (match) => decodeURIComponent(match)))
|
|
372
444
|
.join("/");
|
|
373
445
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,5 +26,5 @@ export declare function pwa(options?: PwaPluginOptions): import("@farm.js/core/p
|
|
|
26
26
|
scope: string;
|
|
27
27
|
update: "prompt" | "auto";
|
|
28
28
|
workerType: "classic" | "module";
|
|
29
|
-
}, unknown, false>;
|
|
29
|
+
}, unknown, false, import("@farm.js/core/plugin").PluginRoutes>;
|
|
30
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,6 +10,7 @@ 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
16
|
workerUrl: "/sw.js",
|
|
@@ -23,6 +24,10 @@ export function pwa(options = {}) {
|
|
|
23
24
|
configuredRoot = config.root ?? ".";
|
|
24
25
|
const basePath = normalizeBasePath(config.basePath);
|
|
25
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 : "/";
|
|
26
31
|
configuredOutputDir = `${config.root ?? "."}/.farm/.output`;
|
|
27
32
|
publicConfig.workerUrl = withBasePath("/sw.js", basePath);
|
|
28
33
|
publicConfig.scope = basePath === "/" ? "/" : `${basePath}/`;
|
|
@@ -42,6 +47,7 @@ export function pwa(options = {}) {
|
|
|
42
47
|
publicDir: nitroConfig.output?.publicDir,
|
|
43
48
|
preset: nitroConfig.preset ?? "node-server",
|
|
44
49
|
basePath: configuredBasePath,
|
|
50
|
+
htmlBasePath,
|
|
45
51
|
options: resolved,
|
|
46
52
|
});
|
|
47
53
|
generated = true;
|
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.4",
|
|
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
|
|
35
|
+
"@farm.js/core": "0.1.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^20.10.5",
|