offline-page-kit 0.1.0 → 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 +249 -48
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +249 -48
- 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,65 +261,146 @@ 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
|
-
|
|
223
|
-
|
|
270
|
+
|
|
271
|
+
// \u2705 fail-safe install (never redundant from a single bad file)
|
|
272
|
+
await Promise.allSettled(PRECACHE.map((u) => cache.add(u)));
|
|
273
|
+
|
|
274
|
+
await self.skipWaiting();
|
|
224
275
|
})());
|
|
225
276
|
});
|
|
226
277
|
|
|
227
278
|
self.addEventListener("activate", (event) => {
|
|
228
279
|
event.waitUntil((async () => {
|
|
229
|
-
await
|
|
280
|
+
await cleanOldCaches();
|
|
230
281
|
await self.clients.claim();
|
|
231
282
|
})());
|
|
232
283
|
});
|
|
233
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
|
+
// ---------------------------
|
|
234
296
|
self.addEventListener("fetch", (event) => {
|
|
235
297
|
const req = event.request;
|
|
298
|
+
|
|
299
|
+
// Don't touch non-GET requests
|
|
300
|
+
if (req.method && req.method !== "GET") return;
|
|
301
|
+
|
|
236
302
|
const url = new URL(req.url);
|
|
237
303
|
|
|
238
|
-
//
|
|
239
|
-
if (
|
|
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);
|
|
240
309
|
|
|
241
|
-
// HTML navigations
|
|
242
|
-
|
|
310
|
+
// 1) HTML / navigations -> offline page fallback
|
|
311
|
+
const isHtml = req.mode === "navigate" || accept.includes("text/html");
|
|
312
|
+
if (isHtml) {
|
|
243
313
|
const fn = pickStrategy(HTML_STRATEGY);
|
|
244
314
|
event.respondWith(fn(req, OFFLINE_PAGE));
|
|
245
315
|
return;
|
|
246
316
|
}
|
|
247
317
|
|
|
248
|
-
// API calls (
|
|
318
|
+
// 2) API calls (network-first by default)
|
|
249
319
|
if (isApi(url)) {
|
|
250
|
-
// network-first is usually best for APIs
|
|
251
320
|
event.respondWith(networkFirst(req));
|
|
252
321
|
return;
|
|
253
322
|
}
|
|
254
323
|
|
|
255
|
-
// Images
|
|
256
|
-
|
|
257
|
-
if (["png","jpg","jpeg","webp","gif","svg","ico"].includes(ext)) {
|
|
324
|
+
// 3) Images (cache-first default) with OFFLINE_IMAGE fallback
|
|
325
|
+
if (isImageExt(ext)) {
|
|
258
326
|
const fn = pickStrategy(IMAGE_STRATEGY);
|
|
259
327
|
event.respondWith(fn(req, OFFLINE_IMAGE));
|
|
260
328
|
return;
|
|
261
329
|
}
|
|
262
330
|
|
|
263
|
-
// Static assets (js/css/fonts/etc.)
|
|
331
|
+
// 4) Static assets (js/css/fonts/json/etc.)
|
|
264
332
|
if (ASSET_EXTS.has(ext)) {
|
|
265
333
|
const fn = pickStrategy(ASSET_STRATEGY);
|
|
266
|
-
|
|
267
|
-
|
|
334
|
+
if (fn === staleWhileRevalidate) {
|
|
335
|
+
event.respondWith(staleWhileRevalidate(req));
|
|
336
|
+
} else {
|
|
337
|
+
event.respondWith(fn(req));
|
|
338
|
+
}
|
|
268
339
|
return;
|
|
269
340
|
}
|
|
341
|
+
|
|
342
|
+
// Everything else: do nothing (network as normal)
|
|
270
343
|
});
|
|
271
344
|
`;
|
|
272
345
|
}
|
|
273
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
|
+
|
|
274
392
|
// src/cli.ts
|
|
275
393
|
function normalizePublicPath(p) {
|
|
276
|
-
if (!p
|
|
277
|
-
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;
|
|
278
404
|
}
|
|
279
405
|
function withDefaults(o) {
|
|
280
406
|
return {
|
|
@@ -288,28 +414,69 @@ function withDefaults(o) {
|
|
|
288
414
|
assetStrategy: o.assetStrategy ?? "staleWhileRevalidate",
|
|
289
415
|
imageStrategy: o.imageStrategy ?? "cacheFirst",
|
|
290
416
|
assetExtensions: o.assetExtensions ?? [],
|
|
291
|
-
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
|
|
292
446
|
};
|
|
293
447
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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");
|
|
297
459
|
var options = withDefaults({
|
|
298
460
|
outDir,
|
|
299
461
|
swFileName: args.get("swFileName") || "sw.js",
|
|
300
462
|
offlinePage: args.get("offlinePage") || "/offline.html",
|
|
301
463
|
offlineImage: args.get("offlineImage") || "/offline.svg",
|
|
302
464
|
cacheName: args.get("cacheName") || "offline-page-kit",
|
|
303
|
-
precache: splitList(args.get("precache")),
|
|
465
|
+
precache: splitList(args.get("precache")).map(normalizePublicPath),
|
|
304
466
|
htmlStrategy: args.get("htmlStrategy") || "networkFirst",
|
|
305
467
|
assetStrategy: args.get("assetStrategy") || "staleWhileRevalidate",
|
|
306
468
|
imageStrategy: args.get("imageStrategy") || "cacheFirst",
|
|
307
469
|
assetExtensions: splitList(args.get("assetExtensions")),
|
|
308
|
-
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)
|
|
309
476
|
});
|
|
310
|
-
var swOut =
|
|
311
|
-
var offlineHtmlOut =
|
|
312
|
-
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(/^\//, ""));
|
|
313
480
|
if (cmd === "init" || cmd === "build") {
|
|
314
481
|
if (cmd === "build" || !exists(offlineHtmlOut)) {
|
|
315
482
|
writeFileSafe(offlineHtmlOut, offlineHtmlTemplate());
|
|
@@ -317,12 +484,46 @@ if (cmd === "init" || cmd === "build") {
|
|
|
317
484
|
if (cmd === "build" || !exists(offlineSvgOut)) {
|
|
318
485
|
writeFileSafe(offlineSvgOut, offlineSvgTemplate());
|
|
319
486
|
}
|
|
320
|
-
|
|
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
|
+
});
|
|
321
515
|
writeFileSafe(swOut, sw);
|
|
516
|
+
const manualCount = (options.precache || []).length;
|
|
517
|
+
const scannedCount = scanned.length;
|
|
322
518
|
console.log(`[offline-page-kit] Generated:
|
|
323
519
|
- ${swOut}
|
|
324
520
|
- ${offlineHtmlOut}
|
|
325
521
|
- ${offlineSvgOut}
|
|
522
|
+
|
|
523
|
+
Precache:
|
|
524
|
+
- manual: ${manualCount}
|
|
525
|
+
- scanned: ${scannedCount}
|
|
526
|
+
- total: ${allPrecache.length}
|
|
326
527
|
`);
|
|
327
528
|
} else {
|
|
328
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 await cache.addAll(PRECACHE);\r\n self.skipWaiting();\r\n })());\r\n});\r\n\r\nself.addEventListener(\"activate\", (event) => {\r\n event.waitUntil((async () => {\r\n await cacheDeleteOld();\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 const url = new URL(req.url);\r\n\r\n // Only handle same-origin requests (safe default)\r\n if (url.origin !== self.location.origin) return;\r\n\r\n // HTML navigations (SPA, MPA, Next, etc.)\r\n if (req.mode === \"navigate\" || (req.headers.get(\"accept\") || \"\").includes(\"text/html\")) {\r\n const fn = pickStrategy(HTML_STRATEGY);\r\n event.respondWith(fn(req, OFFLINE_PAGE));\r\n return;\r\n }\r\n\r\n // API calls (optional: you can also skip caching)\r\n if (isApi(url)) {\r\n // network-first is usually best for APIs\r\n event.respondWith(networkFirst(req));\r\n return;\r\n }\r\n\r\n // Images\r\n const ext = extOf(url.pathname);\r\n if ([\"png\",\"jpg\",\"jpeg\",\"webp\",\"gif\",\"svg\",\"ico\"].includes(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 // Static assets (js/css/fonts/etc.)\r\n if (ASSET_EXTS.has(ext)) {\r\n const fn = pickStrategy(ASSET_STRATEGY);\r\n // staleWhileRevalidate doesn't use fallback\r\n event.respondWith(fn === staleWhileRevalidate ? staleWhileRevalidate(req) : fn(req));\r\n return;\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,65 +238,146 @@ 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
|
-
|
|
200
|
-
|
|
247
|
+
|
|
248
|
+
// \u2705 fail-safe install (never redundant from a single bad file)
|
|
249
|
+
await Promise.allSettled(PRECACHE.map((u) => cache.add(u)));
|
|
250
|
+
|
|
251
|
+
await self.skipWaiting();
|
|
201
252
|
})());
|
|
202
253
|
});
|
|
203
254
|
|
|
204
255
|
self.addEventListener("activate", (event) => {
|
|
205
256
|
event.waitUntil((async () => {
|
|
206
|
-
await
|
|
257
|
+
await cleanOldCaches();
|
|
207
258
|
await self.clients.claim();
|
|
208
259
|
})());
|
|
209
260
|
});
|
|
210
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
|
+
// ---------------------------
|
|
211
273
|
self.addEventListener("fetch", (event) => {
|
|
212
274
|
const req = event.request;
|
|
275
|
+
|
|
276
|
+
// Don't touch non-GET requests
|
|
277
|
+
if (req.method && req.method !== "GET") return;
|
|
278
|
+
|
|
213
279
|
const url = new URL(req.url);
|
|
214
280
|
|
|
215
|
-
//
|
|
216
|
-
if (
|
|
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);
|
|
217
286
|
|
|
218
|
-
// HTML navigations
|
|
219
|
-
|
|
287
|
+
// 1) HTML / navigations -> offline page fallback
|
|
288
|
+
const isHtml = req.mode === "navigate" || accept.includes("text/html");
|
|
289
|
+
if (isHtml) {
|
|
220
290
|
const fn = pickStrategy(HTML_STRATEGY);
|
|
221
291
|
event.respondWith(fn(req, OFFLINE_PAGE));
|
|
222
292
|
return;
|
|
223
293
|
}
|
|
224
294
|
|
|
225
|
-
// API calls (
|
|
295
|
+
// 2) API calls (network-first by default)
|
|
226
296
|
if (isApi(url)) {
|
|
227
|
-
// network-first is usually best for APIs
|
|
228
297
|
event.respondWith(networkFirst(req));
|
|
229
298
|
return;
|
|
230
299
|
}
|
|
231
300
|
|
|
232
|
-
// Images
|
|
233
|
-
|
|
234
|
-
if (["png","jpg","jpeg","webp","gif","svg","ico"].includes(ext)) {
|
|
301
|
+
// 3) Images (cache-first default) with OFFLINE_IMAGE fallback
|
|
302
|
+
if (isImageExt(ext)) {
|
|
235
303
|
const fn = pickStrategy(IMAGE_STRATEGY);
|
|
236
304
|
event.respondWith(fn(req, OFFLINE_IMAGE));
|
|
237
305
|
return;
|
|
238
306
|
}
|
|
239
307
|
|
|
240
|
-
// Static assets (js/css/fonts/etc.)
|
|
308
|
+
// 4) Static assets (js/css/fonts/json/etc.)
|
|
241
309
|
if (ASSET_EXTS.has(ext)) {
|
|
242
310
|
const fn = pickStrategy(ASSET_STRATEGY);
|
|
243
|
-
|
|
244
|
-
|
|
311
|
+
if (fn === staleWhileRevalidate) {
|
|
312
|
+
event.respondWith(staleWhileRevalidate(req));
|
|
313
|
+
} else {
|
|
314
|
+
event.respondWith(fn(req));
|
|
315
|
+
}
|
|
245
316
|
return;
|
|
246
317
|
}
|
|
318
|
+
|
|
319
|
+
// Everything else: do nothing (network as normal)
|
|
247
320
|
});
|
|
248
321
|
`;
|
|
249
322
|
}
|
|
250
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
|
+
|
|
251
369
|
// src/cli.ts
|
|
252
370
|
function normalizePublicPath(p) {
|
|
253
|
-
if (!p
|
|
254
|
-
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;
|
|
255
381
|
}
|
|
256
382
|
function withDefaults(o) {
|
|
257
383
|
return {
|
|
@@ -265,28 +391,69 @@ function withDefaults(o) {
|
|
|
265
391
|
assetStrategy: o.assetStrategy ?? "staleWhileRevalidate",
|
|
266
392
|
imageStrategy: o.imageStrategy ?? "cacheFirst",
|
|
267
393
|
assetExtensions: o.assetExtensions ?? [],
|
|
268
|
-
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
|
|
269
423
|
};
|
|
270
424
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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");
|
|
274
436
|
var options = withDefaults({
|
|
275
437
|
outDir,
|
|
276
438
|
swFileName: args.get("swFileName") || "sw.js",
|
|
277
439
|
offlinePage: args.get("offlinePage") || "/offline.html",
|
|
278
440
|
offlineImage: args.get("offlineImage") || "/offline.svg",
|
|
279
441
|
cacheName: args.get("cacheName") || "offline-page-kit",
|
|
280
|
-
precache: splitList(args.get("precache")),
|
|
442
|
+
precache: splitList(args.get("precache")).map(normalizePublicPath),
|
|
281
443
|
htmlStrategy: args.get("htmlStrategy") || "networkFirst",
|
|
282
444
|
assetStrategy: args.get("assetStrategy") || "staleWhileRevalidate",
|
|
283
445
|
imageStrategy: args.get("imageStrategy") || "cacheFirst",
|
|
284
446
|
assetExtensions: splitList(args.get("assetExtensions")),
|
|
285
|
-
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)
|
|
286
453
|
});
|
|
287
|
-
var swOut =
|
|
288
|
-
var offlineHtmlOut =
|
|
289
|
-
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(/^\//, ""));
|
|
290
457
|
if (cmd === "init" || cmd === "build") {
|
|
291
458
|
if (cmd === "build" || !exists(offlineHtmlOut)) {
|
|
292
459
|
writeFileSafe(offlineHtmlOut, offlineHtmlTemplate());
|
|
@@ -294,12 +461,46 @@ if (cmd === "init" || cmd === "build") {
|
|
|
294
461
|
if (cmd === "build" || !exists(offlineSvgOut)) {
|
|
295
462
|
writeFileSafe(offlineSvgOut, offlineSvgTemplate());
|
|
296
463
|
}
|
|
297
|
-
|
|
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
|
+
});
|
|
298
492
|
writeFileSafe(swOut, sw);
|
|
493
|
+
const manualCount = (options.precache || []).length;
|
|
494
|
+
const scannedCount = scanned.length;
|
|
299
495
|
console.log(`[offline-page-kit] Generated:
|
|
300
496
|
- ${swOut}
|
|
301
497
|
- ${offlineHtmlOut}
|
|
302
498
|
- ${offlineSvgOut}
|
|
499
|
+
|
|
500
|
+
Precache:
|
|
501
|
+
- manual: ${manualCount}
|
|
502
|
+
- scanned: ${scannedCount}
|
|
503
|
+
- total: ${allPrecache.length}
|
|
303
504
|
`);
|
|
304
505
|
} else {
|
|
305
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 await cache.addAll(PRECACHE);\r\n self.skipWaiting();\r\n })());\r\n});\r\n\r\nself.addEventListener(\"activate\", (event) => {\r\n event.waitUntil((async () => {\r\n await cacheDeleteOld();\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 const url = new URL(req.url);\r\n\r\n // Only handle same-origin requests (safe default)\r\n if (url.origin !== self.location.origin) return;\r\n\r\n // HTML navigations (SPA, MPA, Next, etc.)\r\n if (req.mode === \"navigate\" || (req.headers.get(\"accept\") || \"\").includes(\"text/html\")) {\r\n const fn = pickStrategy(HTML_STRATEGY);\r\n event.respondWith(fn(req, OFFLINE_PAGE));\r\n return;\r\n }\r\n\r\n // API calls (optional: you can also skip caching)\r\n if (isApi(url)) {\r\n // network-first is usually best for APIs\r\n event.respondWith(networkFirst(req));\r\n return;\r\n }\r\n\r\n // Images\r\n const ext = extOf(url.pathname);\r\n if ([\"png\",\"jpg\",\"jpeg\",\"webp\",\"gif\",\"svg\",\"ico\"].includes(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 // Static assets (js/css/fonts/etc.)\r\n if (ASSET_EXTS.has(ext)) {\r\n const fn = pickStrategy(ASSET_STRATEGY);\r\n // staleWhileRevalidate doesn't use fallback\r\n event.respondWith(fn === staleWhileRevalidate ? staleWhileRevalidate(req) : fn(req));\r\n return;\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
|
};
|