offline-page-kit 0.1.0
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 +334 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +311 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +70 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/package.json +28 -0
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
28
|
+
|
|
29
|
+
// src/core/utils.ts
|
|
30
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
31
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
32
|
+
function ensureDir(p) {
|
|
33
|
+
import_node_fs.default.mkdirSync(p, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
function writeFileSafe(filePath, content) {
|
|
36
|
+
ensureDir(import_node_path.default.dirname(filePath));
|
|
37
|
+
import_node_fs.default.writeFileSync(filePath, content, "utf8");
|
|
38
|
+
}
|
|
39
|
+
function exists(filePath) {
|
|
40
|
+
try {
|
|
41
|
+
import_node_fs.default.accessSync(filePath);
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function parseArgs(argv) {
|
|
48
|
+
const m = /* @__PURE__ */ new Map();
|
|
49
|
+
for (let i = 0; i < argv.length; i++) {
|
|
50
|
+
const a = argv[i];
|
|
51
|
+
if (!a.startsWith("--")) continue;
|
|
52
|
+
const key = a.slice(2);
|
|
53
|
+
const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
|
|
54
|
+
m.set(key, value);
|
|
55
|
+
}
|
|
56
|
+
return m;
|
|
57
|
+
}
|
|
58
|
+
function splitList(v) {
|
|
59
|
+
return (v || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/core/offlineHtml.ts
|
|
63
|
+
function offlineHtmlTemplate(title = "You're Offline") {
|
|
64
|
+
return `<!doctype html>
|
|
65
|
+
<html lang="en">
|
|
66
|
+
<head>
|
|
67
|
+
<meta charset="UTF-8"/>
|
|
68
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
69
|
+
<title>${title}</title>
|
|
70
|
+
<style>
|
|
71
|
+
body{margin:0;height:100vh;display:grid;place-items:center;font-family:system-ui,-apple-system,Segoe UI,Roboto;background:#0b0f19;color:#fff}
|
|
72
|
+
.box{max-width:560px;padding:28px 26px;border-radius:18px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.04)}
|
|
73
|
+
h1{margin:0 0 10px;font-size:28px}
|
|
74
|
+
p{margin:0 0 18px;opacity:.85;line-height:1.5}
|
|
75
|
+
.row{display:flex;gap:10px;flex-wrap:wrap}
|
|
76
|
+
button,a{appearance:none;border:0;border-radius:12px;padding:10px 14px;cursor:pointer;text-decoration:none}
|
|
77
|
+
button{background:#fff;color:#111}
|
|
78
|
+
a{background:rgba(255,255,255,.1);color:#fff}
|
|
79
|
+
</style>
|
|
80
|
+
</head>
|
|
81
|
+
<body>
|
|
82
|
+
<div class="box">
|
|
83
|
+
<h1>\u26A1 You\u2019re Offline</h1>
|
|
84
|
+
<p>No internet connection detected. You can retry, or go back to the homepage (if cached).</p>
|
|
85
|
+
<div class="row">
|
|
86
|
+
<button onclick="location.reload()">Retry</button>
|
|
87
|
+
<a href="/">Go Home</a>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</body>
|
|
91
|
+
</html>`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/core/offlineSvg.ts
|
|
95
|
+
function offlineSvgTemplate() {
|
|
96
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
|
|
97
|
+
<rect width="1200" height="630" fill="#0b0f19"/>
|
|
98
|
+
<text x="80" y="220" fill="#ffffff" font-size="64" font-family="system-ui, -apple-system, Segoe UI, Roboto">You\u2019re Offline</text>
|
|
99
|
+
<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>
|
|
100
|
+
<circle cx="1040" cy="220" r="90" fill="rgba(255,255,255,0.08)"/>
|
|
101
|
+
<path d="M980 220c40-40 120-40 160 0" stroke="#fff" stroke-width="10" fill="none" opacity="0.6"/>
|
|
102
|
+
<path d="M1010 250c25-25 75-25 100 0" stroke="#fff" stroke-width="10" fill="none" opacity="0.6"/>
|
|
103
|
+
<circle cx="1060" cy="290" r="10" fill="#fff" opacity="0.7"/>
|
|
104
|
+
</svg>`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/core/swTemplate.ts
|
|
108
|
+
var DEFAULT_EXTS = [
|
|
109
|
+
"js",
|
|
110
|
+
"css",
|
|
111
|
+
"map",
|
|
112
|
+
"ico",
|
|
113
|
+
"png",
|
|
114
|
+
"jpg",
|
|
115
|
+
"jpeg",
|
|
116
|
+
"webp",
|
|
117
|
+
"svg",
|
|
118
|
+
"gif",
|
|
119
|
+
"woff2",
|
|
120
|
+
"woff",
|
|
121
|
+
"ttf",
|
|
122
|
+
"eot",
|
|
123
|
+
"json",
|
|
124
|
+
"txt",
|
|
125
|
+
"xml",
|
|
126
|
+
"webmanifest"
|
|
127
|
+
];
|
|
128
|
+
function jsString(v) {
|
|
129
|
+
return JSON.stringify(v);
|
|
130
|
+
}
|
|
131
|
+
function buildServiceWorkerJS(options2) {
|
|
132
|
+
const {
|
|
133
|
+
cacheName,
|
|
134
|
+
offlinePage,
|
|
135
|
+
offlineImage,
|
|
136
|
+
precache,
|
|
137
|
+
htmlStrategy,
|
|
138
|
+
assetStrategy,
|
|
139
|
+
imageStrategy,
|
|
140
|
+
assetExtensions,
|
|
141
|
+
apiPrefixes
|
|
142
|
+
} = options2;
|
|
143
|
+
return `/* offline-page-kit service worker */
|
|
144
|
+
const CACHE_NAME = ${jsString(cacheName)};
|
|
145
|
+
const OFFLINE_PAGE = ${jsString(offlinePage)};
|
|
146
|
+
const OFFLINE_IMAGE = ${jsString(offlineImage)};
|
|
147
|
+
const PRECACHE = ${jsString([offlinePage, offlineImage, ...precache].filter(Boolean))};
|
|
148
|
+
|
|
149
|
+
const HTML_STRATEGY = ${jsString(htmlStrategy)};
|
|
150
|
+
const ASSET_STRATEGY = ${jsString(assetStrategy)};
|
|
151
|
+
const IMAGE_STRATEGY = ${jsString(imageStrategy)};
|
|
152
|
+
|
|
153
|
+
const ASSET_EXTS = new Set(${jsString(assetExtensions.length ? assetExtensions : DEFAULT_EXTS)});
|
|
154
|
+
const API_PREFIXES = ${jsString(apiPrefixes)};
|
|
155
|
+
|
|
156
|
+
// helpers
|
|
157
|
+
const isApi = (url) => API_PREFIXES.some(p => url.href.startsWith(p) || url.pathname.startsWith(p));
|
|
158
|
+
const extOf = (pathname) => {
|
|
159
|
+
const i = pathname.lastIndexOf(".");
|
|
160
|
+
return i === -1 ? "" : pathname.slice(i+1).toLowerCase();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
async function cacheGet(req) {
|
|
164
|
+
const cache = await caches.open(CACHE_NAME);
|
|
165
|
+
return cache.match(req);
|
|
166
|
+
}
|
|
167
|
+
async function cachePut(req, res) {
|
|
168
|
+
const cache = await caches.open(CACHE_NAME);
|
|
169
|
+
await cache.put(req, res);
|
|
170
|
+
}
|
|
171
|
+
async function cacheDeleteOld() {
|
|
172
|
+
const keys = await caches.keys();
|
|
173
|
+
await Promise.all(keys.map(k => (k === CACHE_NAME ? null : caches.delete(k))));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// strategies
|
|
177
|
+
async function networkFirst(req, fallbackUrl) {
|
|
178
|
+
try {
|
|
179
|
+
const res = await fetch(req);
|
|
180
|
+
if (res && res.ok) cachePut(req, res.clone());
|
|
181
|
+
return res;
|
|
182
|
+
} catch {
|
|
183
|
+
const cached = await cacheGet(req);
|
|
184
|
+
if (cached) return cached;
|
|
185
|
+
if (fallbackUrl) return cacheGet(fallbackUrl);
|
|
186
|
+
return new Response("Offline", { status: 503 });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function cacheFirst(req, fallbackUrl) {
|
|
191
|
+
const cached = await cacheGet(req);
|
|
192
|
+
if (cached) return cached;
|
|
193
|
+
try {
|
|
194
|
+
const res = await fetch(req);
|
|
195
|
+
if (res && res.ok) cachePut(req, res.clone());
|
|
196
|
+
return res;
|
|
197
|
+
} catch {
|
|
198
|
+
if (fallbackUrl) return cacheGet(fallbackUrl);
|
|
199
|
+
return new Response("Offline", { status: 503 });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function staleWhileRevalidate(req) {
|
|
204
|
+
const cached = await cacheGet(req);
|
|
205
|
+
const fetchPromise = fetch(req).then(res => {
|
|
206
|
+
if (res && res.ok) cachePut(req, res.clone());
|
|
207
|
+
return res;
|
|
208
|
+
}).catch(() => null);
|
|
209
|
+
|
|
210
|
+
return cached || (await fetchPromise) || new Response("Offline", { status: 503 });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function pickStrategy(name) {
|
|
214
|
+
if (name === "cacheFirst") return cacheFirst;
|
|
215
|
+
if (name === "staleWhileRevalidate") return staleWhileRevalidate;
|
|
216
|
+
return networkFirst;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
self.addEventListener("install", (event) => {
|
|
220
|
+
event.waitUntil((async () => {
|
|
221
|
+
const cache = await caches.open(CACHE_NAME);
|
|
222
|
+
await cache.addAll(PRECACHE);
|
|
223
|
+
self.skipWaiting();
|
|
224
|
+
})());
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
self.addEventListener("activate", (event) => {
|
|
228
|
+
event.waitUntil((async () => {
|
|
229
|
+
await cacheDeleteOld();
|
|
230
|
+
await self.clients.claim();
|
|
231
|
+
})());
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
self.addEventListener("fetch", (event) => {
|
|
235
|
+
const req = event.request;
|
|
236
|
+
const url = new URL(req.url);
|
|
237
|
+
|
|
238
|
+
// Only handle same-origin requests (safe default)
|
|
239
|
+
if (url.origin !== self.location.origin) return;
|
|
240
|
+
|
|
241
|
+
// HTML navigations (SPA, MPA, Next, etc.)
|
|
242
|
+
if (req.mode === "navigate" || (req.headers.get("accept") || "").includes("text/html")) {
|
|
243
|
+
const fn = pickStrategy(HTML_STRATEGY);
|
|
244
|
+
event.respondWith(fn(req, OFFLINE_PAGE));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// API calls (optional: you can also skip caching)
|
|
249
|
+
if (isApi(url)) {
|
|
250
|
+
// network-first is usually best for APIs
|
|
251
|
+
event.respondWith(networkFirst(req));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Images
|
|
256
|
+
const ext = extOf(url.pathname);
|
|
257
|
+
if (["png","jpg","jpeg","webp","gif","svg","ico"].includes(ext)) {
|
|
258
|
+
const fn = pickStrategy(IMAGE_STRATEGY);
|
|
259
|
+
event.respondWith(fn(req, OFFLINE_IMAGE));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Static assets (js/css/fonts/etc.)
|
|
264
|
+
if (ASSET_EXTS.has(ext)) {
|
|
265
|
+
const fn = pickStrategy(ASSET_STRATEGY);
|
|
266
|
+
// staleWhileRevalidate doesn't use fallback
|
|
267
|
+
event.respondWith(fn === staleWhileRevalidate ? staleWhileRevalidate(req) : fn(req));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/cli.ts
|
|
275
|
+
function normalizePublicPath(p) {
|
|
276
|
+
if (!p.startsWith("/")) return "/" + p;
|
|
277
|
+
return p;
|
|
278
|
+
}
|
|
279
|
+
function withDefaults(o) {
|
|
280
|
+
return {
|
|
281
|
+
outDir: o.outDir ?? "public",
|
|
282
|
+
swFileName: o.swFileName ?? "sw.js",
|
|
283
|
+
offlinePage: normalizePublicPath(o.offlinePage ?? "/offline.html"),
|
|
284
|
+
offlineImage: normalizePublicPath(o.offlineImage ?? "/offline.svg"),
|
|
285
|
+
cacheName: o.cacheName ?? "offline-page-kit",
|
|
286
|
+
precache: o.precache ?? [],
|
|
287
|
+
htmlStrategy: o.htmlStrategy ?? "networkFirst",
|
|
288
|
+
assetStrategy: o.assetStrategy ?? "staleWhileRevalidate",
|
|
289
|
+
imageStrategy: o.imageStrategy ?? "cacheFirst",
|
|
290
|
+
assetExtensions: o.assetExtensions ?? [],
|
|
291
|
+
apiPrefixes: o.apiPrefixes ?? []
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
var args = parseArgs(process.argv.slice(2));
|
|
295
|
+
var cmd = process.argv.slice(2).find((a) => !a.startsWith("--")) || "init";
|
|
296
|
+
var outDir = import_node_path2.default.resolve(process.cwd(), args.get("outDir") || "public");
|
|
297
|
+
var options = withDefaults({
|
|
298
|
+
outDir,
|
|
299
|
+
swFileName: args.get("swFileName") || "sw.js",
|
|
300
|
+
offlinePage: args.get("offlinePage") || "/offline.html",
|
|
301
|
+
offlineImage: args.get("offlineImage") || "/offline.svg",
|
|
302
|
+
cacheName: args.get("cacheName") || "offline-page-kit",
|
|
303
|
+
precache: splitList(args.get("precache")),
|
|
304
|
+
htmlStrategy: args.get("htmlStrategy") || "networkFirst",
|
|
305
|
+
assetStrategy: args.get("assetStrategy") || "staleWhileRevalidate",
|
|
306
|
+
imageStrategy: args.get("imageStrategy") || "cacheFirst",
|
|
307
|
+
assetExtensions: splitList(args.get("assetExtensions")),
|
|
308
|
+
apiPrefixes: splitList(args.get("apiPrefixes"))
|
|
309
|
+
});
|
|
310
|
+
var swOut = import_node_path2.default.join(outDir, options.swFileName);
|
|
311
|
+
var offlineHtmlOut = import_node_path2.default.join(outDir, options.offlinePage.replace(/^\//, ""));
|
|
312
|
+
var offlineSvgOut = import_node_path2.default.join(outDir, options.offlineImage.replace(/^\//, ""));
|
|
313
|
+
if (cmd === "init" || cmd === "build") {
|
|
314
|
+
if (cmd === "build" || !exists(offlineHtmlOut)) {
|
|
315
|
+
writeFileSafe(offlineHtmlOut, offlineHtmlTemplate());
|
|
316
|
+
}
|
|
317
|
+
if (cmd === "build" || !exists(offlineSvgOut)) {
|
|
318
|
+
writeFileSafe(offlineSvgOut, offlineSvgTemplate());
|
|
319
|
+
}
|
|
320
|
+
const sw = buildServiceWorkerJS(options);
|
|
321
|
+
writeFileSafe(swOut, sw);
|
|
322
|
+
console.log(`[offline-page-kit] Generated:
|
|
323
|
+
- ${swOut}
|
|
324
|
+
- ${offlineHtmlOut}
|
|
325
|
+
- ${offlineSvgOut}
|
|
326
|
+
`);
|
|
327
|
+
} else {
|
|
328
|
+
console.log(`[offline-page-kit] Unknown command: ${cmd}
|
|
329
|
+
Use:
|
|
330
|
+
offline-page-kit init --outDir public
|
|
331
|
+
offline-page-kit build --outDir public
|
|
332
|
+
`);
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +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"]}
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import path2 from "path";
|
|
5
|
+
|
|
6
|
+
// src/core/utils.ts
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
function ensureDir(p) {
|
|
10
|
+
fs.mkdirSync(p, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
function writeFileSafe(filePath, content) {
|
|
13
|
+
ensureDir(path.dirname(filePath));
|
|
14
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
15
|
+
}
|
|
16
|
+
function exists(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
fs.accessSync(filePath);
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function parseArgs(argv) {
|
|
25
|
+
const m = /* @__PURE__ */ new Map();
|
|
26
|
+
for (let i = 0; i < argv.length; i++) {
|
|
27
|
+
const a = argv[i];
|
|
28
|
+
if (!a.startsWith("--")) continue;
|
|
29
|
+
const key = a.slice(2);
|
|
30
|
+
const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
|
|
31
|
+
m.set(key, value);
|
|
32
|
+
}
|
|
33
|
+
return m;
|
|
34
|
+
}
|
|
35
|
+
function splitList(v) {
|
|
36
|
+
return (v || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/core/offlineHtml.ts
|
|
40
|
+
function offlineHtmlTemplate(title = "You're Offline") {
|
|
41
|
+
return `<!doctype html>
|
|
42
|
+
<html lang="en">
|
|
43
|
+
<head>
|
|
44
|
+
<meta charset="UTF-8"/>
|
|
45
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
46
|
+
<title>${title}</title>
|
|
47
|
+
<style>
|
|
48
|
+
body{margin:0;height:100vh;display:grid;place-items:center;font-family:system-ui,-apple-system,Segoe UI,Roboto;background:#0b0f19;color:#fff}
|
|
49
|
+
.box{max-width:560px;padding:28px 26px;border-radius:18px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.04)}
|
|
50
|
+
h1{margin:0 0 10px;font-size:28px}
|
|
51
|
+
p{margin:0 0 18px;opacity:.85;line-height:1.5}
|
|
52
|
+
.row{display:flex;gap:10px;flex-wrap:wrap}
|
|
53
|
+
button,a{appearance:none;border:0;border-radius:12px;padding:10px 14px;cursor:pointer;text-decoration:none}
|
|
54
|
+
button{background:#fff;color:#111}
|
|
55
|
+
a{background:rgba(255,255,255,.1);color:#fff}
|
|
56
|
+
</style>
|
|
57
|
+
</head>
|
|
58
|
+
<body>
|
|
59
|
+
<div class="box">
|
|
60
|
+
<h1>\u26A1 You\u2019re Offline</h1>
|
|
61
|
+
<p>No internet connection detected. You can retry, or go back to the homepage (if cached).</p>
|
|
62
|
+
<div class="row">
|
|
63
|
+
<button onclick="location.reload()">Retry</button>
|
|
64
|
+
<a href="/">Go Home</a>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</body>
|
|
68
|
+
</html>`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/core/offlineSvg.ts
|
|
72
|
+
function offlineSvgTemplate() {
|
|
73
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
|
|
74
|
+
<rect width="1200" height="630" fill="#0b0f19"/>
|
|
75
|
+
<text x="80" y="220" fill="#ffffff" font-size="64" font-family="system-ui, -apple-system, Segoe UI, Roboto">You\u2019re Offline</text>
|
|
76
|
+
<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>
|
|
77
|
+
<circle cx="1040" cy="220" r="90" fill="rgba(255,255,255,0.08)"/>
|
|
78
|
+
<path d="M980 220c40-40 120-40 160 0" stroke="#fff" stroke-width="10" fill="none" opacity="0.6"/>
|
|
79
|
+
<path d="M1010 250c25-25 75-25 100 0" stroke="#fff" stroke-width="10" fill="none" opacity="0.6"/>
|
|
80
|
+
<circle cx="1060" cy="290" r="10" fill="#fff" opacity="0.7"/>
|
|
81
|
+
</svg>`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/core/swTemplate.ts
|
|
85
|
+
var DEFAULT_EXTS = [
|
|
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
|
+
}
|
|
108
|
+
function buildServiceWorkerJS(options2) {
|
|
109
|
+
const {
|
|
110
|
+
cacheName,
|
|
111
|
+
offlinePage,
|
|
112
|
+
offlineImage,
|
|
113
|
+
precache,
|
|
114
|
+
htmlStrategy,
|
|
115
|
+
assetStrategy,
|
|
116
|
+
imageStrategy,
|
|
117
|
+
assetExtensions,
|
|
118
|
+
apiPrefixes
|
|
119
|
+
} = options2;
|
|
120
|
+
return `/* offline-page-kit service worker */
|
|
121
|
+
const CACHE_NAME = ${jsString(cacheName)};
|
|
122
|
+
const OFFLINE_PAGE = ${jsString(offlinePage)};
|
|
123
|
+
const OFFLINE_IMAGE = ${jsString(offlineImage)};
|
|
124
|
+
const PRECACHE = ${jsString([offlinePage, offlineImage, ...precache].filter(Boolean))};
|
|
125
|
+
|
|
126
|
+
const HTML_STRATEGY = ${jsString(htmlStrategy)};
|
|
127
|
+
const ASSET_STRATEGY = ${jsString(assetStrategy)};
|
|
128
|
+
const IMAGE_STRATEGY = ${jsString(imageStrategy)};
|
|
129
|
+
|
|
130
|
+
const ASSET_EXTS = new Set(${jsString(assetExtensions.length ? assetExtensions : DEFAULT_EXTS)});
|
|
131
|
+
const API_PREFIXES = ${jsString(apiPrefixes)};
|
|
132
|
+
|
|
133
|
+
// helpers
|
|
134
|
+
const isApi = (url) => API_PREFIXES.some(p => url.href.startsWith(p) || url.pathname.startsWith(p));
|
|
135
|
+
const extOf = (pathname) => {
|
|
136
|
+
const i = pathname.lastIndexOf(".");
|
|
137
|
+
return i === -1 ? "" : pathname.slice(i+1).toLowerCase();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
async function cacheGet(req) {
|
|
141
|
+
const cache = await caches.open(CACHE_NAME);
|
|
142
|
+
return cache.match(req);
|
|
143
|
+
}
|
|
144
|
+
async function cachePut(req, res) {
|
|
145
|
+
const cache = await caches.open(CACHE_NAME);
|
|
146
|
+
await cache.put(req, res);
|
|
147
|
+
}
|
|
148
|
+
async function cacheDeleteOld() {
|
|
149
|
+
const keys = await caches.keys();
|
|
150
|
+
await Promise.all(keys.map(k => (k === CACHE_NAME ? null : caches.delete(k))));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// strategies
|
|
154
|
+
async function networkFirst(req, fallbackUrl) {
|
|
155
|
+
try {
|
|
156
|
+
const res = await fetch(req);
|
|
157
|
+
if (res && res.ok) cachePut(req, res.clone());
|
|
158
|
+
return res;
|
|
159
|
+
} catch {
|
|
160
|
+
const cached = await cacheGet(req);
|
|
161
|
+
if (cached) return cached;
|
|
162
|
+
if (fallbackUrl) return cacheGet(fallbackUrl);
|
|
163
|
+
return new Response("Offline", { status: 503 });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function cacheFirst(req, fallbackUrl) {
|
|
168
|
+
const cached = await cacheGet(req);
|
|
169
|
+
if (cached) return cached;
|
|
170
|
+
try {
|
|
171
|
+
const res = await fetch(req);
|
|
172
|
+
if (res && res.ok) cachePut(req, res.clone());
|
|
173
|
+
return res;
|
|
174
|
+
} catch {
|
|
175
|
+
if (fallbackUrl) return cacheGet(fallbackUrl);
|
|
176
|
+
return new Response("Offline", { status: 503 });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function staleWhileRevalidate(req) {
|
|
181
|
+
const cached = await cacheGet(req);
|
|
182
|
+
const fetchPromise = fetch(req).then(res => {
|
|
183
|
+
if (res && res.ok) cachePut(req, res.clone());
|
|
184
|
+
return res;
|
|
185
|
+
}).catch(() => null);
|
|
186
|
+
|
|
187
|
+
return cached || (await fetchPromise) || new Response("Offline", { status: 503 });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function pickStrategy(name) {
|
|
191
|
+
if (name === "cacheFirst") return cacheFirst;
|
|
192
|
+
if (name === "staleWhileRevalidate") return staleWhileRevalidate;
|
|
193
|
+
return networkFirst;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
self.addEventListener("install", (event) => {
|
|
197
|
+
event.waitUntil((async () => {
|
|
198
|
+
const cache = await caches.open(CACHE_NAME);
|
|
199
|
+
await cache.addAll(PRECACHE);
|
|
200
|
+
self.skipWaiting();
|
|
201
|
+
})());
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
self.addEventListener("activate", (event) => {
|
|
205
|
+
event.waitUntil((async () => {
|
|
206
|
+
await cacheDeleteOld();
|
|
207
|
+
await self.clients.claim();
|
|
208
|
+
})());
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
self.addEventListener("fetch", (event) => {
|
|
212
|
+
const req = event.request;
|
|
213
|
+
const url = new URL(req.url);
|
|
214
|
+
|
|
215
|
+
// Only handle same-origin requests (safe default)
|
|
216
|
+
if (url.origin !== self.location.origin) return;
|
|
217
|
+
|
|
218
|
+
// HTML navigations (SPA, MPA, Next, etc.)
|
|
219
|
+
if (req.mode === "navigate" || (req.headers.get("accept") || "").includes("text/html")) {
|
|
220
|
+
const fn = pickStrategy(HTML_STRATEGY);
|
|
221
|
+
event.respondWith(fn(req, OFFLINE_PAGE));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// API calls (optional: you can also skip caching)
|
|
226
|
+
if (isApi(url)) {
|
|
227
|
+
// network-first is usually best for APIs
|
|
228
|
+
event.respondWith(networkFirst(req));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Images
|
|
233
|
+
const ext = extOf(url.pathname);
|
|
234
|
+
if (["png","jpg","jpeg","webp","gif","svg","ico"].includes(ext)) {
|
|
235
|
+
const fn = pickStrategy(IMAGE_STRATEGY);
|
|
236
|
+
event.respondWith(fn(req, OFFLINE_IMAGE));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Static assets (js/css/fonts/etc.)
|
|
241
|
+
if (ASSET_EXTS.has(ext)) {
|
|
242
|
+
const fn = pickStrategy(ASSET_STRATEGY);
|
|
243
|
+
// staleWhileRevalidate doesn't use fallback
|
|
244
|
+
event.respondWith(fn === staleWhileRevalidate ? staleWhileRevalidate(req) : fn(req));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/cli.ts
|
|
252
|
+
function normalizePublicPath(p) {
|
|
253
|
+
if (!p.startsWith("/")) return "/" + p;
|
|
254
|
+
return p;
|
|
255
|
+
}
|
|
256
|
+
function withDefaults(o) {
|
|
257
|
+
return {
|
|
258
|
+
outDir: o.outDir ?? "public",
|
|
259
|
+
swFileName: o.swFileName ?? "sw.js",
|
|
260
|
+
offlinePage: normalizePublicPath(o.offlinePage ?? "/offline.html"),
|
|
261
|
+
offlineImage: normalizePublicPath(o.offlineImage ?? "/offline.svg"),
|
|
262
|
+
cacheName: o.cacheName ?? "offline-page-kit",
|
|
263
|
+
precache: o.precache ?? [],
|
|
264
|
+
htmlStrategy: o.htmlStrategy ?? "networkFirst",
|
|
265
|
+
assetStrategy: o.assetStrategy ?? "staleWhileRevalidate",
|
|
266
|
+
imageStrategy: o.imageStrategy ?? "cacheFirst",
|
|
267
|
+
assetExtensions: o.assetExtensions ?? [],
|
|
268
|
+
apiPrefixes: o.apiPrefixes ?? []
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
var args = parseArgs(process.argv.slice(2));
|
|
272
|
+
var cmd = process.argv.slice(2).find((a) => !a.startsWith("--")) || "init";
|
|
273
|
+
var outDir = path2.resolve(process.cwd(), args.get("outDir") || "public");
|
|
274
|
+
var options = withDefaults({
|
|
275
|
+
outDir,
|
|
276
|
+
swFileName: args.get("swFileName") || "sw.js",
|
|
277
|
+
offlinePage: args.get("offlinePage") || "/offline.html",
|
|
278
|
+
offlineImage: args.get("offlineImage") || "/offline.svg",
|
|
279
|
+
cacheName: args.get("cacheName") || "offline-page-kit",
|
|
280
|
+
precache: splitList(args.get("precache")),
|
|
281
|
+
htmlStrategy: args.get("htmlStrategy") || "networkFirst",
|
|
282
|
+
assetStrategy: args.get("assetStrategy") || "staleWhileRevalidate",
|
|
283
|
+
imageStrategy: args.get("imageStrategy") || "cacheFirst",
|
|
284
|
+
assetExtensions: splitList(args.get("assetExtensions")),
|
|
285
|
+
apiPrefixes: splitList(args.get("apiPrefixes"))
|
|
286
|
+
});
|
|
287
|
+
var swOut = path2.join(outDir, options.swFileName);
|
|
288
|
+
var offlineHtmlOut = path2.join(outDir, options.offlinePage.replace(/^\//, ""));
|
|
289
|
+
var offlineSvgOut = path2.join(outDir, options.offlineImage.replace(/^\//, ""));
|
|
290
|
+
if (cmd === "init" || cmd === "build") {
|
|
291
|
+
if (cmd === "build" || !exists(offlineHtmlOut)) {
|
|
292
|
+
writeFileSafe(offlineHtmlOut, offlineHtmlTemplate());
|
|
293
|
+
}
|
|
294
|
+
if (cmd === "build" || !exists(offlineSvgOut)) {
|
|
295
|
+
writeFileSafe(offlineSvgOut, offlineSvgTemplate());
|
|
296
|
+
}
|
|
297
|
+
const sw = buildServiceWorkerJS(options);
|
|
298
|
+
writeFileSafe(swOut, sw);
|
|
299
|
+
console.log(`[offline-page-kit] Generated:
|
|
300
|
+
- ${swOut}
|
|
301
|
+
- ${offlineHtmlOut}
|
|
302
|
+
- ${offlineSvgOut}
|
|
303
|
+
`);
|
|
304
|
+
} else {
|
|
305
|
+
console.log(`[offline-page-kit] Unknown command: ${cmd}
|
|
306
|
+
Use:
|
|
307
|
+
offline-page-kit init --outDir public
|
|
308
|
+
offline-page-kit build --outDir public
|
|
309
|
+
`);
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +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"]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
registerOfflineKit: () => registerOfflineKit
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/register.ts
|
|
28
|
+
function registerOfflineKit(opts = {}) {
|
|
29
|
+
const {
|
|
30
|
+
swUrl = "/sw.js",
|
|
31
|
+
scope = "/",
|
|
32
|
+
debug = false,
|
|
33
|
+
onReady,
|
|
34
|
+
onUpdate,
|
|
35
|
+
onError
|
|
36
|
+
} = opts;
|
|
37
|
+
if (typeof window === "undefined") return;
|
|
38
|
+
if (!("serviceWorker" in navigator)) {
|
|
39
|
+
debug && console.warn("[offline-kit] Service Worker not supported");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
window.addEventListener("load", async () => {
|
|
43
|
+
try {
|
|
44
|
+
const reg = await navigator.serviceWorker.register(swUrl, { scope });
|
|
45
|
+
debug && console.log("[offline-kit] registered:", { swUrl, scope });
|
|
46
|
+
onReady?.(reg);
|
|
47
|
+
reg.addEventListener("updatefound", () => {
|
|
48
|
+
const installing = reg.installing;
|
|
49
|
+
if (!installing) return;
|
|
50
|
+
installing.addEventListener("statechange", () => {
|
|
51
|
+
if (installing.state !== "installed") return;
|
|
52
|
+
if (navigator.serviceWorker.controller) {
|
|
53
|
+
debug && console.log("[offline-kit] update available");
|
|
54
|
+
onUpdate?.(reg);
|
|
55
|
+
} else {
|
|
56
|
+
debug && console.log("[offline-kit] first install complete");
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
} catch (err) {
|
|
61
|
+
debug && console.error("[offline-kit] register failed:", err);
|
|
62
|
+
onError?.(err);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
67
|
+
0 && (module.exports = {
|
|
68
|
+
registerOfflineKit
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/register.ts"],"sourcesContent":["export { registerOfflineKit } from \"./register\";\r\nexport type { OfflineKitRegisterOptions, OfflineKitBuildOptions, Strategy } from \"./types\";","\r\nimport type { OfflineKitRegisterOptions } from \"./types\";\r\n\r\nexport function registerOfflineKit(opts: OfflineKitRegisterOptions = {}) {\r\n const {\r\n swUrl = \"/sw.js\",\r\n scope = \"/\",\r\n debug = false,\r\n onReady,\r\n onUpdate,\r\n onError,\r\n } = opts;\r\n\r\n if (typeof window === \"undefined\") return;\r\n if (!(\"serviceWorker\" in navigator)) {\r\n debug && console.warn(\"[offline-kit] Service Worker not supported\");\r\n return;\r\n }\r\n\r\n window.addEventListener(\"load\", async () => {\r\n try {\r\n const reg = await navigator.serviceWorker.register(swUrl, { scope });\r\n debug && console.log(\"[offline-kit] registered:\", { swUrl, scope });\r\n\r\n onReady?.(reg);\r\n\r\n reg.addEventListener(\"updatefound\", () => {\r\n const installing = reg.installing;\r\n if (!installing) return;\r\n\r\n installing.addEventListener(\"statechange\", () => {\r\n if (installing.state !== \"installed\") return;\r\n\r\n if (navigator.serviceWorker.controller) {\r\n debug && console.log(\"[offline-kit] update available\");\r\n onUpdate?.(reg);\r\n } else {\r\n debug && console.log(\"[offline-kit] first install complete\");\r\n }\r\n });\r\n });\r\n } catch (err) {\r\n debug && console.error(\"[offline-kit] register failed:\", err);\r\n onError?.(err);\r\n }\r\n });\r\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGO,SAAS,mBAAmB,OAAkC,CAAC,GAAG;AACrE,QAAM;AAAA,IACF,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,EACJ,IAAI;AAEJ,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI,EAAE,mBAAmB,YAAY;AACjC,aAAS,QAAQ,KAAK,4CAA4C;AAClE;AAAA,EACJ;AAEA,SAAO,iBAAiB,QAAQ,YAAY;AACxC,QAAI;AACA,YAAM,MAAM,MAAM,UAAU,cAAc,SAAS,OAAO,EAAE,MAAM,CAAC;AACnE,eAAS,QAAQ,IAAI,6BAA6B,EAAE,OAAO,MAAM,CAAC;AAElE,gBAAU,GAAG;AAEb,UAAI,iBAAiB,eAAe,MAAM;AACtC,cAAM,aAAa,IAAI;AACvB,YAAI,CAAC,WAAY;AAEjB,mBAAW,iBAAiB,eAAe,MAAM;AAC7C,cAAI,WAAW,UAAU,YAAa;AAEtC,cAAI,UAAU,cAAc,YAAY;AACpC,qBAAS,QAAQ,IAAI,gCAAgC;AACrD,uBAAW,GAAG;AAAA,UAClB,OAAO;AACH,qBAAS,QAAQ,IAAI,sCAAsC;AAAA,UAC/D;AAAA,QACJ,CAAC;AAAA,MACL,CAAC;AAAA,IACL,SAAS,KAAK;AACV,eAAS,QAAQ,MAAM,kCAAkC,GAAG;AAC5D,gBAAU,GAAG;AAAA,IACjB;AAAA,EACJ,CAAC;AACL;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type Strategy = "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
2
|
+
type OfflineKitRegisterOptions = {
|
|
3
|
+
swUrl?: string;
|
|
4
|
+
scope?: string;
|
|
5
|
+
debug?: boolean;
|
|
6
|
+
onReady?: (reg: ServiceWorkerRegistration) => void;
|
|
7
|
+
onUpdate?: (reg: ServiceWorkerRegistration) => void;
|
|
8
|
+
onError?: (err: unknown) => void;
|
|
9
|
+
};
|
|
10
|
+
type OfflineKitBuildOptions = {
|
|
11
|
+
outDir?: string;
|
|
12
|
+
swFileName?: string;
|
|
13
|
+
offlinePage?: string;
|
|
14
|
+
offlineImage?: string;
|
|
15
|
+
cacheName?: string;
|
|
16
|
+
precache?: string[];
|
|
17
|
+
htmlStrategy?: Strategy;
|
|
18
|
+
assetStrategy?: Strategy;
|
|
19
|
+
imageStrategy?: Strategy;
|
|
20
|
+
assetExtensions?: string[];
|
|
21
|
+
apiPrefixes?: string[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
declare function registerOfflineKit(opts?: OfflineKitRegisterOptions): void;
|
|
25
|
+
|
|
26
|
+
export { type OfflineKitBuildOptions, type OfflineKitRegisterOptions, type Strategy, registerOfflineKit };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type Strategy = "networkFirst" | "cacheFirst" | "staleWhileRevalidate";
|
|
2
|
+
type OfflineKitRegisterOptions = {
|
|
3
|
+
swUrl?: string;
|
|
4
|
+
scope?: string;
|
|
5
|
+
debug?: boolean;
|
|
6
|
+
onReady?: (reg: ServiceWorkerRegistration) => void;
|
|
7
|
+
onUpdate?: (reg: ServiceWorkerRegistration) => void;
|
|
8
|
+
onError?: (err: unknown) => void;
|
|
9
|
+
};
|
|
10
|
+
type OfflineKitBuildOptions = {
|
|
11
|
+
outDir?: string;
|
|
12
|
+
swFileName?: string;
|
|
13
|
+
offlinePage?: string;
|
|
14
|
+
offlineImage?: string;
|
|
15
|
+
cacheName?: string;
|
|
16
|
+
precache?: string[];
|
|
17
|
+
htmlStrategy?: Strategy;
|
|
18
|
+
assetStrategy?: Strategy;
|
|
19
|
+
imageStrategy?: Strategy;
|
|
20
|
+
assetExtensions?: string[];
|
|
21
|
+
apiPrefixes?: string[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
declare function registerOfflineKit(opts?: OfflineKitRegisterOptions): void;
|
|
25
|
+
|
|
26
|
+
export { type OfflineKitBuildOptions, type OfflineKitRegisterOptions, type Strategy, registerOfflineKit };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/register.ts
|
|
2
|
+
function registerOfflineKit(opts = {}) {
|
|
3
|
+
const {
|
|
4
|
+
swUrl = "/sw.js",
|
|
5
|
+
scope = "/",
|
|
6
|
+
debug = false,
|
|
7
|
+
onReady,
|
|
8
|
+
onUpdate,
|
|
9
|
+
onError
|
|
10
|
+
} = opts;
|
|
11
|
+
if (typeof window === "undefined") return;
|
|
12
|
+
if (!("serviceWorker" in navigator)) {
|
|
13
|
+
debug && console.warn("[offline-kit] Service Worker not supported");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
window.addEventListener("load", async () => {
|
|
17
|
+
try {
|
|
18
|
+
const reg = await navigator.serviceWorker.register(swUrl, { scope });
|
|
19
|
+
debug && console.log("[offline-kit] registered:", { swUrl, scope });
|
|
20
|
+
onReady?.(reg);
|
|
21
|
+
reg.addEventListener("updatefound", () => {
|
|
22
|
+
const installing = reg.installing;
|
|
23
|
+
if (!installing) return;
|
|
24
|
+
installing.addEventListener("statechange", () => {
|
|
25
|
+
if (installing.state !== "installed") return;
|
|
26
|
+
if (navigator.serviceWorker.controller) {
|
|
27
|
+
debug && console.log("[offline-kit] update available");
|
|
28
|
+
onUpdate?.(reg);
|
|
29
|
+
} else {
|
|
30
|
+
debug && console.log("[offline-kit] first install complete");
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
} catch (err) {
|
|
35
|
+
debug && console.error("[offline-kit] register failed:", err);
|
|
36
|
+
onError?.(err);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
registerOfflineKit
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/register.ts"],"sourcesContent":["\r\nimport type { OfflineKitRegisterOptions } from \"./types\";\r\n\r\nexport function registerOfflineKit(opts: OfflineKitRegisterOptions = {}) {\r\n const {\r\n swUrl = \"/sw.js\",\r\n scope = \"/\",\r\n debug = false,\r\n onReady,\r\n onUpdate,\r\n onError,\r\n } = opts;\r\n\r\n if (typeof window === \"undefined\") return;\r\n if (!(\"serviceWorker\" in navigator)) {\r\n debug && console.warn(\"[offline-kit] Service Worker not supported\");\r\n return;\r\n }\r\n\r\n window.addEventListener(\"load\", async () => {\r\n try {\r\n const reg = await navigator.serviceWorker.register(swUrl, { scope });\r\n debug && console.log(\"[offline-kit] registered:\", { swUrl, scope });\r\n\r\n onReady?.(reg);\r\n\r\n reg.addEventListener(\"updatefound\", () => {\r\n const installing = reg.installing;\r\n if (!installing) return;\r\n\r\n installing.addEventListener(\"statechange\", () => {\r\n if (installing.state !== \"installed\") return;\r\n\r\n if (navigator.serviceWorker.controller) {\r\n debug && console.log(\"[offline-kit] update available\");\r\n onUpdate?.(reg);\r\n } else {\r\n debug && console.log(\"[offline-kit] first install complete\");\r\n }\r\n });\r\n });\r\n } catch (err) {\r\n debug && console.error(\"[offline-kit] register failed:\", err);\r\n onError?.(err);\r\n }\r\n });\r\n}"],"mappings":";AAGO,SAAS,mBAAmB,OAAkC,CAAC,GAAG;AACrE,QAAM;AAAA,IACF,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,EACJ,IAAI;AAEJ,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI,EAAE,mBAAmB,YAAY;AACjC,aAAS,QAAQ,KAAK,4CAA4C;AAClE;AAAA,EACJ;AAEA,SAAO,iBAAiB,QAAQ,YAAY;AACxC,QAAI;AACA,YAAM,MAAM,MAAM,UAAU,cAAc,SAAS,OAAO,EAAE,MAAM,CAAC;AACnE,eAAS,QAAQ,IAAI,6BAA6B,EAAE,OAAO,MAAM,CAAC;AAElE,gBAAU,GAAG;AAEb,UAAI,iBAAiB,eAAe,MAAM;AACtC,cAAM,aAAa,IAAI;AACvB,YAAI,CAAC,WAAY;AAEjB,mBAAW,iBAAiB,eAAe,MAAM;AAC7C,cAAI,WAAW,UAAU,YAAa;AAEtC,cAAI,UAAU,cAAc,YAAY;AACpC,qBAAS,QAAQ,IAAI,gCAAgC;AACrD,uBAAW,GAAG;AAAA,UAClB,OAAO;AACH,qBAAS,QAAQ,IAAI,sCAAsC;AAAA,UAC/D;AAAA,QACJ,CAAC;AAAA,MACL,CAAC;AAAA,IACL,SAAS,KAAK;AACV,eAAS,QAAQ,MAAM,kCAAkC,GAAG;AAC5D,gBAAU,GAAG;AAAA,IACjB;AAAA,EACJ,CAAC;AACL;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "offline-page-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic offline page + service worker generator (TypeScript)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"bin": {
|
|
21
|
+
"offline-page-kit": "./dist/cli.cjs"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
}
|
|
28
|
+
}
|