offline-page-kit 0.1.1 → 0.2.1
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/cli.cjs +266 -49
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +266 -49
- package/dist/cli.js.map +1 -1
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
|
-
var
|
|
27
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
28
28
|
|
|
29
29
|
// src/core/utils.ts
|
|
30
30
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
@@ -140,72 +140,117 @@ function buildServiceWorkerJS(options2) {
|
|
|
140
140
|
assetExtensions,
|
|
141
141
|
apiPrefixes
|
|
142
142
|
} = options2;
|
|
143
|
+
const precacheList = Array.from(
|
|
144
|
+
new Set([offlinePage, offlineImage, ...precache ?? []].filter(Boolean))
|
|
145
|
+
);
|
|
143
146
|
return `/* offline-page-kit service worker */
|
|
144
147
|
const CACHE_NAME = ${jsString(cacheName)};
|
|
145
148
|
const OFFLINE_PAGE = ${jsString(offlinePage)};
|
|
146
149
|
const OFFLINE_IMAGE = ${jsString(offlineImage)};
|
|
147
|
-
const PRECACHE = ${jsString(
|
|
150
|
+
const PRECACHE = ${jsString(precacheList)};
|
|
148
151
|
|
|
149
152
|
const HTML_STRATEGY = ${jsString(htmlStrategy)};
|
|
150
153
|
const ASSET_STRATEGY = ${jsString(assetStrategy)};
|
|
151
154
|
const IMAGE_STRATEGY = ${jsString(imageStrategy)};
|
|
152
155
|
|
|
153
156
|
const ASSET_EXTS = new Set(${jsString(assetExtensions.length ? assetExtensions : DEFAULT_EXTS)});
|
|
154
|
-
const API_PREFIXES = ${jsString(apiPrefixes)};
|
|
157
|
+
const API_PREFIXES = ${jsString(apiPrefixes || [])};
|
|
158
|
+
|
|
159
|
+
const SAME_ORIGIN_ONLY = true;
|
|
155
160
|
|
|
161
|
+
// ---------------------------
|
|
156
162
|
// helpers
|
|
157
|
-
|
|
163
|
+
// ---------------------------
|
|
164
|
+
const isSameOrigin = (url) => url.origin === self.location.origin;
|
|
165
|
+
|
|
166
|
+
const isApi = (url) => {
|
|
167
|
+
if (!API_PREFIXES || API_PREFIXES.length === 0) return false;
|
|
168
|
+
return API_PREFIXES.some((p) => {
|
|
169
|
+
try {
|
|
170
|
+
// allow either absolute prefix or pathname prefix
|
|
171
|
+
return url.href.startsWith(p) || url.pathname.startsWith(p);
|
|
172
|
+
} catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
158
178
|
const extOf = (pathname) => {
|
|
159
179
|
const i = pathname.lastIndexOf(".");
|
|
160
|
-
return i === -1 ? "" : pathname.slice(i+1).toLowerCase();
|
|
180
|
+
return i === -1 ? "" : pathname.slice(i + 1).toLowerCase();
|
|
161
181
|
};
|
|
162
182
|
|
|
163
|
-
|
|
183
|
+
const isImageExt = (ext) => ["png","jpg","jpeg","webp","gif","svg","ico"].includes(ext);
|
|
184
|
+
|
|
185
|
+
// Only cache successful, basic responses (avoid caching errors)
|
|
186
|
+
const isCacheableResponse = (res) => res && (res.ok || res.type === "opaque");
|
|
187
|
+
|
|
188
|
+
// Cache helpers
|
|
189
|
+
async function cacheGet(reqOrUrl) {
|
|
164
190
|
const cache = await caches.open(CACHE_NAME);
|
|
165
|
-
return cache.match(
|
|
191
|
+
return cache.match(reqOrUrl);
|
|
166
192
|
}
|
|
193
|
+
|
|
167
194
|
async function cachePut(req, res) {
|
|
168
|
-
|
|
169
|
-
|
|
195
|
+
try {
|
|
196
|
+
if (!isCacheableResponse(res)) return;
|
|
197
|
+
const cache = await caches.open(CACHE_NAME);
|
|
198
|
+
await cache.put(req, res);
|
|
199
|
+
} catch {
|
|
200
|
+
// ignore quota / put errors
|
|
201
|
+
}
|
|
170
202
|
}
|
|
171
|
-
|
|
203
|
+
|
|
204
|
+
async function cleanOldCaches() {
|
|
172
205
|
const keys = await caches.keys();
|
|
173
|
-
await Promise.all(keys.map(k => (k === CACHE_NAME ? null : caches.delete(k))));
|
|
206
|
+
await Promise.all(keys.map((k) => (k === CACHE_NAME ? null : caches.delete(k))));
|
|
174
207
|
}
|
|
175
208
|
|
|
209
|
+
// ---------------------------
|
|
176
210
|
// strategies
|
|
211
|
+
// ---------------------------
|
|
177
212
|
async function networkFirst(req, fallbackUrl) {
|
|
178
213
|
try {
|
|
179
214
|
const res = await fetch(req);
|
|
180
|
-
|
|
215
|
+
await cachePut(req, res.clone());
|
|
181
216
|
return res;
|
|
182
217
|
} catch {
|
|
183
218
|
const cached = await cacheGet(req);
|
|
184
219
|
if (cached) return cached;
|
|
185
|
-
if (fallbackUrl)
|
|
186
|
-
|
|
220
|
+
if (fallbackUrl) {
|
|
221
|
+
const fb = await cacheGet(fallbackUrl);
|
|
222
|
+
if (fb) return fb;
|
|
223
|
+
}
|
|
224
|
+
return new Response("Offline", { status: 503, headers: { "content-type": "text/plain" } });
|
|
187
225
|
}
|
|
188
226
|
}
|
|
189
227
|
|
|
190
228
|
async function cacheFirst(req, fallbackUrl) {
|
|
191
229
|
const cached = await cacheGet(req);
|
|
192
230
|
if (cached) return cached;
|
|
231
|
+
|
|
193
232
|
try {
|
|
194
233
|
const res = await fetch(req);
|
|
195
|
-
|
|
234
|
+
await cachePut(req, res.clone());
|
|
196
235
|
return res;
|
|
197
236
|
} catch {
|
|
198
|
-
if (fallbackUrl)
|
|
199
|
-
|
|
237
|
+
if (fallbackUrl) {
|
|
238
|
+
const fb = await cacheGet(fallbackUrl);
|
|
239
|
+
if (fb) return fb;
|
|
240
|
+
}
|
|
241
|
+
return new Response("Offline", { status: 503, headers: { "content-type": "text/plain" } });
|
|
200
242
|
}
|
|
201
243
|
}
|
|
202
244
|
|
|
203
245
|
async function staleWhileRevalidate(req) {
|
|
204
246
|
const cached = await cacheGet(req);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
247
|
+
|
|
248
|
+
const fetchPromise = fetch(req)
|
|
249
|
+
.then(async (res) => {
|
|
250
|
+
await cachePut(req, res.clone());
|
|
251
|
+
return res;
|
|
252
|
+
})
|
|
253
|
+
.catch(() => null);
|
|
209
254
|
|
|
210
255
|
return cached || (await fetchPromise) || new Response("Offline", { status: 503 });
|
|
211
256
|
}
|
|
@@ -216,16 +261,15 @@ function pickStrategy(name) {
|
|
|
216
261
|
return networkFirst;
|
|
217
262
|
}
|
|
218
263
|
|
|
264
|
+
// ---------------------------
|
|
265
|
+
// lifecycle
|
|
266
|
+
// ---------------------------
|
|
219
267
|
self.addEventListener("install", (event) => {
|
|
220
268
|
event.waitUntil((async () => {
|
|
221
269
|
const cache = await caches.open(CACHE_NAME);
|
|
222
270
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// \u2705 Do not let one 404 kill the whole SW install
|
|
226
|
-
await Promise.allSettled(
|
|
227
|
-
urls.map((u) => cache.add(u))
|
|
228
|
-
);
|
|
271
|
+
// \u2705 fail-safe install (never redundant from a single bad file)
|
|
272
|
+
await Promise.allSettled(PRECACHE.map((u) => cache.add(u)));
|
|
229
273
|
|
|
230
274
|
await self.skipWaiting();
|
|
231
275
|
})());
|
|
@@ -233,32 +277,130 @@ self.addEventListener("install", (event) => {
|
|
|
233
277
|
|
|
234
278
|
self.addEventListener("activate", (event) => {
|
|
235
279
|
event.waitUntil((async () => {
|
|
280
|
+
await cleanOldCaches();
|
|
236
281
|
await self.clients.claim();
|
|
237
282
|
})());
|
|
238
283
|
});
|
|
239
284
|
|
|
285
|
+
// Allow app to trigger update instantly:
|
|
286
|
+
// navigator.serviceWorker.controller?.postMessage({ type: "SKIP_WAITING" })
|
|
287
|
+
self.addEventListener("message", (event) => {
|
|
288
|
+
if (event?.data?.type === "SKIP_WAITING") {
|
|
289
|
+
self.skipWaiting();
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// ---------------------------
|
|
294
|
+
// fetch routing
|
|
295
|
+
// ---------------------------
|
|
240
296
|
self.addEventListener("fetch", (event) => {
|
|
241
297
|
const req = event.request;
|
|
242
298
|
|
|
243
|
-
//
|
|
244
|
-
if (req.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
299
|
+
// Don't touch non-GET requests
|
|
300
|
+
if (req.method && req.method !== "GET") return;
|
|
301
|
+
|
|
302
|
+
const url = new URL(req.url);
|
|
303
|
+
|
|
304
|
+
// Safe default: same-origin only
|
|
305
|
+
if (SAME_ORIGIN_ONLY && !isSameOrigin(url)) return;
|
|
306
|
+
|
|
307
|
+
const accept = req.headers.get("accept") || "";
|
|
308
|
+
const ext = extOf(url.pathname);
|
|
309
|
+
|
|
310
|
+
// 1) HTML / navigations -> offline page fallback
|
|
311
|
+
const isHtml = req.mode === "navigate" || accept.includes("text/html");
|
|
312
|
+
if (isHtml) {
|
|
313
|
+
const fn = pickStrategy(HTML_STRATEGY);
|
|
314
|
+
event.respondWith(fn(req, OFFLINE_PAGE));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 2) API calls (network-first by default)
|
|
319
|
+
if (isApi(url)) {
|
|
320
|
+
event.respondWith(networkFirst(req));
|
|
321
|
+
return;
|
|
253
322
|
}
|
|
323
|
+
|
|
324
|
+
// 3) Images (cache-first default) with OFFLINE_IMAGE fallback
|
|
325
|
+
if (isImageExt(ext)) {
|
|
326
|
+
const fn = pickStrategy(IMAGE_STRATEGY);
|
|
327
|
+
event.respondWith(fn(req, OFFLINE_IMAGE));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 4) Static assets (js/css/fonts/json/etc.)
|
|
332
|
+
if (ASSET_EXTS.has(ext)) {
|
|
333
|
+
const fn = pickStrategy(ASSET_STRATEGY);
|
|
334
|
+
if (fn === staleWhileRevalidate) {
|
|
335
|
+
event.respondWith(staleWhileRevalidate(req));
|
|
336
|
+
} else {
|
|
337
|
+
event.respondWith(fn(req));
|
|
338
|
+
}
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Everything else: do nothing (network as normal)
|
|
254
343
|
});
|
|
255
344
|
`;
|
|
256
345
|
}
|
|
257
346
|
|
|
347
|
+
// src/core/precacheScan.ts
|
|
348
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
349
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
350
|
+
function normalizeExts(exts) {
|
|
351
|
+
return new Set(exts.map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
352
|
+
}
|
|
353
|
+
function toUrlPath(outDirAbs, fileAbs) {
|
|
354
|
+
const rel = import_node_path2.default.relative(outDirAbs, fileAbs).split(import_node_path2.default.sep).join("/");
|
|
355
|
+
return "/" + rel.replace(/^\/+/, "");
|
|
356
|
+
}
|
|
357
|
+
function startsWithAny(urlPath, prefixes) {
|
|
358
|
+
return prefixes.some((p) => urlPath.startsWith(p));
|
|
359
|
+
}
|
|
360
|
+
function scanPrecacheFiles(opts) {
|
|
361
|
+
const exts = normalizeExts(opts.extensions);
|
|
362
|
+
const results = [];
|
|
363
|
+
const walk = (dirAbs) => {
|
|
364
|
+
if (results.length >= opts.maxFiles) return;
|
|
365
|
+
const entries = import_node_fs2.default.readdirSync(dirAbs, { withFileTypes: true });
|
|
366
|
+
for (const ent of entries) {
|
|
367
|
+
if (results.length >= opts.maxFiles) break;
|
|
368
|
+
const abs = import_node_path2.default.join(dirAbs, ent.name);
|
|
369
|
+
if (ent.isDirectory()) {
|
|
370
|
+
walk(abs);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (!ent.isFile()) continue;
|
|
374
|
+
try {
|
|
375
|
+
const st = import_node_fs2.default.statSync(abs);
|
|
376
|
+
if (st.size > opts.maxFileSizeKB * 1024) continue;
|
|
377
|
+
} catch {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
const urlPath = toUrlPath(opts.outDir, abs);
|
|
381
|
+
if (startsWithAny(urlPath, opts.ignorePrefixes)) continue;
|
|
382
|
+
const dot = urlPath.lastIndexOf(".");
|
|
383
|
+
const ext = dot === -1 ? "" : urlPath.slice(dot + 1).toLowerCase();
|
|
384
|
+
if (!exts.has(ext)) continue;
|
|
385
|
+
results.push(urlPath);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
walk(opts.outDir);
|
|
389
|
+
return results;
|
|
390
|
+
}
|
|
391
|
+
|
|
258
392
|
// src/cli.ts
|
|
259
393
|
function normalizePublicPath(p) {
|
|
260
|
-
if (!p
|
|
261
|
-
return p;
|
|
394
|
+
if (!p) return "/";
|
|
395
|
+
return p.startsWith("/") ? p : "/" + p;
|
|
396
|
+
}
|
|
397
|
+
function normalizeIgnorePrefix(p) {
|
|
398
|
+
return normalizePublicPath(p.trim());
|
|
399
|
+
}
|
|
400
|
+
function safeNumber(v, fallback) {
|
|
401
|
+
if (!v) return fallback;
|
|
402
|
+
const n = Number(v);
|
|
403
|
+
return Number.isFinite(n) ? n : fallback;
|
|
262
404
|
}
|
|
263
405
|
function withDefaults(o) {
|
|
264
406
|
return {
|
|
@@ -272,28 +414,69 @@ function withDefaults(o) {
|
|
|
272
414
|
assetStrategy: o.assetStrategy ?? "staleWhileRevalidate",
|
|
273
415
|
imageStrategy: o.imageStrategy ?? "cacheFirst",
|
|
274
416
|
assetExtensions: o.assetExtensions ?? [],
|
|
275
|
-
apiPrefixes: o.apiPrefixes ?? []
|
|
417
|
+
apiPrefixes: o.apiPrefixes ?? [],
|
|
418
|
+
autoPrecache: o.autoPrecache ?? true,
|
|
419
|
+
precacheExtensions: o.precacheExtensions ?? [
|
|
420
|
+
"ico",
|
|
421
|
+
"png",
|
|
422
|
+
"jpg",
|
|
423
|
+
"jpeg",
|
|
424
|
+
"webp",
|
|
425
|
+
"svg",
|
|
426
|
+
"gif",
|
|
427
|
+
"css",
|
|
428
|
+
"js",
|
|
429
|
+
"woff2",
|
|
430
|
+
"woff",
|
|
431
|
+
"ttf",
|
|
432
|
+
"eot",
|
|
433
|
+
"json",
|
|
434
|
+
"webmanifest",
|
|
435
|
+
"txt",
|
|
436
|
+
"xml"
|
|
437
|
+
],
|
|
438
|
+
precacheIgnore: o.precacheIgnore ?? [
|
|
439
|
+
"/sw.js",
|
|
440
|
+
"/sw.js.map",
|
|
441
|
+
"/_next/",
|
|
442
|
+
"/static/"
|
|
443
|
+
],
|
|
444
|
+
precacheMaxFiles: o.precacheMaxFiles ?? 200,
|
|
445
|
+
precacheMaxFileSizeKB: o.precacheMaxFileSizeKB ?? 512
|
|
276
446
|
};
|
|
277
447
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
448
|
+
function parseCommand(argv) {
|
|
449
|
+
const first = argv[0];
|
|
450
|
+
if (first === "init" || first === "build") {
|
|
451
|
+
return { cmd: first, rest: argv.slice(1) };
|
|
452
|
+
}
|
|
453
|
+
return { cmd: "init", rest: argv };
|
|
454
|
+
}
|
|
455
|
+
var rawArgv = process.argv.slice(2);
|
|
456
|
+
var { cmd, rest } = parseCommand(rawArgv);
|
|
457
|
+
var args = parseArgs(rest);
|
|
458
|
+
var outDir = import_node_path3.default.resolve(process.cwd(), args.get("outDir") || "public");
|
|
281
459
|
var options = withDefaults({
|
|
282
460
|
outDir,
|
|
283
461
|
swFileName: args.get("swFileName") || "sw.js",
|
|
284
462
|
offlinePage: args.get("offlinePage") || "/offline.html",
|
|
285
463
|
offlineImage: args.get("offlineImage") || "/offline.svg",
|
|
286
464
|
cacheName: args.get("cacheName") || "offline-page-kit",
|
|
287
|
-
precache: splitList(args.get("precache")),
|
|
465
|
+
precache: splitList(args.get("precache")).map(normalizePublicPath),
|
|
288
466
|
htmlStrategy: args.get("htmlStrategy") || "networkFirst",
|
|
289
467
|
assetStrategy: args.get("assetStrategy") || "staleWhileRevalidate",
|
|
290
468
|
imageStrategy: args.get("imageStrategy") || "cacheFirst",
|
|
291
469
|
assetExtensions: splitList(args.get("assetExtensions")),
|
|
292
|
-
apiPrefixes: splitList(args.get("apiPrefixes"))
|
|
470
|
+
apiPrefixes: splitList(args.get("apiPrefixes")),
|
|
471
|
+
autoPrecache: args.get("autoPrecache") !== "false",
|
|
472
|
+
precacheExtensions: splitList(args.get("precacheExtensions")),
|
|
473
|
+
precacheIgnore: splitList(args.get("precacheIgnore")).map(normalizeIgnorePrefix),
|
|
474
|
+
precacheMaxFiles: safeNumber(args.get("precacheMaxFiles"), 200),
|
|
475
|
+
precacheMaxFileSizeKB: safeNumber(args.get("precacheMaxFileSizeKB"), 512)
|
|
293
476
|
});
|
|
294
|
-
var swOut =
|
|
295
|
-
var offlineHtmlOut =
|
|
296
|
-
var offlineSvgOut =
|
|
477
|
+
var swOut = import_node_path3.default.join(outDir, options.swFileName);
|
|
478
|
+
var offlineHtmlOut = import_node_path3.default.join(outDir, options.offlinePage.replace(/^\//, ""));
|
|
479
|
+
var offlineSvgOut = import_node_path3.default.join(outDir, options.offlineImage.replace(/^\//, ""));
|
|
297
480
|
if (cmd === "init" || cmd === "build") {
|
|
298
481
|
if (cmd === "build" || !exists(offlineHtmlOut)) {
|
|
299
482
|
writeFileSafe(offlineHtmlOut, offlineHtmlTemplate());
|
|
@@ -301,12 +484,46 @@ if (cmd === "init" || cmd === "build") {
|
|
|
301
484
|
if (cmd === "build" || !exists(offlineSvgOut)) {
|
|
302
485
|
writeFileSafe(offlineSvgOut, offlineSvgTemplate());
|
|
303
486
|
}
|
|
304
|
-
|
|
487
|
+
let scanned = [];
|
|
488
|
+
if (options.autoPrecache) {
|
|
489
|
+
const swUrlPath = normalizePublicPath(options.swFileName);
|
|
490
|
+
const swMapUrlPath = swUrlPath + ".map";
|
|
491
|
+
const ignorePrefixes = Array.from(/* @__PURE__ */ new Set([
|
|
492
|
+
swUrlPath,
|
|
493
|
+
swMapUrlPath,
|
|
494
|
+
...options.precacheIgnore.map(normalizeIgnorePrefix)
|
|
495
|
+
]));
|
|
496
|
+
scanned = scanPrecacheFiles({
|
|
497
|
+
outDir,
|
|
498
|
+
// absolute /public
|
|
499
|
+
extensions: options.precacheExtensions.length ? options.precacheExtensions : ["ico", "png", "css", "js", "woff2", "json", "webmanifest", "svg"],
|
|
500
|
+
ignorePrefixes,
|
|
501
|
+
maxFiles: options.precacheMaxFiles,
|
|
502
|
+
maxFileSizeKB: options.precacheMaxFileSizeKB
|
|
503
|
+
}).map(normalizePublicPath);
|
|
504
|
+
}
|
|
505
|
+
const allPrecache = Array.from(/* @__PURE__ */ new Set([
|
|
506
|
+
normalizePublicPath(options.offlinePage),
|
|
507
|
+
normalizePublicPath(options.offlineImage),
|
|
508
|
+
...(options.precache || []).map(normalizePublicPath),
|
|
509
|
+
...scanned.map(normalizePublicPath)
|
|
510
|
+
]));
|
|
511
|
+
const sw = buildServiceWorkerJS({
|
|
512
|
+
...options,
|
|
513
|
+
precache: allPrecache
|
|
514
|
+
});
|
|
305
515
|
writeFileSafe(swOut, sw);
|
|
516
|
+
const manualCount = (options.precache || []).length;
|
|
517
|
+
const scannedCount = scanned.length;
|
|
306
518
|
console.log(`[offline-page-kit] Generated:
|
|
307
519
|
- ${swOut}
|
|
308
520
|
- ${offlineHtmlOut}
|
|
309
521
|
- ${offlineSvgOut}
|
|
522
|
+
|
|
523
|
+
Precache:
|
|
524
|
+
- manual: ${manualCount}
|
|
525
|
+
- scanned: ${scannedCount}
|
|
526
|
+
- total: ${allPrecache.length}
|
|
310
527
|
`);
|
|
311
528
|
} else {
|
|
312
529
|
console.log(`[offline-page-kit] Unknown command: ${cmd}
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/core/utils.ts","../src/core/offlineHtml.ts","../src/core/offlineSvg.ts","../src/core/swTemplate.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport path from \"node:path\";\r\nimport { parseArgs, splitList, writeFileSafe, exists } from \"./core/utils\";\r\nimport { offlineHtmlTemplate } from \"./core/offlineHtml\";\r\nimport { offlineSvgTemplate } from \"./core/offlineSvg\";\r\nimport { buildServiceWorkerJS } from \"./core/swTemplate\";\r\nimport type { OfflineKitBuildOptions } from \"./types\";\r\n\r\nfunction normalizePublicPath(p: string) {\r\n if (!p.startsWith(\"/\")) return \"/\" + p;\r\n return p;\r\n}\r\n\r\nfunction withDefaults(o: OfflineKitBuildOptions): Required<OfflineKitBuildOptions> {\r\n return {\r\n outDir: o.outDir ?? \"public\",\r\n swFileName: o.swFileName ?? \"sw.js\",\r\n offlinePage: normalizePublicPath(o.offlinePage ?? \"/offline.html\"),\r\n offlineImage: normalizePublicPath(o.offlineImage ?? \"/offline.svg\"),\r\n cacheName: o.cacheName ?? \"offline-page-kit\",\r\n precache: o.precache ?? [],\r\n htmlStrategy: o.htmlStrategy ?? \"networkFirst\",\r\n assetStrategy: o.assetStrategy ?? \"staleWhileRevalidate\",\r\n imageStrategy: o.imageStrategy ?? \"cacheFirst\",\r\n assetExtensions: o.assetExtensions ?? [],\r\n apiPrefixes: o.apiPrefixes ?? [],\r\n };\r\n}\r\n\r\nconst args = parseArgs(process.argv.slice(2));\r\nconst cmd = process.argv.slice(2).find(a => !a.startsWith(\"--\")) || \"init\";\r\n\r\nconst outDir = path.resolve(process.cwd(), args.get(\"outDir\") || \"public\");\r\n\r\nconst options = withDefaults({\r\n outDir,\r\n swFileName: args.get(\"swFileName\") || \"sw.js\",\r\n offlinePage: args.get(\"offlinePage\") || \"/offline.html\",\r\n offlineImage: args.get(\"offlineImage\") || \"/offline.svg\",\r\n cacheName: args.get(\"cacheName\") || \"offline-page-kit\",\r\n precache: splitList(args.get(\"precache\")),\r\n htmlStrategy: (args.get(\"htmlStrategy\") as any) || \"networkFirst\",\r\n assetStrategy: (args.get(\"assetStrategy\") as any) || \"staleWhileRevalidate\",\r\n imageStrategy: (args.get(\"imageStrategy\") as any) || \"cacheFirst\",\r\n assetExtensions: splitList(args.get(\"assetExtensions\")),\r\n apiPrefixes: splitList(args.get(\"apiPrefixes\")),\r\n} as OfflineKitBuildOptions);\r\n\r\nconst swOut = path.join(outDir, options.swFileName);\r\nconst offlineHtmlOut = path.join(outDir, options.offlinePage.replace(/^\\//, \"\"));\r\nconst offlineSvgOut = path.join(outDir, options.offlineImage.replace(/^\\//, \"\"));\r\n\r\nif (cmd === \"init\" || cmd === \"build\") {\r\n // generate offline page if missing OR force (when build)\r\n if (cmd === \"build\" || !exists(offlineHtmlOut)) {\r\n writeFileSafe(offlineHtmlOut, offlineHtmlTemplate());\r\n }\r\n if (cmd === \"build\" || !exists(offlineSvgOut)) {\r\n writeFileSafe(offlineSvgOut, offlineSvgTemplate());\r\n }\r\n\r\n const sw = buildServiceWorkerJS(options);\r\n writeFileSafe(swOut, sw);\r\n\r\n console.log(`[offline-page-kit] Generated:\r\n- ${swOut}\r\n- ${offlineHtmlOut}\r\n- ${offlineSvgOut}\r\n`);\r\n} else {\r\n console.log(`[offline-page-kit] Unknown command: ${cmd}\r\nUse:\r\n offline-page-kit init --outDir public\r\n offline-page-kit build --outDir public\r\n`);\r\n}","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\n\r\nexport function ensureDir(p: string) {\r\n fs.mkdirSync(p, { recursive: true });\r\n}\r\n\r\nexport function writeFileSafe(filePath: string, content: string) {\r\n ensureDir(path.dirname(filePath));\r\n fs.writeFileSync(filePath, content, \"utf8\");\r\n}\r\n\r\nexport function exists(filePath: string) {\r\n try { fs.accessSync(filePath); return true; } catch { return false; }\r\n}\r\n\r\nexport function parseArgs(argv: string[]) {\r\n const m = new Map<string, string>();\r\n for (let i = 0; i < argv.length; i++) {\r\n const a = argv[i];\r\n if (!a.startsWith(\"--\")) continue;\r\n const key = a.slice(2);\r\n const value = argv[i + 1] && !argv[i + 1].startsWith(\"--\") ? argv[++i] : \"true\";\r\n m.set(key, value);\r\n }\r\n return m;\r\n}\r\n\r\nexport function splitList(v: string | undefined) {\r\n return (v || \"\")\r\n .split(\",\")\r\n .map(s => s.trim())\r\n .filter(Boolean);\r\n}","export function offlineHtmlTemplate(title = \"You're Offline\") {\r\n return `<!doctype html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\"/>\r\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>\r\n <title>${title}</title>\r\n <style>\r\n body{margin:0;height:100vh;display:grid;place-items:center;font-family:system-ui,-apple-system,Segoe UI,Roboto;background:#0b0f19;color:#fff}\r\n .box{max-width:560px;padding:28px 26px;border-radius:18px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.04)}\r\n h1{margin:0 0 10px;font-size:28px}\r\n p{margin:0 0 18px;opacity:.85;line-height:1.5}\r\n .row{display:flex;gap:10px;flex-wrap:wrap}\r\n button,a{appearance:none;border:0;border-radius:12px;padding:10px 14px;cursor:pointer;text-decoration:none}\r\n button{background:#fff;color:#111}\r\n a{background:rgba(255,255,255,.1);color:#fff}\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"box\">\r\n <h1>⚡ You’re Offline</h1>\r\n <p>No internet connection detected. You can retry, or go back to the homepage (if cached).</p>\r\n <div class=\"row\">\r\n <button onclick=\"location.reload()\">Retry</button>\r\n <a href=\"/\">Go Home</a>\r\n </div>\r\n </div>\r\n</body>\r\n</html>`;\r\n}","export function offlineSvgTemplate() {\r\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"630\" viewBox=\"0 0 1200 630\">\r\n <rect width=\"1200\" height=\"630\" fill=\"#0b0f19\"/>\r\n <text x=\"80\" y=\"220\" fill=\"#ffffff\" font-size=\"64\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto\">You’re Offline</text>\r\n <text x=\"80\" y=\"300\" fill=\"#cbd5e1\" font-size=\"28\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto\">Please check your connection and try again.</text>\r\n <circle cx=\"1040\" cy=\"220\" r=\"90\" fill=\"rgba(255,255,255,0.08)\"/>\r\n <path d=\"M980 220c40-40 120-40 160 0\" stroke=\"#fff\" stroke-width=\"10\" fill=\"none\" opacity=\"0.6\"/>\r\n <path d=\"M1010 250c25-25 75-25 100 0\" stroke=\"#fff\" stroke-width=\"10\" fill=\"none\" opacity=\"0.6\"/>\r\n <circle cx=\"1060\" cy=\"290\" r=\"10\" fill=\"#fff\" opacity=\"0.7\"/>\r\n</svg>`;\r\n}","import type { OfflineKitBuildOptions, Strategy } from \"../types\";\r\n\r\nconst DEFAULT_EXTS = [\r\n \"js\", \"css\", \"map\", \"ico\", \"png\", \"jpg\", \"jpeg\", \"webp\", \"svg\", \"gif\",\r\n \"woff2\", \"woff\", \"ttf\", \"eot\", \"json\", \"txt\", \"xml\", \"webmanifest\"\r\n];\r\n\r\nfunction jsString(v: unknown) {\r\n return JSON.stringify(v);\r\n}\r\n\r\nexport function buildServiceWorkerJS(options: Required<OfflineKitBuildOptions>) {\r\n const {\r\n cacheName,\r\n offlinePage,\r\n offlineImage,\r\n precache,\r\n htmlStrategy,\r\n assetStrategy,\r\n imageStrategy,\r\n assetExtensions,\r\n apiPrefixes,\r\n } = options;\r\n\r\n return `/* offline-page-kit service worker */\r\nconst CACHE_NAME = ${jsString(cacheName)};\r\nconst OFFLINE_PAGE = ${jsString(offlinePage)};\r\nconst OFFLINE_IMAGE = ${jsString(offlineImage)};\r\nconst PRECACHE = ${jsString([offlinePage, offlineImage, ...precache].filter(Boolean))};\r\n\r\nconst HTML_STRATEGY = ${jsString(htmlStrategy)};\r\nconst ASSET_STRATEGY = ${jsString(assetStrategy)};\r\nconst IMAGE_STRATEGY = ${jsString(imageStrategy)};\r\n\r\nconst ASSET_EXTS = new Set(${jsString(assetExtensions.length ? assetExtensions : DEFAULT_EXTS)});\r\nconst API_PREFIXES = ${jsString(apiPrefixes)};\r\n\r\n// helpers\r\nconst isApi = (url) => API_PREFIXES.some(p => url.href.startsWith(p) || url.pathname.startsWith(p));\r\nconst extOf = (pathname) => {\r\n const i = pathname.lastIndexOf(\".\");\r\n return i === -1 ? \"\" : pathname.slice(i+1).toLowerCase();\r\n};\r\n\r\nasync function cacheGet(req) {\r\n const cache = await caches.open(CACHE_NAME);\r\n return cache.match(req);\r\n}\r\nasync function cachePut(req, res) {\r\n const cache = await caches.open(CACHE_NAME);\r\n await cache.put(req, res);\r\n}\r\nasync function cacheDeleteOld() {\r\n const keys = await caches.keys();\r\n await Promise.all(keys.map(k => (k === CACHE_NAME ? null : caches.delete(k))));\r\n}\r\n\r\n// strategies\r\nasync function networkFirst(req, fallbackUrl) {\r\n try {\r\n const res = await fetch(req);\r\n if (res && res.ok) cachePut(req, res.clone());\r\n return res;\r\n } catch {\r\n const cached = await cacheGet(req);\r\n if (cached) return cached;\r\n if (fallbackUrl) return cacheGet(fallbackUrl);\r\n return new Response(\"Offline\", { status: 503 });\r\n }\r\n}\r\n\r\nasync function cacheFirst(req, fallbackUrl) {\r\n const cached = await cacheGet(req);\r\n if (cached) return cached;\r\n try {\r\n const res = await fetch(req);\r\n if (res && res.ok) cachePut(req, res.clone());\r\n return res;\r\n } catch {\r\n if (fallbackUrl) return cacheGet(fallbackUrl);\r\n return new Response(\"Offline\", { status: 503 });\r\n }\r\n}\r\n\r\nasync function staleWhileRevalidate(req) {\r\n const cached = await cacheGet(req);\r\n const fetchPromise = fetch(req).then(res => {\r\n if (res && res.ok) cachePut(req, res.clone());\r\n return res;\r\n }).catch(() => null);\r\n\r\n return cached || (await fetchPromise) || new Response(\"Offline\", { status: 503 });\r\n}\r\n\r\nfunction pickStrategy(name) {\r\n if (name === \"cacheFirst\") return cacheFirst;\r\n if (name === \"staleWhileRevalidate\") return staleWhileRevalidate;\r\n return networkFirst;\r\n}\r\n\r\nself.addEventListener(\"install\", (event) => {\r\n event.waitUntil((async () => {\r\n const cache = await caches.open(CACHE_NAME);\r\n\r\n const urls = [OFFLINE_PAGE, OFFLINE_IMAGE];\r\n\r\n // ✅ Do not let one 404 kill the whole SW install\r\n await Promise.allSettled(\r\n urls.map((u) => cache.add(u))\r\n );\r\n\r\n await self.skipWaiting();\r\n })());\r\n});\r\n\r\nself.addEventListener(\"activate\", (event) => {\r\n event.waitUntil((async () => {\r\n await self.clients.claim();\r\n })());\r\n});\r\n\r\nself.addEventListener(\"fetch\", (event) => {\r\n const req = event.request;\r\n\r\n // ✅ Offline fallback for page navigations\r\n if (req.mode === \"navigate\") {\r\n event.respondWith((async () => {\r\n try {\r\n return await fetch(req);\r\n } catch {\r\n const cache = await caches.open(CACHE_NAME);\r\n return (await cache.match(OFFLINE_PAGE)) || new Response(\"Offline\", { status: 503 });\r\n }\r\n })());\r\n }\r\n});\r\n`;\r\n}"],"mappings":"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","names":["import_node_path","fs","path","options","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/core/utils.ts","../src/core/offlineHtml.ts","../src/core/offlineSvg.ts","../src/core/swTemplate.ts","../src/core/precacheScan.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport path from \"node:path\";\r\nimport { parseArgs, splitList, writeFileSafe, exists } from \"./core/utils\";\r\nimport { offlineHtmlTemplate } from \"./core/offlineHtml\";\r\nimport { offlineSvgTemplate } from \"./core/offlineSvg\";\r\nimport { buildServiceWorkerJS } from \"./core/swTemplate\";\r\nimport type { OfflineKitBuildOptions } from \"./types\";\r\nimport { scanPrecacheFiles } from \"./core/precacheScan\";\r\n\r\ntype Cmd = \"init\" | \"build\";\r\n\r\nfunction normalizePublicPath(p: string) {\r\n if (!p) return \"/\";\r\n return p.startsWith(\"/\") ? p : \"/\" + p;\r\n}\r\n\r\nfunction normalizeIgnorePrefix(p: string) {\r\n // ignore prefixes are URL-style and should start with \"/\"\r\n return normalizePublicPath(p.trim());\r\n}\r\n\r\nfunction safeNumber(v: string | undefined, fallback: number) {\r\n if (!v) return fallback;\r\n const n = Number(v);\r\n return Number.isFinite(n) ? n : fallback;\r\n}\r\n\r\nfunction withDefaults(o: OfflineKitBuildOptions): Required<OfflineKitBuildOptions> {\r\n return {\r\n outDir: o.outDir ?? \"public\",\r\n swFileName: o.swFileName ?? \"sw.js\",\r\n\r\n offlinePage: normalizePublicPath(o.offlinePage ?? \"/offline.html\"),\r\n offlineImage: normalizePublicPath(o.offlineImage ?? \"/offline.svg\"),\r\n\r\n cacheName: o.cacheName ?? \"offline-page-kit\",\r\n precache: o.precache ?? [],\r\n\r\n htmlStrategy: o.htmlStrategy ?? \"networkFirst\",\r\n assetStrategy: o.assetStrategy ?? \"staleWhileRevalidate\",\r\n imageStrategy: o.imageStrategy ?? \"cacheFirst\",\r\n\r\n assetExtensions: o.assetExtensions ?? [],\r\n apiPrefixes: o.apiPrefixes ?? [],\r\n\r\n autoPrecache: o.autoPrecache ?? true,\r\n precacheExtensions: o.precacheExtensions ?? [\r\n \"ico\", \"png\", \"jpg\", \"jpeg\", \"webp\", \"svg\", \"gif\",\r\n \"css\", \"js\", \"woff2\", \"woff\", \"ttf\", \"eot\",\r\n \"json\", \"webmanifest\", \"txt\", \"xml\"\r\n ],\r\n precacheIgnore: o.precacheIgnore ?? [\r\n \"/sw.js\",\r\n \"/sw.js.map\",\r\n \"/_next/\",\r\n \"/static/\"\r\n ],\r\n precacheMaxFiles: o.precacheMaxFiles ?? 200,\r\n precacheMaxFileSizeKB: o.precacheMaxFileSizeKB ?? 512,\r\n };\r\n}\r\n\r\n/**\r\n * Command parsing\r\n * Supports:\r\n * offline-page-kit init --outDir public\r\n * offline-page-kit --outDir public (defaults to init)\r\n */\r\nfunction parseCommand(argv: string[]): { cmd: Cmd; rest: string[] } {\r\n const first = argv[0];\r\n if (first === \"init\" || first === \"build\") {\r\n return { cmd: first, rest: argv.slice(1) };\r\n }\r\n return { cmd: \"init\", rest: argv };\r\n}\r\n\r\nconst rawArgv = process.argv.slice(2);\r\nconst { cmd, rest } = parseCommand(rawArgv);\r\nconst args = parseArgs(rest);\r\n\r\nconst outDir = path.resolve(process.cwd(), args.get(\"outDir\") || \"public\");\r\n\r\nconst options = withDefaults({\r\n outDir,\r\n swFileName: args.get(\"swFileName\") || \"sw.js\",\r\n offlinePage: args.get(\"offlinePage\") || \"/offline.html\",\r\n offlineImage: args.get(\"offlineImage\") || \"/offline.svg\",\r\n cacheName: args.get(\"cacheName\") || \"offline-page-kit\",\r\n precache: splitList(args.get(\"precache\")).map(normalizePublicPath),\r\n\r\n htmlStrategy: (args.get(\"htmlStrategy\") as any) || \"networkFirst\",\r\n assetStrategy: (args.get(\"assetStrategy\") as any) || \"staleWhileRevalidate\",\r\n imageStrategy: (args.get(\"imageStrategy\") as any) || \"cacheFirst\",\r\n\r\n assetExtensions: splitList(args.get(\"assetExtensions\")),\r\n apiPrefixes: splitList(args.get(\"apiPrefixes\")),\r\n\r\n autoPrecache: args.get(\"autoPrecache\") !== \"false\",\r\n precacheExtensions: splitList(args.get(\"precacheExtensions\")),\r\n precacheIgnore: splitList(args.get(\"precacheIgnore\")).map(normalizeIgnorePrefix),\r\n\r\n precacheMaxFiles: safeNumber(args.get(\"precacheMaxFiles\"), 200),\r\n precacheMaxFileSizeKB: safeNumber(args.get(\"precacheMaxFileSizeKB\"), 512),\r\n} as OfflineKitBuildOptions);\r\n\r\nconst swOut = path.join(outDir, options.swFileName);\r\nconst offlineHtmlOut = path.join(outDir, options.offlinePage.replace(/^\\//, \"\"));\r\nconst offlineSvgOut = path.join(outDir, options.offlineImage.replace(/^\\//, \"\"));\r\n\r\nif (cmd === \"init\" || cmd === \"build\") {\r\n // 1) Generate offline page + offline svg first (so scanner can find them)\r\n if (cmd === \"build\" || !exists(offlineHtmlOut)) {\r\n writeFileSafe(offlineHtmlOut, offlineHtmlTemplate());\r\n }\r\n if (cmd === \"build\" || !exists(offlineSvgOut)) {\r\n writeFileSafe(offlineSvgOut, offlineSvgTemplate());\r\n }\r\n\r\n // 2) Auto-scan (after offline files exist)\r\n let scanned: string[] = [];\r\n if (options.autoPrecache) {\r\n // ✅ always ignore the actual sw filename (even if user customizes it)\r\n const swUrlPath = normalizePublicPath(options.swFileName);\r\n const swMapUrlPath = swUrlPath + \".map\";\r\n\r\n const ignorePrefixes = Array.from(new Set([\r\n swUrlPath,\r\n swMapUrlPath,\r\n ...options.precacheIgnore.map(normalizeIgnorePrefix),\r\n ]));\r\n\r\n scanned = scanPrecacheFiles({\r\n outDir, // absolute /public\r\n extensions: options.precacheExtensions.length\r\n ? options.precacheExtensions\r\n : [\"ico\", \"png\", \"css\", \"js\", \"woff2\", \"json\", \"webmanifest\", \"svg\"],\r\n ignorePrefixes,\r\n maxFiles: options.precacheMaxFiles,\r\n maxFileSizeKB: options.precacheMaxFileSizeKB,\r\n }).map(normalizePublicPath);\r\n }\r\n\r\n // 3) Merge + de-duplicate (and normalize)\r\n const allPrecache = Array.from(new Set([\r\n normalizePublicPath(options.offlinePage),\r\n normalizePublicPath(options.offlineImage),\r\n ...(options.precache || []).map(normalizePublicPath),\r\n ...scanned.map(normalizePublicPath),\r\n ]));\r\n\r\n // 4) Build SW using merged list\r\n const sw = buildServiceWorkerJS({\r\n ...options,\r\n precache: allPrecache,\r\n });\r\n\r\n writeFileSafe(swOut, sw);\r\n\r\n const manualCount = (options.precache || []).length;\r\n const scannedCount = scanned.length;\r\n\r\n console.log(`[offline-page-kit] Generated:\r\n- ${swOut}\r\n- ${offlineHtmlOut}\r\n- ${offlineSvgOut}\r\n\r\nPrecache:\r\n- manual: ${manualCount}\r\n- scanned: ${scannedCount}\r\n- total: ${allPrecache.length}\r\n`);\r\n} else {\r\n console.log(`[offline-page-kit] Unknown command: ${cmd}\r\nUse:\r\n offline-page-kit init --outDir public\r\n offline-page-kit build --outDir public\r\n`);\r\n}","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\n\r\nexport function ensureDir(p: string) {\r\n fs.mkdirSync(p, { recursive: true });\r\n}\r\n\r\nexport function writeFileSafe(filePath: string, content: string) {\r\n ensureDir(path.dirname(filePath));\r\n fs.writeFileSync(filePath, content, \"utf8\");\r\n}\r\n\r\nexport function exists(filePath: string) {\r\n try { fs.accessSync(filePath); return true; } catch { return false; }\r\n}\r\n\r\nexport function parseArgs(argv: string[]) {\r\n const m = new Map<string, string>();\r\n for (let i = 0; i < argv.length; i++) {\r\n const a = argv[i];\r\n if (!a.startsWith(\"--\")) continue;\r\n const key = a.slice(2);\r\n const value = argv[i + 1] && !argv[i + 1].startsWith(\"--\") ? argv[++i] : \"true\";\r\n m.set(key, value);\r\n }\r\n return m;\r\n}\r\n\r\nexport function splitList(v: string | undefined) {\r\n return (v || \"\")\r\n .split(\",\")\r\n .map(s => s.trim())\r\n .filter(Boolean);\r\n}","export function offlineHtmlTemplate(title = \"You're Offline\") {\r\n return `<!doctype html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\"/>\r\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>\r\n <title>${title}</title>\r\n <style>\r\n body{margin:0;height:100vh;display:grid;place-items:center;font-family:system-ui,-apple-system,Segoe UI,Roboto;background:#0b0f19;color:#fff}\r\n .box{max-width:560px;padding:28px 26px;border-radius:18px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.04)}\r\n h1{margin:0 0 10px;font-size:28px}\r\n p{margin:0 0 18px;opacity:.85;line-height:1.5}\r\n .row{display:flex;gap:10px;flex-wrap:wrap}\r\n button,a{appearance:none;border:0;border-radius:12px;padding:10px 14px;cursor:pointer;text-decoration:none}\r\n button{background:#fff;color:#111}\r\n a{background:rgba(255,255,255,.1);color:#fff}\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"box\">\r\n <h1>⚡ You’re Offline</h1>\r\n <p>No internet connection detected. You can retry, or go back to the homepage (if cached).</p>\r\n <div class=\"row\">\r\n <button onclick=\"location.reload()\">Retry</button>\r\n <a href=\"/\">Go Home</a>\r\n </div>\r\n </div>\r\n</body>\r\n</html>`;\r\n}","export function offlineSvgTemplate() {\r\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"630\" viewBox=\"0 0 1200 630\">\r\n <rect width=\"1200\" height=\"630\" fill=\"#0b0f19\"/>\r\n <text x=\"80\" y=\"220\" fill=\"#ffffff\" font-size=\"64\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto\">You’re Offline</text>\r\n <text x=\"80\" y=\"300\" fill=\"#cbd5e1\" font-size=\"28\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto\">Please check your connection and try again.</text>\r\n <circle cx=\"1040\" cy=\"220\" r=\"90\" fill=\"rgba(255,255,255,0.08)\"/>\r\n <path d=\"M980 220c40-40 120-40 160 0\" stroke=\"#fff\" stroke-width=\"10\" fill=\"none\" opacity=\"0.6\"/>\r\n <path d=\"M1010 250c25-25 75-25 100 0\" stroke=\"#fff\" stroke-width=\"10\" fill=\"none\" opacity=\"0.6\"/>\r\n <circle cx=\"1060\" cy=\"290\" r=\"10\" fill=\"#fff\" opacity=\"0.7\"/>\r\n</svg>`;\r\n}","import type { OfflineKitBuildOptions } from \"../types\";\r\n\r\nconst DEFAULT_EXTS = [\r\n \"js\", \"css\", \"map\", \"ico\", \"png\", \"jpg\", \"jpeg\", \"webp\", \"svg\", \"gif\",\r\n \"woff2\", \"woff\", \"ttf\", \"eot\", \"json\", \"txt\", \"xml\", \"webmanifest\"\r\n];\r\n\r\nfunction jsString(v: unknown) {\r\n return JSON.stringify(v);\r\n}\r\n\r\nexport function buildServiceWorkerJS(options: Required<OfflineKitBuildOptions>) {\r\n const {\r\n cacheName,\r\n offlinePage,\r\n offlineImage,\r\n precache,\r\n htmlStrategy,\r\n assetStrategy,\r\n imageStrategy,\r\n assetExtensions,\r\n apiPrefixes,\r\n } = options;\r\n\r\n // precache already normalized in CLI; still keep it safe\r\n const precacheList = Array.from(\r\n new Set([offlinePage, offlineImage, ...(precache ?? [])].filter(Boolean))\r\n );\r\n\r\n return `/* offline-page-kit service worker */\r\nconst CACHE_NAME = ${jsString(cacheName)};\r\nconst OFFLINE_PAGE = ${jsString(offlinePage)};\r\nconst OFFLINE_IMAGE = ${jsString(offlineImage)};\r\nconst PRECACHE = ${jsString(precacheList)};\r\n\r\nconst HTML_STRATEGY = ${jsString(htmlStrategy)};\r\nconst ASSET_STRATEGY = ${jsString(assetStrategy)};\r\nconst IMAGE_STRATEGY = ${jsString(imageStrategy)};\r\n\r\nconst ASSET_EXTS = new Set(${jsString(assetExtensions.length ? assetExtensions : DEFAULT_EXTS)});\r\nconst API_PREFIXES = ${jsString(apiPrefixes || [])};\r\n\r\nconst SAME_ORIGIN_ONLY = true;\r\n\r\n// ---------------------------\r\n// helpers\r\n// ---------------------------\r\nconst isSameOrigin = (url) => url.origin === self.location.origin;\r\n\r\nconst isApi = (url) => {\r\n if (!API_PREFIXES || API_PREFIXES.length === 0) return false;\r\n return API_PREFIXES.some((p) => {\r\n try {\r\n // allow either absolute prefix or pathname prefix\r\n return url.href.startsWith(p) || url.pathname.startsWith(p);\r\n } catch {\r\n return false;\r\n }\r\n });\r\n};\r\n\r\nconst extOf = (pathname) => {\r\n const i = pathname.lastIndexOf(\".\");\r\n return i === -1 ? \"\" : pathname.slice(i + 1).toLowerCase();\r\n};\r\n\r\nconst isImageExt = (ext) => [\"png\",\"jpg\",\"jpeg\",\"webp\",\"gif\",\"svg\",\"ico\"].includes(ext);\r\n\r\n// Only cache successful, basic responses (avoid caching errors)\r\nconst isCacheableResponse = (res) => res && (res.ok || res.type === \"opaque\");\r\n\r\n// Cache helpers\r\nasync function cacheGet(reqOrUrl) {\r\n const cache = await caches.open(CACHE_NAME);\r\n return cache.match(reqOrUrl);\r\n}\r\n\r\nasync function cachePut(req, res) {\r\n try {\r\n if (!isCacheableResponse(res)) return;\r\n const cache = await caches.open(CACHE_NAME);\r\n await cache.put(req, res);\r\n } catch {\r\n // ignore quota / put errors\r\n }\r\n}\r\n\r\nasync function cleanOldCaches() {\r\n const keys = await caches.keys();\r\n await Promise.all(keys.map((k) => (k === CACHE_NAME ? null : caches.delete(k))));\r\n}\r\n\r\n// ---------------------------\r\n// strategies\r\n// ---------------------------\r\nasync function networkFirst(req, fallbackUrl) {\r\n try {\r\n const res = await fetch(req);\r\n await cachePut(req, res.clone());\r\n return res;\r\n } catch {\r\n const cached = await cacheGet(req);\r\n if (cached) return cached;\r\n if (fallbackUrl) {\r\n const fb = await cacheGet(fallbackUrl);\r\n if (fb) return fb;\r\n }\r\n return new Response(\"Offline\", { status: 503, headers: { \"content-type\": \"text/plain\" } });\r\n }\r\n}\r\n\r\nasync function cacheFirst(req, fallbackUrl) {\r\n const cached = await cacheGet(req);\r\n if (cached) return cached;\r\n\r\n try {\r\n const res = await fetch(req);\r\n await cachePut(req, res.clone());\r\n return res;\r\n } catch {\r\n if (fallbackUrl) {\r\n const fb = await cacheGet(fallbackUrl);\r\n if (fb) return fb;\r\n }\r\n return new Response(\"Offline\", { status: 503, headers: { \"content-type\": \"text/plain\" } });\r\n }\r\n}\r\n\r\nasync function staleWhileRevalidate(req) {\r\n const cached = await cacheGet(req);\r\n\r\n const fetchPromise = fetch(req)\r\n .then(async (res) => {\r\n await cachePut(req, res.clone());\r\n return res;\r\n })\r\n .catch(() => null);\r\n\r\n return cached || (await fetchPromise) || new Response(\"Offline\", { status: 503 });\r\n}\r\n\r\nfunction pickStrategy(name) {\r\n if (name === \"cacheFirst\") return cacheFirst;\r\n if (name === \"staleWhileRevalidate\") return staleWhileRevalidate;\r\n return networkFirst;\r\n}\r\n\r\n// ---------------------------\r\n// lifecycle\r\n// ---------------------------\r\nself.addEventListener(\"install\", (event) => {\r\n event.waitUntil((async () => {\r\n const cache = await caches.open(CACHE_NAME);\r\n\r\n // ✅ fail-safe install (never redundant from a single bad file)\r\n await Promise.allSettled(PRECACHE.map((u) => cache.add(u)));\r\n\r\n await self.skipWaiting();\r\n })());\r\n});\r\n\r\nself.addEventListener(\"activate\", (event) => {\r\n event.waitUntil((async () => {\r\n await cleanOldCaches();\r\n await self.clients.claim();\r\n })());\r\n});\r\n\r\n// Allow app to trigger update instantly:\r\n// navigator.serviceWorker.controller?.postMessage({ type: \"SKIP_WAITING\" })\r\nself.addEventListener(\"message\", (event) => {\r\n if (event?.data?.type === \"SKIP_WAITING\") {\r\n self.skipWaiting();\r\n }\r\n});\r\n\r\n// ---------------------------\r\n// fetch routing\r\n// ---------------------------\r\nself.addEventListener(\"fetch\", (event) => {\r\n const req = event.request;\r\n\r\n // Don't touch non-GET requests\r\n if (req.method && req.method !== \"GET\") return;\r\n\r\n const url = new URL(req.url);\r\n\r\n // Safe default: same-origin only\r\n if (SAME_ORIGIN_ONLY && !isSameOrigin(url)) return;\r\n\r\n const accept = req.headers.get(\"accept\") || \"\";\r\n const ext = extOf(url.pathname);\r\n\r\n // 1) HTML / navigations -> offline page fallback\r\n const isHtml = req.mode === \"navigate\" || accept.includes(\"text/html\");\r\n if (isHtml) {\r\n const fn = pickStrategy(HTML_STRATEGY);\r\n event.respondWith(fn(req, OFFLINE_PAGE));\r\n return;\r\n }\r\n\r\n // 2) API calls (network-first by default)\r\n if (isApi(url)) {\r\n event.respondWith(networkFirst(req));\r\n return;\r\n }\r\n\r\n // 3) Images (cache-first default) with OFFLINE_IMAGE fallback\r\n if (isImageExt(ext)) {\r\n const fn = pickStrategy(IMAGE_STRATEGY);\r\n event.respondWith(fn(req, OFFLINE_IMAGE));\r\n return;\r\n }\r\n\r\n // 4) Static assets (js/css/fonts/json/etc.)\r\n if (ASSET_EXTS.has(ext)) {\r\n const fn = pickStrategy(ASSET_STRATEGY);\r\n if (fn === staleWhileRevalidate) {\r\n event.respondWith(staleWhileRevalidate(req));\r\n } else {\r\n event.respondWith(fn(req));\r\n }\r\n return;\r\n }\r\n\r\n // Everything else: do nothing (network as normal)\r\n});\r\n`;\r\n}","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\n\r\ntype ScanOptions = {\r\n outDir: string; // absolute path to public\r\n extensions: string[];\r\n ignorePrefixes: string[]; // url-style (\"/_next\", \"/sw.js\")\r\n maxFiles: number;\r\n maxFileSizeKB: number;\r\n};\r\n\r\nfunction normalizeExts(exts: string[]) {\r\n return new Set(exts.map(e => e.replace(/^\\./, \"\").toLowerCase()));\r\n}\r\n\r\nfunction toUrlPath(outDirAbs: string, fileAbs: string) {\r\n // convert /abs/public/assets/a.css -> /assets/a.css\r\n const rel = path.relative(outDirAbs, fileAbs).split(path.sep).join(\"/\");\r\n return \"/\" + rel.replace(/^\\/+/, \"\");\r\n}\r\n\r\nfunction startsWithAny(urlPath: string, prefixes: string[]) {\r\n return prefixes.some(p => urlPath.startsWith(p));\r\n}\r\n\r\nexport function scanPrecacheFiles(opts: ScanOptions): string[] {\r\n const exts = normalizeExts(opts.extensions);\r\n const results: string[] = [];\r\n\r\n const walk = (dirAbs: string) => {\r\n if (results.length >= opts.maxFiles) return;\r\n\r\n const entries = fs.readdirSync(dirAbs, { withFileTypes: true });\r\n for (const ent of entries) {\r\n if (results.length >= opts.maxFiles) break;\r\n\r\n const abs = path.join(dirAbs, ent.name);\r\n\r\n if (ent.isDirectory()) {\r\n walk(abs);\r\n continue;\r\n }\r\n\r\n if (!ent.isFile()) continue;\r\n\r\n // file size limit\r\n try {\r\n const st = fs.statSync(abs);\r\n if (st.size > opts.maxFileSizeKB * 1024) continue;\r\n } catch {\r\n continue;\r\n }\r\n\r\n const urlPath = toUrlPath(opts.outDir, abs);\r\n\r\n // ignore list\r\n if (startsWithAny(urlPath, opts.ignorePrefixes)) continue;\r\n\r\n // extension check\r\n const dot = urlPath.lastIndexOf(\".\");\r\n const ext = dot === -1 ? \"\" : urlPath.slice(dot + 1).toLowerCase();\r\n if (!exts.has(ext)) continue;\r\n\r\n results.push(urlPath);\r\n }\r\n };\r\n\r\n walk(opts.outDir);\r\n return results;\r\n}"],"mappings":"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","names":["import_node_path","fs","path","options","import_node_fs","import_node_path","path","fs","path"]}
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import path3 from "path";
|
|
5
5
|
|
|
6
6
|
// src/core/utils.ts
|
|
7
7
|
import fs from "fs";
|
|
@@ -117,72 +117,117 @@ function buildServiceWorkerJS(options2) {
|
|
|
117
117
|
assetExtensions,
|
|
118
118
|
apiPrefixes
|
|
119
119
|
} = options2;
|
|
120
|
+
const precacheList = Array.from(
|
|
121
|
+
new Set([offlinePage, offlineImage, ...precache ?? []].filter(Boolean))
|
|
122
|
+
);
|
|
120
123
|
return `/* offline-page-kit service worker */
|
|
121
124
|
const CACHE_NAME = ${jsString(cacheName)};
|
|
122
125
|
const OFFLINE_PAGE = ${jsString(offlinePage)};
|
|
123
126
|
const OFFLINE_IMAGE = ${jsString(offlineImage)};
|
|
124
|
-
const PRECACHE = ${jsString(
|
|
127
|
+
const PRECACHE = ${jsString(precacheList)};
|
|
125
128
|
|
|
126
129
|
const HTML_STRATEGY = ${jsString(htmlStrategy)};
|
|
127
130
|
const ASSET_STRATEGY = ${jsString(assetStrategy)};
|
|
128
131
|
const IMAGE_STRATEGY = ${jsString(imageStrategy)};
|
|
129
132
|
|
|
130
133
|
const ASSET_EXTS = new Set(${jsString(assetExtensions.length ? assetExtensions : DEFAULT_EXTS)});
|
|
131
|
-
const API_PREFIXES = ${jsString(apiPrefixes)};
|
|
134
|
+
const API_PREFIXES = ${jsString(apiPrefixes || [])};
|
|
135
|
+
|
|
136
|
+
const SAME_ORIGIN_ONLY = true;
|
|
132
137
|
|
|
138
|
+
// ---------------------------
|
|
133
139
|
// helpers
|
|
134
|
-
|
|
140
|
+
// ---------------------------
|
|
141
|
+
const isSameOrigin = (url) => url.origin === self.location.origin;
|
|
142
|
+
|
|
143
|
+
const isApi = (url) => {
|
|
144
|
+
if (!API_PREFIXES || API_PREFIXES.length === 0) return false;
|
|
145
|
+
return API_PREFIXES.some((p) => {
|
|
146
|
+
try {
|
|
147
|
+
// allow either absolute prefix or pathname prefix
|
|
148
|
+
return url.href.startsWith(p) || url.pathname.startsWith(p);
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
135
155
|
const extOf = (pathname) => {
|
|
136
156
|
const i = pathname.lastIndexOf(".");
|
|
137
|
-
return i === -1 ? "" : pathname.slice(i+1).toLowerCase();
|
|
157
|
+
return i === -1 ? "" : pathname.slice(i + 1).toLowerCase();
|
|
138
158
|
};
|
|
139
159
|
|
|
140
|
-
|
|
160
|
+
const isImageExt = (ext) => ["png","jpg","jpeg","webp","gif","svg","ico"].includes(ext);
|
|
161
|
+
|
|
162
|
+
// Only cache successful, basic responses (avoid caching errors)
|
|
163
|
+
const isCacheableResponse = (res) => res && (res.ok || res.type === "opaque");
|
|
164
|
+
|
|
165
|
+
// Cache helpers
|
|
166
|
+
async function cacheGet(reqOrUrl) {
|
|
141
167
|
const cache = await caches.open(CACHE_NAME);
|
|
142
|
-
return cache.match(
|
|
168
|
+
return cache.match(reqOrUrl);
|
|
143
169
|
}
|
|
170
|
+
|
|
144
171
|
async function cachePut(req, res) {
|
|
145
|
-
|
|
146
|
-
|
|
172
|
+
try {
|
|
173
|
+
if (!isCacheableResponse(res)) return;
|
|
174
|
+
const cache = await caches.open(CACHE_NAME);
|
|
175
|
+
await cache.put(req, res);
|
|
176
|
+
} catch {
|
|
177
|
+
// ignore quota / put errors
|
|
178
|
+
}
|
|
147
179
|
}
|
|
148
|
-
|
|
180
|
+
|
|
181
|
+
async function cleanOldCaches() {
|
|
149
182
|
const keys = await caches.keys();
|
|
150
|
-
await Promise.all(keys.map(k => (k === CACHE_NAME ? null : caches.delete(k))));
|
|
183
|
+
await Promise.all(keys.map((k) => (k === CACHE_NAME ? null : caches.delete(k))));
|
|
151
184
|
}
|
|
152
185
|
|
|
186
|
+
// ---------------------------
|
|
153
187
|
// strategies
|
|
188
|
+
// ---------------------------
|
|
154
189
|
async function networkFirst(req, fallbackUrl) {
|
|
155
190
|
try {
|
|
156
191
|
const res = await fetch(req);
|
|
157
|
-
|
|
192
|
+
await cachePut(req, res.clone());
|
|
158
193
|
return res;
|
|
159
194
|
} catch {
|
|
160
195
|
const cached = await cacheGet(req);
|
|
161
196
|
if (cached) return cached;
|
|
162
|
-
if (fallbackUrl)
|
|
163
|
-
|
|
197
|
+
if (fallbackUrl) {
|
|
198
|
+
const fb = await cacheGet(fallbackUrl);
|
|
199
|
+
if (fb) return fb;
|
|
200
|
+
}
|
|
201
|
+
return new Response("Offline", { status: 503, headers: { "content-type": "text/plain" } });
|
|
164
202
|
}
|
|
165
203
|
}
|
|
166
204
|
|
|
167
205
|
async function cacheFirst(req, fallbackUrl) {
|
|
168
206
|
const cached = await cacheGet(req);
|
|
169
207
|
if (cached) return cached;
|
|
208
|
+
|
|
170
209
|
try {
|
|
171
210
|
const res = await fetch(req);
|
|
172
|
-
|
|
211
|
+
await cachePut(req, res.clone());
|
|
173
212
|
return res;
|
|
174
213
|
} catch {
|
|
175
|
-
if (fallbackUrl)
|
|
176
|
-
|
|
214
|
+
if (fallbackUrl) {
|
|
215
|
+
const fb = await cacheGet(fallbackUrl);
|
|
216
|
+
if (fb) return fb;
|
|
217
|
+
}
|
|
218
|
+
return new Response("Offline", { status: 503, headers: { "content-type": "text/plain" } });
|
|
177
219
|
}
|
|
178
220
|
}
|
|
179
221
|
|
|
180
222
|
async function staleWhileRevalidate(req) {
|
|
181
223
|
const cached = await cacheGet(req);
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
224
|
+
|
|
225
|
+
const fetchPromise = fetch(req)
|
|
226
|
+
.then(async (res) => {
|
|
227
|
+
await cachePut(req, res.clone());
|
|
228
|
+
return res;
|
|
229
|
+
})
|
|
230
|
+
.catch(() => null);
|
|
186
231
|
|
|
187
232
|
return cached || (await fetchPromise) || new Response("Offline", { status: 503 });
|
|
188
233
|
}
|
|
@@ -193,16 +238,15 @@ function pickStrategy(name) {
|
|
|
193
238
|
return networkFirst;
|
|
194
239
|
}
|
|
195
240
|
|
|
241
|
+
// ---------------------------
|
|
242
|
+
// lifecycle
|
|
243
|
+
// ---------------------------
|
|
196
244
|
self.addEventListener("install", (event) => {
|
|
197
245
|
event.waitUntil((async () => {
|
|
198
246
|
const cache = await caches.open(CACHE_NAME);
|
|
199
247
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// \u2705 Do not let one 404 kill the whole SW install
|
|
203
|
-
await Promise.allSettled(
|
|
204
|
-
urls.map((u) => cache.add(u))
|
|
205
|
-
);
|
|
248
|
+
// \u2705 fail-safe install (never redundant from a single bad file)
|
|
249
|
+
await Promise.allSettled(PRECACHE.map((u) => cache.add(u)));
|
|
206
250
|
|
|
207
251
|
await self.skipWaiting();
|
|
208
252
|
})());
|
|
@@ -210,32 +254,130 @@ self.addEventListener("install", (event) => {
|
|
|
210
254
|
|
|
211
255
|
self.addEventListener("activate", (event) => {
|
|
212
256
|
event.waitUntil((async () => {
|
|
257
|
+
await cleanOldCaches();
|
|
213
258
|
await self.clients.claim();
|
|
214
259
|
})());
|
|
215
260
|
});
|
|
216
261
|
|
|
262
|
+
// Allow app to trigger update instantly:
|
|
263
|
+
// navigator.serviceWorker.controller?.postMessage({ type: "SKIP_WAITING" })
|
|
264
|
+
self.addEventListener("message", (event) => {
|
|
265
|
+
if (event?.data?.type === "SKIP_WAITING") {
|
|
266
|
+
self.skipWaiting();
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// ---------------------------
|
|
271
|
+
// fetch routing
|
|
272
|
+
// ---------------------------
|
|
217
273
|
self.addEventListener("fetch", (event) => {
|
|
218
274
|
const req = event.request;
|
|
219
275
|
|
|
220
|
-
//
|
|
221
|
-
if (req.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
276
|
+
// Don't touch non-GET requests
|
|
277
|
+
if (req.method && req.method !== "GET") return;
|
|
278
|
+
|
|
279
|
+
const url = new URL(req.url);
|
|
280
|
+
|
|
281
|
+
// Safe default: same-origin only
|
|
282
|
+
if (SAME_ORIGIN_ONLY && !isSameOrigin(url)) return;
|
|
283
|
+
|
|
284
|
+
const accept = req.headers.get("accept") || "";
|
|
285
|
+
const ext = extOf(url.pathname);
|
|
286
|
+
|
|
287
|
+
// 1) HTML / navigations -> offline page fallback
|
|
288
|
+
const isHtml = req.mode === "navigate" || accept.includes("text/html");
|
|
289
|
+
if (isHtml) {
|
|
290
|
+
const fn = pickStrategy(HTML_STRATEGY);
|
|
291
|
+
event.respondWith(fn(req, OFFLINE_PAGE));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 2) API calls (network-first by default)
|
|
296
|
+
if (isApi(url)) {
|
|
297
|
+
event.respondWith(networkFirst(req));
|
|
298
|
+
return;
|
|
230
299
|
}
|
|
300
|
+
|
|
301
|
+
// 3) Images (cache-first default) with OFFLINE_IMAGE fallback
|
|
302
|
+
if (isImageExt(ext)) {
|
|
303
|
+
const fn = pickStrategy(IMAGE_STRATEGY);
|
|
304
|
+
event.respondWith(fn(req, OFFLINE_IMAGE));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 4) Static assets (js/css/fonts/json/etc.)
|
|
309
|
+
if (ASSET_EXTS.has(ext)) {
|
|
310
|
+
const fn = pickStrategy(ASSET_STRATEGY);
|
|
311
|
+
if (fn === staleWhileRevalidate) {
|
|
312
|
+
event.respondWith(staleWhileRevalidate(req));
|
|
313
|
+
} else {
|
|
314
|
+
event.respondWith(fn(req));
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Everything else: do nothing (network as normal)
|
|
231
320
|
});
|
|
232
321
|
`;
|
|
233
322
|
}
|
|
234
323
|
|
|
324
|
+
// src/core/precacheScan.ts
|
|
325
|
+
import fs2 from "fs";
|
|
326
|
+
import path2 from "path";
|
|
327
|
+
function normalizeExts(exts) {
|
|
328
|
+
return new Set(exts.map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
329
|
+
}
|
|
330
|
+
function toUrlPath(outDirAbs, fileAbs) {
|
|
331
|
+
const rel = path2.relative(outDirAbs, fileAbs).split(path2.sep).join("/");
|
|
332
|
+
return "/" + rel.replace(/^\/+/, "");
|
|
333
|
+
}
|
|
334
|
+
function startsWithAny(urlPath, prefixes) {
|
|
335
|
+
return prefixes.some((p) => urlPath.startsWith(p));
|
|
336
|
+
}
|
|
337
|
+
function scanPrecacheFiles(opts) {
|
|
338
|
+
const exts = normalizeExts(opts.extensions);
|
|
339
|
+
const results = [];
|
|
340
|
+
const walk = (dirAbs) => {
|
|
341
|
+
if (results.length >= opts.maxFiles) return;
|
|
342
|
+
const entries = fs2.readdirSync(dirAbs, { withFileTypes: true });
|
|
343
|
+
for (const ent of entries) {
|
|
344
|
+
if (results.length >= opts.maxFiles) break;
|
|
345
|
+
const abs = path2.join(dirAbs, ent.name);
|
|
346
|
+
if (ent.isDirectory()) {
|
|
347
|
+
walk(abs);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (!ent.isFile()) continue;
|
|
351
|
+
try {
|
|
352
|
+
const st = fs2.statSync(abs);
|
|
353
|
+
if (st.size > opts.maxFileSizeKB * 1024) continue;
|
|
354
|
+
} catch {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const urlPath = toUrlPath(opts.outDir, abs);
|
|
358
|
+
if (startsWithAny(urlPath, opts.ignorePrefixes)) continue;
|
|
359
|
+
const dot = urlPath.lastIndexOf(".");
|
|
360
|
+
const ext = dot === -1 ? "" : urlPath.slice(dot + 1).toLowerCase();
|
|
361
|
+
if (!exts.has(ext)) continue;
|
|
362
|
+
results.push(urlPath);
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
walk(opts.outDir);
|
|
366
|
+
return results;
|
|
367
|
+
}
|
|
368
|
+
|
|
235
369
|
// src/cli.ts
|
|
236
370
|
function normalizePublicPath(p) {
|
|
237
|
-
if (!p
|
|
238
|
-
return p;
|
|
371
|
+
if (!p) return "/";
|
|
372
|
+
return p.startsWith("/") ? p : "/" + p;
|
|
373
|
+
}
|
|
374
|
+
function normalizeIgnorePrefix(p) {
|
|
375
|
+
return normalizePublicPath(p.trim());
|
|
376
|
+
}
|
|
377
|
+
function safeNumber(v, fallback) {
|
|
378
|
+
if (!v) return fallback;
|
|
379
|
+
const n = Number(v);
|
|
380
|
+
return Number.isFinite(n) ? n : fallback;
|
|
239
381
|
}
|
|
240
382
|
function withDefaults(o) {
|
|
241
383
|
return {
|
|
@@ -249,28 +391,69 @@ function withDefaults(o) {
|
|
|
249
391
|
assetStrategy: o.assetStrategy ?? "staleWhileRevalidate",
|
|
250
392
|
imageStrategy: o.imageStrategy ?? "cacheFirst",
|
|
251
393
|
assetExtensions: o.assetExtensions ?? [],
|
|
252
|
-
apiPrefixes: o.apiPrefixes ?? []
|
|
394
|
+
apiPrefixes: o.apiPrefixes ?? [],
|
|
395
|
+
autoPrecache: o.autoPrecache ?? true,
|
|
396
|
+
precacheExtensions: o.precacheExtensions ?? [
|
|
397
|
+
"ico",
|
|
398
|
+
"png",
|
|
399
|
+
"jpg",
|
|
400
|
+
"jpeg",
|
|
401
|
+
"webp",
|
|
402
|
+
"svg",
|
|
403
|
+
"gif",
|
|
404
|
+
"css",
|
|
405
|
+
"js",
|
|
406
|
+
"woff2",
|
|
407
|
+
"woff",
|
|
408
|
+
"ttf",
|
|
409
|
+
"eot",
|
|
410
|
+
"json",
|
|
411
|
+
"webmanifest",
|
|
412
|
+
"txt",
|
|
413
|
+
"xml"
|
|
414
|
+
],
|
|
415
|
+
precacheIgnore: o.precacheIgnore ?? [
|
|
416
|
+
"/sw.js",
|
|
417
|
+
"/sw.js.map",
|
|
418
|
+
"/_next/",
|
|
419
|
+
"/static/"
|
|
420
|
+
],
|
|
421
|
+
precacheMaxFiles: o.precacheMaxFiles ?? 200,
|
|
422
|
+
precacheMaxFileSizeKB: o.precacheMaxFileSizeKB ?? 512
|
|
253
423
|
};
|
|
254
424
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
425
|
+
function parseCommand(argv) {
|
|
426
|
+
const first = argv[0];
|
|
427
|
+
if (first === "init" || first === "build") {
|
|
428
|
+
return { cmd: first, rest: argv.slice(1) };
|
|
429
|
+
}
|
|
430
|
+
return { cmd: "init", rest: argv };
|
|
431
|
+
}
|
|
432
|
+
var rawArgv = process.argv.slice(2);
|
|
433
|
+
var { cmd, rest } = parseCommand(rawArgv);
|
|
434
|
+
var args = parseArgs(rest);
|
|
435
|
+
var outDir = path3.resolve(process.cwd(), args.get("outDir") || "public");
|
|
258
436
|
var options = withDefaults({
|
|
259
437
|
outDir,
|
|
260
438
|
swFileName: args.get("swFileName") || "sw.js",
|
|
261
439
|
offlinePage: args.get("offlinePage") || "/offline.html",
|
|
262
440
|
offlineImage: args.get("offlineImage") || "/offline.svg",
|
|
263
441
|
cacheName: args.get("cacheName") || "offline-page-kit",
|
|
264
|
-
precache: splitList(args.get("precache")),
|
|
442
|
+
precache: splitList(args.get("precache")).map(normalizePublicPath),
|
|
265
443
|
htmlStrategy: args.get("htmlStrategy") || "networkFirst",
|
|
266
444
|
assetStrategy: args.get("assetStrategy") || "staleWhileRevalidate",
|
|
267
445
|
imageStrategy: args.get("imageStrategy") || "cacheFirst",
|
|
268
446
|
assetExtensions: splitList(args.get("assetExtensions")),
|
|
269
|
-
apiPrefixes: splitList(args.get("apiPrefixes"))
|
|
447
|
+
apiPrefixes: splitList(args.get("apiPrefixes")),
|
|
448
|
+
autoPrecache: args.get("autoPrecache") !== "false",
|
|
449
|
+
precacheExtensions: splitList(args.get("precacheExtensions")),
|
|
450
|
+
precacheIgnore: splitList(args.get("precacheIgnore")).map(normalizeIgnorePrefix),
|
|
451
|
+
precacheMaxFiles: safeNumber(args.get("precacheMaxFiles"), 200),
|
|
452
|
+
precacheMaxFileSizeKB: safeNumber(args.get("precacheMaxFileSizeKB"), 512)
|
|
270
453
|
});
|
|
271
|
-
var swOut =
|
|
272
|
-
var offlineHtmlOut =
|
|
273
|
-
var offlineSvgOut =
|
|
454
|
+
var swOut = path3.join(outDir, options.swFileName);
|
|
455
|
+
var offlineHtmlOut = path3.join(outDir, options.offlinePage.replace(/^\//, ""));
|
|
456
|
+
var offlineSvgOut = path3.join(outDir, options.offlineImage.replace(/^\//, ""));
|
|
274
457
|
if (cmd === "init" || cmd === "build") {
|
|
275
458
|
if (cmd === "build" || !exists(offlineHtmlOut)) {
|
|
276
459
|
writeFileSafe(offlineHtmlOut, offlineHtmlTemplate());
|
|
@@ -278,12 +461,46 @@ if (cmd === "init" || cmd === "build") {
|
|
|
278
461
|
if (cmd === "build" || !exists(offlineSvgOut)) {
|
|
279
462
|
writeFileSafe(offlineSvgOut, offlineSvgTemplate());
|
|
280
463
|
}
|
|
281
|
-
|
|
464
|
+
let scanned = [];
|
|
465
|
+
if (options.autoPrecache) {
|
|
466
|
+
const swUrlPath = normalizePublicPath(options.swFileName);
|
|
467
|
+
const swMapUrlPath = swUrlPath + ".map";
|
|
468
|
+
const ignorePrefixes = Array.from(/* @__PURE__ */ new Set([
|
|
469
|
+
swUrlPath,
|
|
470
|
+
swMapUrlPath,
|
|
471
|
+
...options.precacheIgnore.map(normalizeIgnorePrefix)
|
|
472
|
+
]));
|
|
473
|
+
scanned = scanPrecacheFiles({
|
|
474
|
+
outDir,
|
|
475
|
+
// absolute /public
|
|
476
|
+
extensions: options.precacheExtensions.length ? options.precacheExtensions : ["ico", "png", "css", "js", "woff2", "json", "webmanifest", "svg"],
|
|
477
|
+
ignorePrefixes,
|
|
478
|
+
maxFiles: options.precacheMaxFiles,
|
|
479
|
+
maxFileSizeKB: options.precacheMaxFileSizeKB
|
|
480
|
+
}).map(normalizePublicPath);
|
|
481
|
+
}
|
|
482
|
+
const allPrecache = Array.from(/* @__PURE__ */ new Set([
|
|
483
|
+
normalizePublicPath(options.offlinePage),
|
|
484
|
+
normalizePublicPath(options.offlineImage),
|
|
485
|
+
...(options.precache || []).map(normalizePublicPath),
|
|
486
|
+
...scanned.map(normalizePublicPath)
|
|
487
|
+
]));
|
|
488
|
+
const sw = buildServiceWorkerJS({
|
|
489
|
+
...options,
|
|
490
|
+
precache: allPrecache
|
|
491
|
+
});
|
|
282
492
|
writeFileSafe(swOut, sw);
|
|
493
|
+
const manualCount = (options.precache || []).length;
|
|
494
|
+
const scannedCount = scanned.length;
|
|
283
495
|
console.log(`[offline-page-kit] Generated:
|
|
284
496
|
- ${swOut}
|
|
285
497
|
- ${offlineHtmlOut}
|
|
286
498
|
- ${offlineSvgOut}
|
|
499
|
+
|
|
500
|
+
Precache:
|
|
501
|
+
- manual: ${manualCount}
|
|
502
|
+
- scanned: ${scannedCount}
|
|
503
|
+
- total: ${allPrecache.length}
|
|
287
504
|
`);
|
|
288
505
|
} else {
|
|
289
506
|
console.log(`[offline-page-kit] Unknown command: ${cmd}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/core/utils.ts","../src/core/offlineHtml.ts","../src/core/offlineSvg.ts","../src/core/swTemplate.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport path from \"node:path\";\r\nimport { parseArgs, splitList, writeFileSafe, exists } from \"./core/utils\";\r\nimport { offlineHtmlTemplate } from \"./core/offlineHtml\";\r\nimport { offlineSvgTemplate } from \"./core/offlineSvg\";\r\nimport { buildServiceWorkerJS } from \"./core/swTemplate\";\r\nimport type { OfflineKitBuildOptions } from \"./types\";\r\n\r\nfunction normalizePublicPath(p: string) {\r\n if (!p.startsWith(\"/\")) return \"/\" + p;\r\n return p;\r\n}\r\n\r\nfunction withDefaults(o: OfflineKitBuildOptions): Required<OfflineKitBuildOptions> {\r\n return {\r\n outDir: o.outDir ?? \"public\",\r\n swFileName: o.swFileName ?? \"sw.js\",\r\n offlinePage: normalizePublicPath(o.offlinePage ?? \"/offline.html\"),\r\n offlineImage: normalizePublicPath(o.offlineImage ?? \"/offline.svg\"),\r\n cacheName: o.cacheName ?? \"offline-page-kit\",\r\n precache: o.precache ?? [],\r\n htmlStrategy: o.htmlStrategy ?? \"networkFirst\",\r\n assetStrategy: o.assetStrategy ?? \"staleWhileRevalidate\",\r\n imageStrategy: o.imageStrategy ?? \"cacheFirst\",\r\n assetExtensions: o.assetExtensions ?? [],\r\n apiPrefixes: o.apiPrefixes ?? [],\r\n };\r\n}\r\n\r\nconst args = parseArgs(process.argv.slice(2));\r\nconst cmd = process.argv.slice(2).find(a => !a.startsWith(\"--\")) || \"init\";\r\n\r\nconst outDir = path.resolve(process.cwd(), args.get(\"outDir\") || \"public\");\r\n\r\nconst options = withDefaults({\r\n outDir,\r\n swFileName: args.get(\"swFileName\") || \"sw.js\",\r\n offlinePage: args.get(\"offlinePage\") || \"/offline.html\",\r\n offlineImage: args.get(\"offlineImage\") || \"/offline.svg\",\r\n cacheName: args.get(\"cacheName\") || \"offline-page-kit\",\r\n precache: splitList(args.get(\"precache\")),\r\n htmlStrategy: (args.get(\"htmlStrategy\") as any) || \"networkFirst\",\r\n assetStrategy: (args.get(\"assetStrategy\") as any) || \"staleWhileRevalidate\",\r\n imageStrategy: (args.get(\"imageStrategy\") as any) || \"cacheFirst\",\r\n assetExtensions: splitList(args.get(\"assetExtensions\")),\r\n apiPrefixes: splitList(args.get(\"apiPrefixes\")),\r\n} as OfflineKitBuildOptions);\r\n\r\nconst swOut = path.join(outDir, options.swFileName);\r\nconst offlineHtmlOut = path.join(outDir, options.offlinePage.replace(/^\\//, \"\"));\r\nconst offlineSvgOut = path.join(outDir, options.offlineImage.replace(/^\\//, \"\"));\r\n\r\nif (cmd === \"init\" || cmd === \"build\") {\r\n // generate offline page if missing OR force (when build)\r\n if (cmd === \"build\" || !exists(offlineHtmlOut)) {\r\n writeFileSafe(offlineHtmlOut, offlineHtmlTemplate());\r\n }\r\n if (cmd === \"build\" || !exists(offlineSvgOut)) {\r\n writeFileSafe(offlineSvgOut, offlineSvgTemplate());\r\n }\r\n\r\n const sw = buildServiceWorkerJS(options);\r\n writeFileSafe(swOut, sw);\r\n\r\n console.log(`[offline-page-kit] Generated:\r\n- ${swOut}\r\n- ${offlineHtmlOut}\r\n- ${offlineSvgOut}\r\n`);\r\n} else {\r\n console.log(`[offline-page-kit] Unknown command: ${cmd}\r\nUse:\r\n offline-page-kit init --outDir public\r\n offline-page-kit build --outDir public\r\n`);\r\n}","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\n\r\nexport function ensureDir(p: string) {\r\n fs.mkdirSync(p, { recursive: true });\r\n}\r\n\r\nexport function writeFileSafe(filePath: string, content: string) {\r\n ensureDir(path.dirname(filePath));\r\n fs.writeFileSync(filePath, content, \"utf8\");\r\n}\r\n\r\nexport function exists(filePath: string) {\r\n try { fs.accessSync(filePath); return true; } catch { return false; }\r\n}\r\n\r\nexport function parseArgs(argv: string[]) {\r\n const m = new Map<string, string>();\r\n for (let i = 0; i < argv.length; i++) {\r\n const a = argv[i];\r\n if (!a.startsWith(\"--\")) continue;\r\n const key = a.slice(2);\r\n const value = argv[i + 1] && !argv[i + 1].startsWith(\"--\") ? argv[++i] : \"true\";\r\n m.set(key, value);\r\n }\r\n return m;\r\n}\r\n\r\nexport function splitList(v: string | undefined) {\r\n return (v || \"\")\r\n .split(\",\")\r\n .map(s => s.trim())\r\n .filter(Boolean);\r\n}","export function offlineHtmlTemplate(title = \"You're Offline\") {\r\n return `<!doctype html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\"/>\r\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>\r\n <title>${title}</title>\r\n <style>\r\n body{margin:0;height:100vh;display:grid;place-items:center;font-family:system-ui,-apple-system,Segoe UI,Roboto;background:#0b0f19;color:#fff}\r\n .box{max-width:560px;padding:28px 26px;border-radius:18px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.04)}\r\n h1{margin:0 0 10px;font-size:28px}\r\n p{margin:0 0 18px;opacity:.85;line-height:1.5}\r\n .row{display:flex;gap:10px;flex-wrap:wrap}\r\n button,a{appearance:none;border:0;border-radius:12px;padding:10px 14px;cursor:pointer;text-decoration:none}\r\n button{background:#fff;color:#111}\r\n a{background:rgba(255,255,255,.1);color:#fff}\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"box\">\r\n <h1>⚡ You’re Offline</h1>\r\n <p>No internet connection detected. You can retry, or go back to the homepage (if cached).</p>\r\n <div class=\"row\">\r\n <button onclick=\"location.reload()\">Retry</button>\r\n <a href=\"/\">Go Home</a>\r\n </div>\r\n </div>\r\n</body>\r\n</html>`;\r\n}","export function offlineSvgTemplate() {\r\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"630\" viewBox=\"0 0 1200 630\">\r\n <rect width=\"1200\" height=\"630\" fill=\"#0b0f19\"/>\r\n <text x=\"80\" y=\"220\" fill=\"#ffffff\" font-size=\"64\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto\">You’re Offline</text>\r\n <text x=\"80\" y=\"300\" fill=\"#cbd5e1\" font-size=\"28\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto\">Please check your connection and try again.</text>\r\n <circle cx=\"1040\" cy=\"220\" r=\"90\" fill=\"rgba(255,255,255,0.08)\"/>\r\n <path d=\"M980 220c40-40 120-40 160 0\" stroke=\"#fff\" stroke-width=\"10\" fill=\"none\" opacity=\"0.6\"/>\r\n <path d=\"M1010 250c25-25 75-25 100 0\" stroke=\"#fff\" stroke-width=\"10\" fill=\"none\" opacity=\"0.6\"/>\r\n <circle cx=\"1060\" cy=\"290\" r=\"10\" fill=\"#fff\" opacity=\"0.7\"/>\r\n</svg>`;\r\n}","import type { OfflineKitBuildOptions, Strategy } from \"../types\";\r\n\r\nconst DEFAULT_EXTS = [\r\n \"js\", \"css\", \"map\", \"ico\", \"png\", \"jpg\", \"jpeg\", \"webp\", \"svg\", \"gif\",\r\n \"woff2\", \"woff\", \"ttf\", \"eot\", \"json\", \"txt\", \"xml\", \"webmanifest\"\r\n];\r\n\r\nfunction jsString(v: unknown) {\r\n return JSON.stringify(v);\r\n}\r\n\r\nexport function buildServiceWorkerJS(options: Required<OfflineKitBuildOptions>) {\r\n const {\r\n cacheName,\r\n offlinePage,\r\n offlineImage,\r\n precache,\r\n htmlStrategy,\r\n assetStrategy,\r\n imageStrategy,\r\n assetExtensions,\r\n apiPrefixes,\r\n } = options;\r\n\r\n return `/* offline-page-kit service worker */\r\nconst CACHE_NAME = ${jsString(cacheName)};\r\nconst OFFLINE_PAGE = ${jsString(offlinePage)};\r\nconst OFFLINE_IMAGE = ${jsString(offlineImage)};\r\nconst PRECACHE = ${jsString([offlinePage, offlineImage, ...precache].filter(Boolean))};\r\n\r\nconst HTML_STRATEGY = ${jsString(htmlStrategy)};\r\nconst ASSET_STRATEGY = ${jsString(assetStrategy)};\r\nconst IMAGE_STRATEGY = ${jsString(imageStrategy)};\r\n\r\nconst ASSET_EXTS = new Set(${jsString(assetExtensions.length ? assetExtensions : DEFAULT_EXTS)});\r\nconst API_PREFIXES = ${jsString(apiPrefixes)};\r\n\r\n// helpers\r\nconst isApi = (url) => API_PREFIXES.some(p => url.href.startsWith(p) || url.pathname.startsWith(p));\r\nconst extOf = (pathname) => {\r\n const i = pathname.lastIndexOf(\".\");\r\n return i === -1 ? \"\" : pathname.slice(i+1).toLowerCase();\r\n};\r\n\r\nasync function cacheGet(req) {\r\n const cache = await caches.open(CACHE_NAME);\r\n return cache.match(req);\r\n}\r\nasync function cachePut(req, res) {\r\n const cache = await caches.open(CACHE_NAME);\r\n await cache.put(req, res);\r\n}\r\nasync function cacheDeleteOld() {\r\n const keys = await caches.keys();\r\n await Promise.all(keys.map(k => (k === CACHE_NAME ? null : caches.delete(k))));\r\n}\r\n\r\n// strategies\r\nasync function networkFirst(req, fallbackUrl) {\r\n try {\r\n const res = await fetch(req);\r\n if (res && res.ok) cachePut(req, res.clone());\r\n return res;\r\n } catch {\r\n const cached = await cacheGet(req);\r\n if (cached) return cached;\r\n if (fallbackUrl) return cacheGet(fallbackUrl);\r\n return new Response(\"Offline\", { status: 503 });\r\n }\r\n}\r\n\r\nasync function cacheFirst(req, fallbackUrl) {\r\n const cached = await cacheGet(req);\r\n if (cached) return cached;\r\n try {\r\n const res = await fetch(req);\r\n if (res && res.ok) cachePut(req, res.clone());\r\n return res;\r\n } catch {\r\n if (fallbackUrl) return cacheGet(fallbackUrl);\r\n return new Response(\"Offline\", { status: 503 });\r\n }\r\n}\r\n\r\nasync function staleWhileRevalidate(req) {\r\n const cached = await cacheGet(req);\r\n const fetchPromise = fetch(req).then(res => {\r\n if (res && res.ok) cachePut(req, res.clone());\r\n return res;\r\n }).catch(() => null);\r\n\r\n return cached || (await fetchPromise) || new Response(\"Offline\", { status: 503 });\r\n}\r\n\r\nfunction pickStrategy(name) {\r\n if (name === \"cacheFirst\") return cacheFirst;\r\n if (name === \"staleWhileRevalidate\") return staleWhileRevalidate;\r\n return networkFirst;\r\n}\r\n\r\nself.addEventListener(\"install\", (event) => {\r\n event.waitUntil((async () => {\r\n const cache = await caches.open(CACHE_NAME);\r\n\r\n const urls = [OFFLINE_PAGE, OFFLINE_IMAGE];\r\n\r\n // ✅ Do not let one 404 kill the whole SW install\r\n await Promise.allSettled(\r\n urls.map((u) => cache.add(u))\r\n );\r\n\r\n await self.skipWaiting();\r\n })());\r\n});\r\n\r\nself.addEventListener(\"activate\", (event) => {\r\n event.waitUntil((async () => {\r\n await self.clients.claim();\r\n })());\r\n});\r\n\r\nself.addEventListener(\"fetch\", (event) => {\r\n const req = event.request;\r\n\r\n // ✅ Offline fallback for page navigations\r\n if (req.mode === \"navigate\") {\r\n event.respondWith((async () => {\r\n try {\r\n return await fetch(req);\r\n } catch {\r\n const cache = await caches.open(CACHE_NAME);\r\n return (await cache.match(OFFLINE_PAGE)) || new Response(\"Offline\", { status: 503 });\r\n }\r\n })());\r\n }\r\n});\r\n`;\r\n}"],"mappings":"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","names":["path","options","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/core/utils.ts","../src/core/offlineHtml.ts","../src/core/offlineSvg.ts","../src/core/swTemplate.ts","../src/core/precacheScan.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport path from \"node:path\";\r\nimport { parseArgs, splitList, writeFileSafe, exists } from \"./core/utils\";\r\nimport { offlineHtmlTemplate } from \"./core/offlineHtml\";\r\nimport { offlineSvgTemplate } from \"./core/offlineSvg\";\r\nimport { buildServiceWorkerJS } from \"./core/swTemplate\";\r\nimport type { OfflineKitBuildOptions } from \"./types\";\r\nimport { scanPrecacheFiles } from \"./core/precacheScan\";\r\n\r\ntype Cmd = \"init\" | \"build\";\r\n\r\nfunction normalizePublicPath(p: string) {\r\n if (!p) return \"/\";\r\n return p.startsWith(\"/\") ? p : \"/\" + p;\r\n}\r\n\r\nfunction normalizeIgnorePrefix(p: string) {\r\n // ignore prefixes are URL-style and should start with \"/\"\r\n return normalizePublicPath(p.trim());\r\n}\r\n\r\nfunction safeNumber(v: string | undefined, fallback: number) {\r\n if (!v) return fallback;\r\n const n = Number(v);\r\n return Number.isFinite(n) ? n : fallback;\r\n}\r\n\r\nfunction withDefaults(o: OfflineKitBuildOptions): Required<OfflineKitBuildOptions> {\r\n return {\r\n outDir: o.outDir ?? \"public\",\r\n swFileName: o.swFileName ?? \"sw.js\",\r\n\r\n offlinePage: normalizePublicPath(o.offlinePage ?? \"/offline.html\"),\r\n offlineImage: normalizePublicPath(o.offlineImage ?? \"/offline.svg\"),\r\n\r\n cacheName: o.cacheName ?? \"offline-page-kit\",\r\n precache: o.precache ?? [],\r\n\r\n htmlStrategy: o.htmlStrategy ?? \"networkFirst\",\r\n assetStrategy: o.assetStrategy ?? \"staleWhileRevalidate\",\r\n imageStrategy: o.imageStrategy ?? \"cacheFirst\",\r\n\r\n assetExtensions: o.assetExtensions ?? [],\r\n apiPrefixes: o.apiPrefixes ?? [],\r\n\r\n autoPrecache: o.autoPrecache ?? true,\r\n precacheExtensions: o.precacheExtensions ?? [\r\n \"ico\", \"png\", \"jpg\", \"jpeg\", \"webp\", \"svg\", \"gif\",\r\n \"css\", \"js\", \"woff2\", \"woff\", \"ttf\", \"eot\",\r\n \"json\", \"webmanifest\", \"txt\", \"xml\"\r\n ],\r\n precacheIgnore: o.precacheIgnore ?? [\r\n \"/sw.js\",\r\n \"/sw.js.map\",\r\n \"/_next/\",\r\n \"/static/\"\r\n ],\r\n precacheMaxFiles: o.precacheMaxFiles ?? 200,\r\n precacheMaxFileSizeKB: o.precacheMaxFileSizeKB ?? 512,\r\n };\r\n}\r\n\r\n/**\r\n * Command parsing\r\n * Supports:\r\n * offline-page-kit init --outDir public\r\n * offline-page-kit --outDir public (defaults to init)\r\n */\r\nfunction parseCommand(argv: string[]): { cmd: Cmd; rest: string[] } {\r\n const first = argv[0];\r\n if (first === \"init\" || first === \"build\") {\r\n return { cmd: first, rest: argv.slice(1) };\r\n }\r\n return { cmd: \"init\", rest: argv };\r\n}\r\n\r\nconst rawArgv = process.argv.slice(2);\r\nconst { cmd, rest } = parseCommand(rawArgv);\r\nconst args = parseArgs(rest);\r\n\r\nconst outDir = path.resolve(process.cwd(), args.get(\"outDir\") || \"public\");\r\n\r\nconst options = withDefaults({\r\n outDir,\r\n swFileName: args.get(\"swFileName\") || \"sw.js\",\r\n offlinePage: args.get(\"offlinePage\") || \"/offline.html\",\r\n offlineImage: args.get(\"offlineImage\") || \"/offline.svg\",\r\n cacheName: args.get(\"cacheName\") || \"offline-page-kit\",\r\n precache: splitList(args.get(\"precache\")).map(normalizePublicPath),\r\n\r\n htmlStrategy: (args.get(\"htmlStrategy\") as any) || \"networkFirst\",\r\n assetStrategy: (args.get(\"assetStrategy\") as any) || \"staleWhileRevalidate\",\r\n imageStrategy: (args.get(\"imageStrategy\") as any) || \"cacheFirst\",\r\n\r\n assetExtensions: splitList(args.get(\"assetExtensions\")),\r\n apiPrefixes: splitList(args.get(\"apiPrefixes\")),\r\n\r\n autoPrecache: args.get(\"autoPrecache\") !== \"false\",\r\n precacheExtensions: splitList(args.get(\"precacheExtensions\")),\r\n precacheIgnore: splitList(args.get(\"precacheIgnore\")).map(normalizeIgnorePrefix),\r\n\r\n precacheMaxFiles: safeNumber(args.get(\"precacheMaxFiles\"), 200),\r\n precacheMaxFileSizeKB: safeNumber(args.get(\"precacheMaxFileSizeKB\"), 512),\r\n} as OfflineKitBuildOptions);\r\n\r\nconst swOut = path.join(outDir, options.swFileName);\r\nconst offlineHtmlOut = path.join(outDir, options.offlinePage.replace(/^\\//, \"\"));\r\nconst offlineSvgOut = path.join(outDir, options.offlineImage.replace(/^\\//, \"\"));\r\n\r\nif (cmd === \"init\" || cmd === \"build\") {\r\n // 1) Generate offline page + offline svg first (so scanner can find them)\r\n if (cmd === \"build\" || !exists(offlineHtmlOut)) {\r\n writeFileSafe(offlineHtmlOut, offlineHtmlTemplate());\r\n }\r\n if (cmd === \"build\" || !exists(offlineSvgOut)) {\r\n writeFileSafe(offlineSvgOut, offlineSvgTemplate());\r\n }\r\n\r\n // 2) Auto-scan (after offline files exist)\r\n let scanned: string[] = [];\r\n if (options.autoPrecache) {\r\n // ✅ always ignore the actual sw filename (even if user customizes it)\r\n const swUrlPath = normalizePublicPath(options.swFileName);\r\n const swMapUrlPath = swUrlPath + \".map\";\r\n\r\n const ignorePrefixes = Array.from(new Set([\r\n swUrlPath,\r\n swMapUrlPath,\r\n ...options.precacheIgnore.map(normalizeIgnorePrefix),\r\n ]));\r\n\r\n scanned = scanPrecacheFiles({\r\n outDir, // absolute /public\r\n extensions: options.precacheExtensions.length\r\n ? options.precacheExtensions\r\n : [\"ico\", \"png\", \"css\", \"js\", \"woff2\", \"json\", \"webmanifest\", \"svg\"],\r\n ignorePrefixes,\r\n maxFiles: options.precacheMaxFiles,\r\n maxFileSizeKB: options.precacheMaxFileSizeKB,\r\n }).map(normalizePublicPath);\r\n }\r\n\r\n // 3) Merge + de-duplicate (and normalize)\r\n const allPrecache = Array.from(new Set([\r\n normalizePublicPath(options.offlinePage),\r\n normalizePublicPath(options.offlineImage),\r\n ...(options.precache || []).map(normalizePublicPath),\r\n ...scanned.map(normalizePublicPath),\r\n ]));\r\n\r\n // 4) Build SW using merged list\r\n const sw = buildServiceWorkerJS({\r\n ...options,\r\n precache: allPrecache,\r\n });\r\n\r\n writeFileSafe(swOut, sw);\r\n\r\n const manualCount = (options.precache || []).length;\r\n const scannedCount = scanned.length;\r\n\r\n console.log(`[offline-page-kit] Generated:\r\n- ${swOut}\r\n- ${offlineHtmlOut}\r\n- ${offlineSvgOut}\r\n\r\nPrecache:\r\n- manual: ${manualCount}\r\n- scanned: ${scannedCount}\r\n- total: ${allPrecache.length}\r\n`);\r\n} else {\r\n console.log(`[offline-page-kit] Unknown command: ${cmd}\r\nUse:\r\n offline-page-kit init --outDir public\r\n offline-page-kit build --outDir public\r\n`);\r\n}","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\n\r\nexport function ensureDir(p: string) {\r\n fs.mkdirSync(p, { recursive: true });\r\n}\r\n\r\nexport function writeFileSafe(filePath: string, content: string) {\r\n ensureDir(path.dirname(filePath));\r\n fs.writeFileSync(filePath, content, \"utf8\");\r\n}\r\n\r\nexport function exists(filePath: string) {\r\n try { fs.accessSync(filePath); return true; } catch { return false; }\r\n}\r\n\r\nexport function parseArgs(argv: string[]) {\r\n const m = new Map<string, string>();\r\n for (let i = 0; i < argv.length; i++) {\r\n const a = argv[i];\r\n if (!a.startsWith(\"--\")) continue;\r\n const key = a.slice(2);\r\n const value = argv[i + 1] && !argv[i + 1].startsWith(\"--\") ? argv[++i] : \"true\";\r\n m.set(key, value);\r\n }\r\n return m;\r\n}\r\n\r\nexport function splitList(v: string | undefined) {\r\n return (v || \"\")\r\n .split(\",\")\r\n .map(s => s.trim())\r\n .filter(Boolean);\r\n}","export function offlineHtmlTemplate(title = \"You're Offline\") {\r\n return `<!doctype html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\"/>\r\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>\r\n <title>${title}</title>\r\n <style>\r\n body{margin:0;height:100vh;display:grid;place-items:center;font-family:system-ui,-apple-system,Segoe UI,Roboto;background:#0b0f19;color:#fff}\r\n .box{max-width:560px;padding:28px 26px;border-radius:18px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.04)}\r\n h1{margin:0 0 10px;font-size:28px}\r\n p{margin:0 0 18px;opacity:.85;line-height:1.5}\r\n .row{display:flex;gap:10px;flex-wrap:wrap}\r\n button,a{appearance:none;border:0;border-radius:12px;padding:10px 14px;cursor:pointer;text-decoration:none}\r\n button{background:#fff;color:#111}\r\n a{background:rgba(255,255,255,.1);color:#fff}\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"box\">\r\n <h1>⚡ You’re Offline</h1>\r\n <p>No internet connection detected. You can retry, or go back to the homepage (if cached).</p>\r\n <div class=\"row\">\r\n <button onclick=\"location.reload()\">Retry</button>\r\n <a href=\"/\">Go Home</a>\r\n </div>\r\n </div>\r\n</body>\r\n</html>`;\r\n}","export function offlineSvgTemplate() {\r\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"630\" viewBox=\"0 0 1200 630\">\r\n <rect width=\"1200\" height=\"630\" fill=\"#0b0f19\"/>\r\n <text x=\"80\" y=\"220\" fill=\"#ffffff\" font-size=\"64\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto\">You’re Offline</text>\r\n <text x=\"80\" y=\"300\" fill=\"#cbd5e1\" font-size=\"28\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto\">Please check your connection and try again.</text>\r\n <circle cx=\"1040\" cy=\"220\" r=\"90\" fill=\"rgba(255,255,255,0.08)\"/>\r\n <path d=\"M980 220c40-40 120-40 160 0\" stroke=\"#fff\" stroke-width=\"10\" fill=\"none\" opacity=\"0.6\"/>\r\n <path d=\"M1010 250c25-25 75-25 100 0\" stroke=\"#fff\" stroke-width=\"10\" fill=\"none\" opacity=\"0.6\"/>\r\n <circle cx=\"1060\" cy=\"290\" r=\"10\" fill=\"#fff\" opacity=\"0.7\"/>\r\n</svg>`;\r\n}","import type { OfflineKitBuildOptions } from \"../types\";\r\n\r\nconst DEFAULT_EXTS = [\r\n \"js\", \"css\", \"map\", \"ico\", \"png\", \"jpg\", \"jpeg\", \"webp\", \"svg\", \"gif\",\r\n \"woff2\", \"woff\", \"ttf\", \"eot\", \"json\", \"txt\", \"xml\", \"webmanifest\"\r\n];\r\n\r\nfunction jsString(v: unknown) {\r\n return JSON.stringify(v);\r\n}\r\n\r\nexport function buildServiceWorkerJS(options: Required<OfflineKitBuildOptions>) {\r\n const {\r\n cacheName,\r\n offlinePage,\r\n offlineImage,\r\n precache,\r\n htmlStrategy,\r\n assetStrategy,\r\n imageStrategy,\r\n assetExtensions,\r\n apiPrefixes,\r\n } = options;\r\n\r\n // precache already normalized in CLI; still keep it safe\r\n const precacheList = Array.from(\r\n new Set([offlinePage, offlineImage, ...(precache ?? [])].filter(Boolean))\r\n );\r\n\r\n return `/* offline-page-kit service worker */\r\nconst CACHE_NAME = ${jsString(cacheName)};\r\nconst OFFLINE_PAGE = ${jsString(offlinePage)};\r\nconst OFFLINE_IMAGE = ${jsString(offlineImage)};\r\nconst PRECACHE = ${jsString(precacheList)};\r\n\r\nconst HTML_STRATEGY = ${jsString(htmlStrategy)};\r\nconst ASSET_STRATEGY = ${jsString(assetStrategy)};\r\nconst IMAGE_STRATEGY = ${jsString(imageStrategy)};\r\n\r\nconst ASSET_EXTS = new Set(${jsString(assetExtensions.length ? assetExtensions : DEFAULT_EXTS)});\r\nconst API_PREFIXES = ${jsString(apiPrefixes || [])};\r\n\r\nconst SAME_ORIGIN_ONLY = true;\r\n\r\n// ---------------------------\r\n// helpers\r\n// ---------------------------\r\nconst isSameOrigin = (url) => url.origin === self.location.origin;\r\n\r\nconst isApi = (url) => {\r\n if (!API_PREFIXES || API_PREFIXES.length === 0) return false;\r\n return API_PREFIXES.some((p) => {\r\n try {\r\n // allow either absolute prefix or pathname prefix\r\n return url.href.startsWith(p) || url.pathname.startsWith(p);\r\n } catch {\r\n return false;\r\n }\r\n });\r\n};\r\n\r\nconst extOf = (pathname) => {\r\n const i = pathname.lastIndexOf(\".\");\r\n return i === -1 ? \"\" : pathname.slice(i + 1).toLowerCase();\r\n};\r\n\r\nconst isImageExt = (ext) => [\"png\",\"jpg\",\"jpeg\",\"webp\",\"gif\",\"svg\",\"ico\"].includes(ext);\r\n\r\n// Only cache successful, basic responses (avoid caching errors)\r\nconst isCacheableResponse = (res) => res && (res.ok || res.type === \"opaque\");\r\n\r\n// Cache helpers\r\nasync function cacheGet(reqOrUrl) {\r\n const cache = await caches.open(CACHE_NAME);\r\n return cache.match(reqOrUrl);\r\n}\r\n\r\nasync function cachePut(req, res) {\r\n try {\r\n if (!isCacheableResponse(res)) return;\r\n const cache = await caches.open(CACHE_NAME);\r\n await cache.put(req, res);\r\n } catch {\r\n // ignore quota / put errors\r\n }\r\n}\r\n\r\nasync function cleanOldCaches() {\r\n const keys = await caches.keys();\r\n await Promise.all(keys.map((k) => (k === CACHE_NAME ? null : caches.delete(k))));\r\n}\r\n\r\n// ---------------------------\r\n// strategies\r\n// ---------------------------\r\nasync function networkFirst(req, fallbackUrl) {\r\n try {\r\n const res = await fetch(req);\r\n await cachePut(req, res.clone());\r\n return res;\r\n } catch {\r\n const cached = await cacheGet(req);\r\n if (cached) return cached;\r\n if (fallbackUrl) {\r\n const fb = await cacheGet(fallbackUrl);\r\n if (fb) return fb;\r\n }\r\n return new Response(\"Offline\", { status: 503, headers: { \"content-type\": \"text/plain\" } });\r\n }\r\n}\r\n\r\nasync function cacheFirst(req, fallbackUrl) {\r\n const cached = await cacheGet(req);\r\n if (cached) return cached;\r\n\r\n try {\r\n const res = await fetch(req);\r\n await cachePut(req, res.clone());\r\n return res;\r\n } catch {\r\n if (fallbackUrl) {\r\n const fb = await cacheGet(fallbackUrl);\r\n if (fb) return fb;\r\n }\r\n return new Response(\"Offline\", { status: 503, headers: { \"content-type\": \"text/plain\" } });\r\n }\r\n}\r\n\r\nasync function staleWhileRevalidate(req) {\r\n const cached = await cacheGet(req);\r\n\r\n const fetchPromise = fetch(req)\r\n .then(async (res) => {\r\n await cachePut(req, res.clone());\r\n return res;\r\n })\r\n .catch(() => null);\r\n\r\n return cached || (await fetchPromise) || new Response(\"Offline\", { status: 503 });\r\n}\r\n\r\nfunction pickStrategy(name) {\r\n if (name === \"cacheFirst\") return cacheFirst;\r\n if (name === \"staleWhileRevalidate\") return staleWhileRevalidate;\r\n return networkFirst;\r\n}\r\n\r\n// ---------------------------\r\n// lifecycle\r\n// ---------------------------\r\nself.addEventListener(\"install\", (event) => {\r\n event.waitUntil((async () => {\r\n const cache = await caches.open(CACHE_NAME);\r\n\r\n // ✅ fail-safe install (never redundant from a single bad file)\r\n await Promise.allSettled(PRECACHE.map((u) => cache.add(u)));\r\n\r\n await self.skipWaiting();\r\n })());\r\n});\r\n\r\nself.addEventListener(\"activate\", (event) => {\r\n event.waitUntil((async () => {\r\n await cleanOldCaches();\r\n await self.clients.claim();\r\n })());\r\n});\r\n\r\n// Allow app to trigger update instantly:\r\n// navigator.serviceWorker.controller?.postMessage({ type: \"SKIP_WAITING\" })\r\nself.addEventListener(\"message\", (event) => {\r\n if (event?.data?.type === \"SKIP_WAITING\") {\r\n self.skipWaiting();\r\n }\r\n});\r\n\r\n// ---------------------------\r\n// fetch routing\r\n// ---------------------------\r\nself.addEventListener(\"fetch\", (event) => {\r\n const req = event.request;\r\n\r\n // Don't touch non-GET requests\r\n if (req.method && req.method !== \"GET\") return;\r\n\r\n const url = new URL(req.url);\r\n\r\n // Safe default: same-origin only\r\n if (SAME_ORIGIN_ONLY && !isSameOrigin(url)) return;\r\n\r\n const accept = req.headers.get(\"accept\") || \"\";\r\n const ext = extOf(url.pathname);\r\n\r\n // 1) HTML / navigations -> offline page fallback\r\n const isHtml = req.mode === \"navigate\" || accept.includes(\"text/html\");\r\n if (isHtml) {\r\n const fn = pickStrategy(HTML_STRATEGY);\r\n event.respondWith(fn(req, OFFLINE_PAGE));\r\n return;\r\n }\r\n\r\n // 2) API calls (network-first by default)\r\n if (isApi(url)) {\r\n event.respondWith(networkFirst(req));\r\n return;\r\n }\r\n\r\n // 3) Images (cache-first default) with OFFLINE_IMAGE fallback\r\n if (isImageExt(ext)) {\r\n const fn = pickStrategy(IMAGE_STRATEGY);\r\n event.respondWith(fn(req, OFFLINE_IMAGE));\r\n return;\r\n }\r\n\r\n // 4) Static assets (js/css/fonts/json/etc.)\r\n if (ASSET_EXTS.has(ext)) {\r\n const fn = pickStrategy(ASSET_STRATEGY);\r\n if (fn === staleWhileRevalidate) {\r\n event.respondWith(staleWhileRevalidate(req));\r\n } else {\r\n event.respondWith(fn(req));\r\n }\r\n return;\r\n }\r\n\r\n // Everything else: do nothing (network as normal)\r\n});\r\n`;\r\n}","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\n\r\ntype ScanOptions = {\r\n outDir: string; // absolute path to public\r\n extensions: string[];\r\n ignorePrefixes: string[]; // url-style (\"/_next\", \"/sw.js\")\r\n maxFiles: number;\r\n maxFileSizeKB: number;\r\n};\r\n\r\nfunction normalizeExts(exts: string[]) {\r\n return new Set(exts.map(e => e.replace(/^\\./, \"\").toLowerCase()));\r\n}\r\n\r\nfunction toUrlPath(outDirAbs: string, fileAbs: string) {\r\n // convert /abs/public/assets/a.css -> /assets/a.css\r\n const rel = path.relative(outDirAbs, fileAbs).split(path.sep).join(\"/\");\r\n return \"/\" + rel.replace(/^\\/+/, \"\");\r\n}\r\n\r\nfunction startsWithAny(urlPath: string, prefixes: string[]) {\r\n return prefixes.some(p => urlPath.startsWith(p));\r\n}\r\n\r\nexport function scanPrecacheFiles(opts: ScanOptions): string[] {\r\n const exts = normalizeExts(opts.extensions);\r\n const results: string[] = [];\r\n\r\n const walk = (dirAbs: string) => {\r\n if (results.length >= opts.maxFiles) return;\r\n\r\n const entries = fs.readdirSync(dirAbs, { withFileTypes: true });\r\n for (const ent of entries) {\r\n if (results.length >= opts.maxFiles) break;\r\n\r\n const abs = path.join(dirAbs, ent.name);\r\n\r\n if (ent.isDirectory()) {\r\n walk(abs);\r\n continue;\r\n }\r\n\r\n if (!ent.isFile()) continue;\r\n\r\n // file size limit\r\n try {\r\n const st = fs.statSync(abs);\r\n if (st.size > opts.maxFileSizeKB * 1024) continue;\r\n } catch {\r\n continue;\r\n }\r\n\r\n const urlPath = toUrlPath(opts.outDir, abs);\r\n\r\n // ignore list\r\n if (startsWithAny(urlPath, opts.ignorePrefixes)) continue;\r\n\r\n // extension check\r\n const dot = urlPath.lastIndexOf(\".\");\r\n const ext = dot === -1 ? \"\" : urlPath.slice(dot + 1).toLowerCase();\r\n if (!exts.has(ext)) continue;\r\n\r\n results.push(urlPath);\r\n }\r\n };\r\n\r\n walk(opts.outDir);\r\n return results;\r\n}"],"mappings":"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","names":["path","options","fs","path","path"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -14,9 +14,14 @@ type OfflineKitBuildOptions = {
|
|
|
14
14
|
offlineImage?: string;
|
|
15
15
|
cacheName?: string;
|
|
16
16
|
precache?: string[];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
autoPrecache?: boolean;
|
|
18
|
+
precacheExtensions?: string[];
|
|
19
|
+
precacheIgnore?: string[];
|
|
20
|
+
precacheMaxFiles?: number;
|
|
21
|
+
precacheMaxFileSizeKB?: number;
|
|
22
|
+
htmlStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
23
|
+
assetStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
24
|
+
imageStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
20
25
|
assetExtensions?: string[];
|
|
21
26
|
apiPrefixes?: string[];
|
|
22
27
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -14,9 +14,14 @@ type OfflineKitBuildOptions = {
|
|
|
14
14
|
offlineImage?: string;
|
|
15
15
|
cacheName?: string;
|
|
16
16
|
precache?: string[];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
autoPrecache?: boolean;
|
|
18
|
+
precacheExtensions?: string[];
|
|
19
|
+
precacheIgnore?: string[];
|
|
20
|
+
precacheMaxFiles?: number;
|
|
21
|
+
precacheMaxFileSizeKB?: number;
|
|
22
|
+
htmlStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
23
|
+
assetStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
24
|
+
imageStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
20
25
|
assetExtensions?: string[];
|
|
21
26
|
apiPrefixes?: string[];
|
|
22
27
|
};
|