offline-page-kit 0.2.1 → 0.2.2
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/LICENSE +0 -0
- package/README.md +264 -0
- package/dist/cli.cjs +34 -355
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +34 -355
- package/dist/cli.js.map +1 -1
- package/dist/index.d.cts +3 -8
- package/dist/index.d.ts +3 -8
- package/package.json +41 -5
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 path2 from "path";
|
|
5
5
|
|
|
6
6
|
// src/core/utils.ts
|
|
7
7
|
import fs from "fs";
|
|
@@ -82,171 +82,23 @@ function offlineSvgTemplate() {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// src/core/swTemplate.ts
|
|
85
|
-
var
|
|
86
|
-
"js",
|
|
87
|
-
"css",
|
|
88
|
-
"map",
|
|
89
|
-
"ico",
|
|
90
|
-
"png",
|
|
91
|
-
"jpg",
|
|
92
|
-
"jpeg",
|
|
93
|
-
"webp",
|
|
94
|
-
"svg",
|
|
95
|
-
"gif",
|
|
96
|
-
"woff2",
|
|
97
|
-
"woff",
|
|
98
|
-
"ttf",
|
|
99
|
-
"eot",
|
|
100
|
-
"json",
|
|
101
|
-
"txt",
|
|
102
|
-
"xml",
|
|
103
|
-
"webmanifest"
|
|
104
|
-
];
|
|
105
|
-
function jsString(v) {
|
|
106
|
-
return JSON.stringify(v);
|
|
107
|
-
}
|
|
85
|
+
var js = (v) => JSON.stringify(v);
|
|
108
86
|
function buildServiceWorkerJS(options2) {
|
|
109
|
-
const {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
htmlStrategy,
|
|
115
|
-
assetStrategy,
|
|
116
|
-
imageStrategy,
|
|
117
|
-
assetExtensions,
|
|
118
|
-
apiPrefixes
|
|
119
|
-
} = options2;
|
|
120
|
-
const precacheList = Array.from(
|
|
121
|
-
new Set([offlinePage, offlineImage, ...precache ?? []].filter(Boolean))
|
|
122
|
-
);
|
|
123
|
-
return `/* offline-page-kit service worker */
|
|
124
|
-
const CACHE_NAME = ${jsString(cacheName)};
|
|
125
|
-
const OFFLINE_PAGE = ${jsString(offlinePage)};
|
|
126
|
-
const OFFLINE_IMAGE = ${jsString(offlineImage)};
|
|
127
|
-
const PRECACHE = ${jsString(precacheList)};
|
|
128
|
-
|
|
129
|
-
const HTML_STRATEGY = ${jsString(htmlStrategy)};
|
|
130
|
-
const ASSET_STRATEGY = ${jsString(assetStrategy)};
|
|
131
|
-
const IMAGE_STRATEGY = ${jsString(imageStrategy)};
|
|
132
|
-
|
|
133
|
-
const ASSET_EXTS = new Set(${jsString(assetExtensions.length ? assetExtensions : DEFAULT_EXTS)});
|
|
134
|
-
const API_PREFIXES = ${jsString(apiPrefixes || [])};
|
|
135
|
-
|
|
136
|
-
const SAME_ORIGIN_ONLY = true;
|
|
137
|
-
|
|
138
|
-
// ---------------------------
|
|
139
|
-
// helpers
|
|
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
|
-
|
|
155
|
-
const extOf = (pathname) => {
|
|
156
|
-
const i = pathname.lastIndexOf(".");
|
|
157
|
-
return i === -1 ? "" : pathname.slice(i + 1).toLowerCase();
|
|
158
|
-
};
|
|
159
|
-
|
|
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) {
|
|
167
|
-
const cache = await caches.open(CACHE_NAME);
|
|
168
|
-
return cache.match(reqOrUrl);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async function cachePut(req, res) {
|
|
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
|
-
}
|
|
179
|
-
}
|
|
87
|
+
const { cacheName, offlinePage, offlineImage } = options2;
|
|
88
|
+
return `/* offline-page-kit service worker (minimal) */
|
|
89
|
+
const CACHE_NAME = ${js(cacheName)};
|
|
90
|
+
const OFFLINE_PAGE = ${js(offlinePage)};
|
|
91
|
+
const OFFLINE_IMAGE = ${js(offlineImage)};
|
|
180
92
|
|
|
181
|
-
async function cleanOldCaches() {
|
|
182
|
-
const keys = await caches.keys();
|
|
183
|
-
await Promise.all(keys.map((k) => (k === CACHE_NAME ? null : caches.delete(k))));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// ---------------------------
|
|
187
|
-
// strategies
|
|
188
|
-
// ---------------------------
|
|
189
|
-
async function networkFirst(req, fallbackUrl) {
|
|
190
|
-
try {
|
|
191
|
-
const res = await fetch(req);
|
|
192
|
-
await cachePut(req, res.clone());
|
|
193
|
-
return res;
|
|
194
|
-
} catch {
|
|
195
|
-
const cached = await cacheGet(req);
|
|
196
|
-
if (cached) return cached;
|
|
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" } });
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async function cacheFirst(req, fallbackUrl) {
|
|
206
|
-
const cached = await cacheGet(req);
|
|
207
|
-
if (cached) return cached;
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
const res = await fetch(req);
|
|
211
|
-
await cachePut(req, res.clone());
|
|
212
|
-
return res;
|
|
213
|
-
} catch {
|
|
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" } });
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async function staleWhileRevalidate(req) {
|
|
223
|
-
const cached = await cacheGet(req);
|
|
224
|
-
|
|
225
|
-
const fetchPromise = fetch(req)
|
|
226
|
-
.then(async (res) => {
|
|
227
|
-
await cachePut(req, res.clone());
|
|
228
|
-
return res;
|
|
229
|
-
})
|
|
230
|
-
.catch(() => null);
|
|
231
|
-
|
|
232
|
-
return cached || (await fetchPromise) || new Response("Offline", { status: 503 });
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function pickStrategy(name) {
|
|
236
|
-
if (name === "cacheFirst") return cacheFirst;
|
|
237
|
-
if (name === "staleWhileRevalidate") return staleWhileRevalidate;
|
|
238
|
-
return networkFirst;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ---------------------------
|
|
242
|
-
// lifecycle
|
|
243
|
-
// ---------------------------
|
|
244
93
|
self.addEventListener("install", (event) => {
|
|
245
94
|
event.waitUntil((async () => {
|
|
246
95
|
const cache = await caches.open(CACHE_NAME);
|
|
247
96
|
|
|
248
|
-
// \u2705
|
|
249
|
-
await Promise.allSettled(
|
|
97
|
+
// \u2705 Do not let one 404 kill the install
|
|
98
|
+
await Promise.allSettled([
|
|
99
|
+
cache.add(OFFLINE_PAGE),
|
|
100
|
+
cache.add(OFFLINE_IMAGE),
|
|
101
|
+
]);
|
|
250
102
|
|
|
251
103
|
await self.skipWaiting();
|
|
252
104
|
})());
|
|
@@ -254,130 +106,32 @@ self.addEventListener("install", (event) => {
|
|
|
254
106
|
|
|
255
107
|
self.addEventListener("activate", (event) => {
|
|
256
108
|
event.waitUntil((async () => {
|
|
257
|
-
await cleanOldCaches();
|
|
258
109
|
await self.clients.claim();
|
|
259
110
|
})());
|
|
260
111
|
});
|
|
261
112
|
|
|
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
|
-
// ---------------------------
|
|
273
113
|
self.addEventListener("fetch", (event) => {
|
|
274
114
|
const req = event.request;
|
|
275
115
|
|
|
276
|
-
//
|
|
277
|
-
if (req.
|
|
278
|
-
|
|
279
|
-
const url = new URL(req.url);
|
|
280
|
-
|
|
281
|
-
// Safe default: same-origin only
|
|
282
|
-
if (SAME_ORIGIN_ONLY && !isSameOrigin(url)) return;
|
|
283
|
-
|
|
284
|
-
const accept = req.headers.get("accept") || "";
|
|
285
|
-
const ext = extOf(url.pathname);
|
|
286
|
-
|
|
287
|
-
// 1) HTML / navigations -> offline page fallback
|
|
288
|
-
const isHtml = req.mode === "navigate" || accept.includes("text/html");
|
|
289
|
-
if (isHtml) {
|
|
290
|
-
const fn = pickStrategy(HTML_STRATEGY);
|
|
291
|
-
event.respondWith(fn(req, OFFLINE_PAGE));
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// 2) API calls (network-first by default)
|
|
296
|
-
if (isApi(url)) {
|
|
297
|
-
event.respondWith(networkFirst(req));
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// 3) Images (cache-first default) with OFFLINE_IMAGE fallback
|
|
302
|
-
if (isImageExt(ext)) {
|
|
303
|
-
const fn = pickStrategy(IMAGE_STRATEGY);
|
|
304
|
-
event.respondWith(fn(req, OFFLINE_IMAGE));
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// 4) Static assets (js/css/fonts/json/etc.)
|
|
309
|
-
if (ASSET_EXTS.has(ext)) {
|
|
310
|
-
const fn = pickStrategy(ASSET_STRATEGY);
|
|
311
|
-
if (fn === staleWhileRevalidate) {
|
|
312
|
-
event.respondWith(staleWhileRevalidate(req));
|
|
313
|
-
} else {
|
|
314
|
-
event.respondWith(fn(req));
|
|
315
|
-
}
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Everything else: do nothing (network as normal)
|
|
320
|
-
});
|
|
321
|
-
`;
|
|
322
|
-
}
|
|
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;
|
|
116
|
+
// \u2705 Offline fallback only for page navigations
|
|
117
|
+
if (req.mode === "navigate") {
|
|
118
|
+
event.respondWith((async () => {
|
|
351
119
|
try {
|
|
352
|
-
|
|
353
|
-
if (st.size > opts.maxFileSizeKB * 1024) continue;
|
|
120
|
+
return await fetch(req);
|
|
354
121
|
} catch {
|
|
355
|
-
|
|
122
|
+
const cache = await caches.open(CACHE_NAME);
|
|
123
|
+
return (await cache.match(OFFLINE_PAGE)) || new Response("Offline", { status: 503 });
|
|
356
124
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (!exts.has(ext)) continue;
|
|
362
|
-
results.push(urlPath);
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
walk(opts.outDir);
|
|
366
|
-
return results;
|
|
125
|
+
})());
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
`;
|
|
367
129
|
}
|
|
368
130
|
|
|
369
131
|
// src/cli.ts
|
|
370
132
|
function normalizePublicPath(p) {
|
|
371
|
-
if (!p) return "/";
|
|
372
|
-
return 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;
|
|
133
|
+
if (!p.startsWith("/")) return "/" + p;
|
|
134
|
+
return p;
|
|
381
135
|
}
|
|
382
136
|
function withDefaults(o) {
|
|
383
137
|
return {
|
|
@@ -391,69 +145,28 @@ function withDefaults(o) {
|
|
|
391
145
|
assetStrategy: o.assetStrategy ?? "staleWhileRevalidate",
|
|
392
146
|
imageStrategy: o.imageStrategy ?? "cacheFirst",
|
|
393
147
|
assetExtensions: o.assetExtensions ?? [],
|
|
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
|
|
148
|
+
apiPrefixes: o.apiPrefixes ?? []
|
|
423
149
|
};
|
|
424
150
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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");
|
|
151
|
+
var args = parseArgs(process.argv.slice(2));
|
|
152
|
+
var cmd = process.argv.slice(2).find((a) => !a.startsWith("--")) || "init";
|
|
153
|
+
var outDir = path2.resolve(process.cwd(), args.get("outDir") || "public");
|
|
436
154
|
var options = withDefaults({
|
|
437
155
|
outDir,
|
|
438
156
|
swFileName: args.get("swFileName") || "sw.js",
|
|
439
157
|
offlinePage: args.get("offlinePage") || "/offline.html",
|
|
440
158
|
offlineImage: args.get("offlineImage") || "/offline.svg",
|
|
441
159
|
cacheName: args.get("cacheName") || "offline-page-kit",
|
|
442
|
-
precache: splitList(args.get("precache"))
|
|
160
|
+
precache: splitList(args.get("precache")),
|
|
443
161
|
htmlStrategy: args.get("htmlStrategy") || "networkFirst",
|
|
444
162
|
assetStrategy: args.get("assetStrategy") || "staleWhileRevalidate",
|
|
445
163
|
imageStrategy: args.get("imageStrategy") || "cacheFirst",
|
|
446
164
|
assetExtensions: splitList(args.get("assetExtensions")),
|
|
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)
|
|
165
|
+
apiPrefixes: splitList(args.get("apiPrefixes"))
|
|
453
166
|
});
|
|
454
|
-
var swOut =
|
|
455
|
-
var offlineHtmlOut =
|
|
456
|
-
var offlineSvgOut =
|
|
167
|
+
var swOut = path2.join(outDir, options.swFileName);
|
|
168
|
+
var offlineHtmlOut = path2.join(outDir, options.offlinePage.replace(/^\//, ""));
|
|
169
|
+
var offlineSvgOut = path2.join(outDir, options.offlineImage.replace(/^\//, ""));
|
|
457
170
|
if (cmd === "init" || cmd === "build") {
|
|
458
171
|
if (cmd === "build" || !exists(offlineHtmlOut)) {
|
|
459
172
|
writeFileSafe(offlineHtmlOut, offlineHtmlTemplate());
|
|
@@ -461,46 +174,12 @@ if (cmd === "init" || cmd === "build") {
|
|
|
461
174
|
if (cmd === "build" || !exists(offlineSvgOut)) {
|
|
462
175
|
writeFileSafe(offlineSvgOut, offlineSvgTemplate());
|
|
463
176
|
}
|
|
464
|
-
|
|
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
|
-
});
|
|
177
|
+
const sw = buildServiceWorkerJS(options);
|
|
492
178
|
writeFileSafe(swOut, sw);
|
|
493
|
-
const manualCount = (options.precache || []).length;
|
|
494
|
-
const scannedCount = scanned.length;
|
|
495
179
|
console.log(`[offline-page-kit] Generated:
|
|
496
180
|
- ${swOut}
|
|
497
181
|
- ${offlineHtmlOut}
|
|
498
182
|
- ${offlineSvgOut}
|
|
499
|
-
|
|
500
|
-
Precache:
|
|
501
|
-
- manual: ${manualCount}
|
|
502
|
-
- scanned: ${scannedCount}
|
|
503
|
-
- total: ${allPrecache.length}
|
|
504
183
|
`);
|
|
505
184
|
} else {
|
|
506
185
|
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","../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"]}
|
|
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 } from \"../types\";\r\n\r\nconst js = (v: unknown) => JSON.stringify(v);\r\n\r\nexport function buildServiceWorkerJS(options: Required<OfflineKitBuildOptions>) {\r\n const { cacheName, offlinePage, offlineImage } = options;\r\n\r\n return `/* offline-page-kit service worker (minimal) */\r\nconst CACHE_NAME = ${js(cacheName)};\r\nconst OFFLINE_PAGE = ${js(offlinePage)};\r\nconst OFFLINE_IMAGE = ${js(offlineImage)};\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 // ✅ Do not let one 404 kill the install\r\n await Promise.allSettled([\r\n cache.add(OFFLINE_PAGE),\r\n cache.add(OFFLINE_IMAGE),\r\n ]);\r\n\r\n await self.skipWaiting();\r\n })());\r\n});\r\n\r\nself.addEventListener(\"activate\", (event) => {\r\n event.waitUntil((async () => {\r\n await self.clients.claim();\r\n })());\r\n});\r\n\r\nself.addEventListener(\"fetch\", (event) => {\r\n const req = event.request;\r\n\r\n // ✅ Offline fallback only for page navigations\r\n if (req.mode === \"navigate\") {\r\n event.respondWith((async () => {\r\n try {\r\n return await fetch(req);\r\n } catch {\r\n const cache = await caches.open(CACHE_NAME);\r\n return (await cache.match(OFFLINE_PAGE)) || new Response(\"Offline\", { status: 503 });\r\n }\r\n })());\r\n }\r\n});\r\n`;\r\n}"],"mappings":"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","names":["path","options","path"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -14,14 +14,9 @@ type OfflineKitBuildOptions = {
|
|
|
14
14
|
offlineImage?: string;
|
|
15
15
|
cacheName?: string;
|
|
16
16
|
precache?: string[];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
precacheMaxFiles?: number;
|
|
21
|
-
precacheMaxFileSizeKB?: number;
|
|
22
|
-
htmlStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
23
|
-
assetStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
24
|
-
imageStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
17
|
+
htmlStrategy?: Strategy;
|
|
18
|
+
assetStrategy?: Strategy;
|
|
19
|
+
imageStrategy?: Strategy;
|
|
25
20
|
assetExtensions?: string[];
|
|
26
21
|
apiPrefixes?: string[];
|
|
27
22
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -14,14 +14,9 @@ type OfflineKitBuildOptions = {
|
|
|
14
14
|
offlineImage?: string;
|
|
15
15
|
cacheName?: string;
|
|
16
16
|
precache?: string[];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
precacheMaxFiles?: number;
|
|
21
|
-
precacheMaxFileSizeKB?: number;
|
|
22
|
-
htmlStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
23
|
-
assetStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
24
|
-
imageStrategy?: "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
17
|
+
htmlStrategy?: Strategy;
|
|
18
|
+
assetStrategy?: Strategy;
|
|
19
|
+
imageStrategy?: Strategy;
|
|
25
20
|
assetExtensions?: string[];
|
|
26
21
|
apiPrefixes?: string[];
|
|
27
22
|
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "offline-page-kit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Framework-agnostic offline page + service worker generator (TypeScript)",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "MD Kuhel Ahmed",
|
|
8
|
+
"email": "kuhelahmed84@gmail.com",
|
|
9
|
+
"url": "https://devcrack.dev"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://devcrack.dev",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/YOUR_GITHUB_USERNAME/offline-page-kit.git"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/YOUR_GITHUB_USERNAME/offline-page-kit/issues"
|
|
18
|
+
},
|
|
19
|
+
"funding": {
|
|
20
|
+
"type": "individual",
|
|
21
|
+
"url": "https://www.youtube.com/@dev-crack"
|
|
22
|
+
},
|
|
6
23
|
"type": "module",
|
|
7
24
|
"main": "./dist/index.cjs",
|
|
8
25
|
"module": "./dist/index.js",
|
|
@@ -14,15 +31,34 @@
|
|
|
14
31
|
"require": "./dist/index.cjs"
|
|
15
32
|
}
|
|
16
33
|
},
|
|
17
|
-
"files": [
|
|
18
|
-
"dist"
|
|
19
|
-
],
|
|
20
34
|
"bin": {
|
|
21
35
|
"offline-page-kit": "./dist/cli.cjs"
|
|
22
36
|
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
42
|
+
"sideEffects": false,
|
|
43
|
+
"keywords": [
|
|
44
|
+
"offline",
|
|
45
|
+
"offline-page",
|
|
46
|
+
"service-worker",
|
|
47
|
+
"pwa",
|
|
48
|
+
"service worker",
|
|
49
|
+
"generator",
|
|
50
|
+
"typescript",
|
|
51
|
+
"nextjs",
|
|
52
|
+
"vite",
|
|
53
|
+
"framework-agnostic"
|
|
54
|
+
],
|
|
23
55
|
"scripts": {
|
|
24
|
-
"
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"dev": "tsup --watch",
|
|
58
|
+
"prepublishOnly": "npm run build"
|
|
25
59
|
},
|
|
26
60
|
"devDependencies": {
|
|
61
|
+
"tsup": "^8.0.0",
|
|
62
|
+
"typescript": "^5.0.0"
|
|
27
63
|
}
|
|
28
64
|
}
|