vite-sw-cacher-plugin 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,6 +35,7 @@ export default defineConfig({
35
35
  ttl: 24 * 60 * 60 * 1000,
36
36
  maxItemsCount: 200,
37
37
  cacheName: "vite-sw-cacher-plugin",
38
+ inlineSw: false,
38
39
  }),
39
40
  ],
40
41
  });
@@ -47,3 +48,4 @@ export default defineConfig({
47
48
  - `ttl?`: время жизни в мс, по умолчанию 24 часа.
48
49
  - `maxItemsCount?`: максимум кэша. По умолчанию `кол-во статичных файлов из Vite * 2`.
49
50
  - `cacheName?`: имя группы кешей, по умолчанию `vite-sw-cacher-plugin`.
51
+ - `inlineSw?`: вшивает SW в инлайн-скрипт через Blob URL (может быть ограничено CSP/браузером).
package/dist/index.cjs CHANGED
@@ -39,10 +39,10 @@ var import_terser = require("terser");
39
39
  var import_mime_types = require("mime-types");
40
40
 
41
41
  // src/templates/sw.ejs
42
- var sw_default = 'const CACHE_NAME = <%- cacheNameJson %>;\nconst MAX_AGE_MS = <%- ttlMs %>;\nconst MAX_ITEMS = <%- maxItems %>;\nconst EXTENSIONS = <%- extensionsJson %>;\nconst ALLOWED_CONTENT_TYPES = <%- allowedContentTypesJson %>;\nconst URL_PATTERN = <%- urlPatternSource %>;\n\nself.addEventListener("install", () => self.skipWaiting());\n\nself.addEventListener("activate", (event) => {\n event.waitUntil(cleanUpCache().then(() => self.clients.claim()));\n});\n\nself.addEventListener("fetch", (event) => {\n const { request } = event;\n if (!shouldHandleRequest(request)) return;\n\n event.respondWith(handleRequest(request));\n});\n\nconst shouldHandleRequest = (request) => {\n if (request.method !== "GET") return false;\n const url = request.url;\n return matchesPattern(url);\n};\n\nconst matchesPattern = (url) => {\n if (!URL_PATTERN) return true;\n return URL_PATTERN.test(url);\n};\n\nconst matchesExtension = (url) => {\n if (!EXTENSIONS.length) return true;\n try {\n const pathname = new URL(url).pathname.toLowerCase();\n return EXTENSIONS.some((ext) => pathname.endsWith(ext));\n } catch {\n return false;\n }\n};\n\nconst matchesContentType = (response) => {\n if (!ALLOWED_CONTENT_TYPES.length) return false;\n const header = response.headers.get("content-type");\n if (!header) return false;\n const contentType = header.split(";")[0]?.trim().toLowerCase();\n if (!contentType) return false;\n return ALLOWED_CONTENT_TYPES.includes(contentType);\n};\n\nconst handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n const shouldCache =\n matchesExtension(request.url) || matchesContentType(response);\n if (shouldCache) {\n await putInCache(request, response);\n }\n return response;\n }\n\n if (response && response.status === 404) {\n const cached = await caches.match(request);\n return cached || response;\n }\n\n return response;\n } catch (error) {\n const cached = await caches.match(request);\n if (cached) return cached;\n throw error;\n }\n};\n\nconst putInCache = async (request, response) => {\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set("sw-cache-time", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n\n await cache.put(request, responseToCache);\n limitCacheSize().catch(() => undefined);\n};\n\nconst cleanUpCache = async () => {\n if (MAX_AGE_MS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n const now = Date.now();\n\n for (const request of keys) {\n const response = await cache.match(request);\n if (!response) continue;\n\n const storedTime = response.headers.get("sw-cache-time");\n const dateHeader = response.headers.get("date");\n const timestamp = storedTime ? Number(storedTime) : dateHeader ? new Date(dateHeader).getTime() : NaN;\n\n if (!Number.isFinite(timestamp)) continue;\n if (now - timestamp > MAX_AGE_MS) {\n await cache.delete(request);\n }\n }\n};\n\nconst limitCacheSize = async () => {\n if (MAX_ITEMS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n\n if (keys.length <= MAX_ITEMS) return;\n const itemsToDelete = keys.slice(0, keys.length - MAX_ITEMS);\n for (const request of itemsToDelete) {\n await cache.delete(request);\n }\n};\n';
42
+ var sw_default = 'const CACHE_NAME = <%- cacheNameJson %>;\nconst MAX_AGE_MS = <%- ttlMs %>;\nconst MAX_ITEMS = <%- maxItems %>;\nconst EXTENSIONS = <%- extensionsJson %>;\nconst ALLOWED_CONTENT_TYPES = <%- allowedContentTypesJson %>;\nconst URL_PATTERN = <%- urlPatternSource %>;\nconst HTML_FALLBACK_KEY = "__sw-cacher-html-fallback__";\n\nself.addEventListener("install", () => self.skipWaiting());\n\nself.addEventListener("activate", (event) => {\n event.waitUntil(cleanUpCache().then(() => self.clients.claim()));\n});\n\nself.addEventListener("fetch", (event) => {\n const { request } = event;\n if (!shouldHandleRequest(request)) return;\n\n event.respondWith(handleRequest(request));\n});\n\nconst shouldHandleRequest = (request) => {\n if (request.method !== "GET") return false;\n const url = request.url;\n return matchesPattern(url);\n};\n\nconst matchesPattern = (url) => {\n if (!URL_PATTERN) return true;\n return URL_PATTERN.test(url);\n};\n\nconst matchesExtension = (url) => {\n if (!EXTENSIONS.length) return true;\n try {\n const pathname = new URL(url).pathname.toLowerCase();\n return EXTENSIONS.some((ext) => pathname.endsWith(ext));\n } catch {\n return false;\n }\n};\n\nconst matchesContentType = (response) => {\n if (!ALLOWED_CONTENT_TYPES.length) return false;\n const header = response.headers.get("content-type");\n if (!header) return false;\n const contentType = header.split(";")[0]?.trim().toLowerCase();\n if (!contentType) return false;\n return ALLOWED_CONTENT_TYPES.includes(contentType);\n};\n\nconst isNavigationRequest = (request) => {\n if (request.mode === "navigate") return true;\n const accept = request.headers.get("accept") || "";\n return accept.includes("text/html");\n};\n\nconst handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n const shouldCache =\n matchesExtension(request.url) || matchesContentType(response);\n if (shouldCache) {\n await putInCache(request, response);\n }\n await maybeStoreHtmlFallback(response);\n return response;\n }\n\n if (response && response.status === 404) {\n const cached = await caches.match(request);\n if (cached) return cached;\n if (isNavigationRequest(request)) {\n const fallback = await getHtmlFallback();\n if (fallback) return fallback;\n }\n return response;\n }\n\n return response;\n } catch (error) {\n const cached = await caches.match(request);\n if (cached) return cached;\n throw error;\n }\n};\n\nconst putInCache = async (request, response) => {\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set("sw-cache-time", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n\n await cache.put(request, responseToCache);\n limitCacheSize().catch(() => undefined);\n};\n\nconst maybeStoreHtmlFallback = async (response) => {\n if (!matchesContentType(response)) return;\n const header = response.headers.get("content-type") || "";\n const contentType = header.split(";")[0]?.trim().toLowerCase();\n if (contentType !== "text/html") return;\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set("sw-cache-time", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n await cache.put(HTML_FALLBACK_KEY, responseToCache);\n};\n\nconst getHtmlFallback = async () => {\n const cache = await caches.open(CACHE_NAME);\n return cache.match(HTML_FALLBACK_KEY);\n};\n\nconst cleanUpCache = async () => {\n if (MAX_AGE_MS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n const now = Date.now();\n\n for (const request of keys) {\n const response = await cache.match(request);\n if (!response) continue;\n\n const storedTime = response.headers.get("sw-cache-time");\n const dateHeader = response.headers.get("date");\n const timestamp = storedTime ? Number(storedTime) : dateHeader ? new Date(dateHeader).getTime() : NaN;\n\n if (!Number.isFinite(timestamp)) continue;\n if (now - timestamp > MAX_AGE_MS) {\n await cache.delete(request);\n }\n }\n};\n\nconst limitCacheSize = async () => {\n if (MAX_ITEMS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n\n if (keys.length <= MAX_ITEMS) return;\n const itemsToDelete = keys.slice(0, keys.length - MAX_ITEMS);\n for (const request of itemsToDelete) {\n await cache.delete(request);\n }\n};\n';
43
43
 
44
44
  // src/templates/inline-script.ejs
45
- var inline_script_default = '(() => {\n if (!("serviceWorker" in navigator)) return;\n window.addEventListener("load", () => {\n navigator.serviceWorker.register(<%- swUrlJson %>).catch((error) => {\n console.warn("[vite-sw-cacher-plugin] SW registration failed", error);\n });\n });\n})();\n';
45
+ var inline_script_default = '(() => {\n if (!("serviceWorker" in navigator)) return;\n window.addEventListener("load", () => {\n <% if (inlineSw) { %>\n const swCode = <%- swCodeJson %>;\n const blob = new Blob([swCode], { type: "text/javascript" });\n const swUrl = URL.createObjectURL(blob);\n navigator.serviceWorker\n .register(swUrl)\n .catch((error) => {\n console.warn("[vite-sw-cacher-plugin] SW registration failed", error);\n })\n .finally(() => {\n URL.revokeObjectURL(swUrl);\n });\n <% } else { %>\n navigator.serviceWorker\n .register(<%- swUrlJson %>)\n .catch((error) => {\n console.warn("[vite-sw-cacher-plugin] SW registration failed", error);\n });\n <% } %>\n });\n})();\n';
46
46
 
47
47
  // src/index.ts
48
48
  var DEFAULT_EXTENSIONS = [
@@ -90,6 +90,17 @@ var joinBase = (base, fileName) => {
90
90
  const normalizedBase = base.endsWith("/") ? base : `${base}/`;
91
91
  return `${normalizedBase}${fileName}`;
92
92
  };
93
+ var injectScriptIntoHtml = (html, script) => {
94
+ const tag = `<script>${script}</script>`;
95
+ if (/<\/head>/i.test(html)) {
96
+ return html.replace(/<\/head>/i, `${tag}</head>`);
97
+ }
98
+ return `${html}${tag}`;
99
+ };
100
+ var toHtmlString = (source) => {
101
+ if (typeof source === "string") return source;
102
+ return new TextDecoder().decode(source);
103
+ };
93
104
  var renderTemplate = (template, data) => import_ejs.default.render(template, data);
94
105
  var minifyScript = async (code) => {
95
106
  const result = await (0, import_terser.minify)(code, {
@@ -127,9 +138,11 @@ var viteSwCacherPlugin = (options = {}) => {
127
138
  resolvedConfig = config;
128
139
  },
129
140
  async transformIndexHtml(html) {
141
+ if (options.inlineSw) return html;
130
142
  const base = resolvedConfig?.base ?? "/";
131
143
  const swUrl = joinBase(base, swFileName);
132
144
  const inlineScript = renderTemplate(inline_script_default, {
145
+ inlineSw: false,
133
146
  swUrlJson: JSON.stringify(swUrl)
134
147
  });
135
148
  const minifiedInlineScript = await minifyScript(inlineScript);
@@ -150,6 +163,7 @@ var viteSwCacherPlugin = (options = {}) => {
150
163
  const ttlMs = options.ttl ?? DEFAULT_TTL_MS;
151
164
  const maxItems = options.maxItemsCount ?? staticCount * 2;
152
165
  const cacheName = options.cacheName ?? DEFAULT_CACHE_NAME;
166
+ const inlineSw = options.inlineSw ?? false;
153
167
  const swSource = await buildServiceWorkerSource({
154
168
  cacheName,
155
169
  ttlMs,
@@ -157,6 +171,20 @@ var viteSwCacherPlugin = (options = {}) => {
157
171
  extensions,
158
172
  pattern: options.pattern
159
173
  });
174
+ if (inlineSw) {
175
+ const inlineScript = renderTemplate(inline_script_default, {
176
+ inlineSw: true,
177
+ swCodeJson: JSON.stringify(swSource)
178
+ });
179
+ const minifiedInlineScript = await minifyScript(inlineScript);
180
+ for (const item of Object.values(bundle)) {
181
+ if (item.type !== "asset") continue;
182
+ if (!item.fileName.toLowerCase().endsWith(".html")) continue;
183
+ const html = toHtmlString(item.source);
184
+ item.source = injectScriptIntoHtml(html, minifiedInlineScript);
185
+ }
186
+ return;
187
+ }
160
188
  this.emitFile({
161
189
  type: "asset",
162
190
  fileName: swFileName,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/templates/sw.ejs","../src/templates/inline-script.ejs"],"sourcesContent":["import type { OutputAsset, OutputBundle, OutputChunk } from \"rollup\";\nimport type { Plugin, ResolvedConfig } from \"vite\";\nimport ejs from \"ejs\";\nimport { minify } from \"terser\";\nimport { lookup as lookupMimeType } from \"mime-types\";\nimport swTemplate from \"./templates/sw.ejs\";\nimport inlineScriptTemplate from \"./templates/inline-script.ejs\";\n\nexport interface ViteSwCacherPluginOptions {\n extensions?: string[];\n pattern?: string;\n ttl?: number;\n maxItemsCount?: number;\n cacheName?: string;\n}\n\nconst DEFAULT_EXTENSIONS = [\n \".html\",\n \".css\",\n \".js\",\n \".svg\",\n \".png\",\n \".jpeg\",\n \".jpg\",\n \".gif\",\n \".webp\",\n \".avif\",\n \".bmp\",\n \".ico\",\n \".tif\",\n \".tiff\",\n];\n\nconst DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;\nconst DEFAULT_CACHE_NAME = \"vite-sw-cacher-plugin\";\nconst DEFAULT_SW_FILE_NAME = \"sw-cacher.js\";\n\nconst normalizeExtensions = (extensions?: string[]): string[] => {\n const list = extensions?.length ? extensions : DEFAULT_EXTENSIONS;\n const normalized = list.map((ext) => {\n const trimmed = ext.trim();\n if (!trimmed) return \"\";\n return trimmed.startsWith(\".\") ? trimmed.toLowerCase() : `.${trimmed.toLowerCase()}`;\n });\n\n return Array.from(new Set(normalized.filter(Boolean)));\n};\n\nconst wildcardToRegexSource = (pattern: string): string => {\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return `^${escaped.replace(/\\*/g, \".*\")}$`;\n};\n\nconst countStaticOutputs = (bundle: OutputBundle, extensions: string[]): number => {\n if (!extensions.length) return 0;\n const normalized = extensions.map((ext) => ext.toLowerCase());\n const items = Object.values(bundle) as Array<OutputAsset | OutputChunk>;\n return items.filter((item) => {\n const name = item.fileName.toLowerCase();\n return normalized.some((ext) => name.endsWith(ext));\n }).length;\n};\n\nconst joinBase = (base: string, fileName: string): string => {\n const normalizedBase = base.endsWith(\"/\") ? base : `${base}/`;\n return `${normalizedBase}${fileName}`;\n};\n\nconst renderTemplate = (template: string, data: Record<string, unknown>): string =>\n ejs.render(template, data);\n\nconst minifyScript = async (code: string): Promise<string> => {\n const result = await minify(code, {\n compress: true,\n mangle: true,\n format: { comments: false },\n });\n return result.code ?? code;\n};\n\nconst buildServiceWorkerSource = async (options: {\n cacheName: string;\n ttlMs: number;\n maxItems: number;\n extensions: string[];\n pattern?: string;\n}): Promise<string> => {\n const patternSource = options.pattern ? wildcardToRegexSource(options.pattern) : null;\n const urlPatternSource = patternSource\n ? `new RegExp(${JSON.stringify(patternSource)})`\n : \"null\";\n const allowedContentTypes = Array.from(\n new Set(\n options.extensions\n .map((ext) => lookupMimeType(ext))\n .filter((value): value is string => Boolean(value))\n .map((value) => value.toLowerCase()),\n ),\n );\n\n const source = renderTemplate(swTemplate, {\n cacheNameJson: JSON.stringify(options.cacheName),\n ttlMs: Math.max(0, options.ttlMs),\n maxItems: options.maxItems,\n extensionsJson: JSON.stringify(options.extensions),\n allowedContentTypesJson: JSON.stringify(allowedContentTypes),\n urlPatternSource,\n });\n\n return minifyScript(source);\n};\n\nexport const viteSwCacherPlugin = (\n options: ViteSwCacherPluginOptions = {},\n): Plugin => {\n let resolvedConfig: ResolvedConfig | null = null;\n const swFileName = DEFAULT_SW_FILE_NAME;\n\n return {\n name: \"vite-sw-cacher-plugin\",\n apply: \"build\",\n configResolved(config) {\n resolvedConfig = config;\n },\n async transformIndexHtml(html) {\n const base = resolvedConfig?.base ?? \"/\";\n const swUrl = joinBase(base, swFileName);\n const inlineScript = renderTemplate(inlineScriptTemplate, {\n swUrlJson: JSON.stringify(swUrl),\n });\n const minifiedInlineScript = await minifyScript(inlineScript);\n\n return {\n html,\n tags: [\n {\n tag: \"script\",\n injectTo: \"head\",\n children: minifiedInlineScript,\n },\n ],\n };\n },\n async generateBundle(_, bundle) {\n const extensions = normalizeExtensions(options.extensions);\n const staticCount = countStaticOutputs(bundle, extensions);\n const ttlMs = options.ttl ?? DEFAULT_TTL_MS;\n const maxItems =\n options.maxItemsCount ?? staticCount * 2;\n const cacheName = options.cacheName ?? DEFAULT_CACHE_NAME;\n\n const swSource = await buildServiceWorkerSource({\n cacheName,\n ttlMs,\n maxItems,\n extensions,\n pattern: options.pattern,\n });\n\n this.emitFile({\n type: \"asset\",\n fileName: swFileName,\n source: swSource,\n });\n },\n };\n};\n\nexport default viteSwCacherPlugin;\n","const CACHE_NAME = <%- cacheNameJson %>;\nconst MAX_AGE_MS = <%- ttlMs %>;\nconst MAX_ITEMS = <%- maxItems %>;\nconst EXTENSIONS = <%- extensionsJson %>;\nconst ALLOWED_CONTENT_TYPES = <%- allowedContentTypesJson %>;\nconst URL_PATTERN = <%- urlPatternSource %>;\n\nself.addEventListener(\"install\", () => self.skipWaiting());\n\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(cleanUpCache().then(() => self.clients.claim()));\n});\n\nself.addEventListener(\"fetch\", (event) => {\n const { request } = event;\n if (!shouldHandleRequest(request)) return;\n\n event.respondWith(handleRequest(request));\n});\n\nconst shouldHandleRequest = (request) => {\n if (request.method !== \"GET\") return false;\n const url = request.url;\n return matchesPattern(url);\n};\n\nconst matchesPattern = (url) => {\n if (!URL_PATTERN) return true;\n return URL_PATTERN.test(url);\n};\n\nconst matchesExtension = (url) => {\n if (!EXTENSIONS.length) return true;\n try {\n const pathname = new URL(url).pathname.toLowerCase();\n return EXTENSIONS.some((ext) => pathname.endsWith(ext));\n } catch {\n return false;\n }\n};\n\nconst matchesContentType = (response) => {\n if (!ALLOWED_CONTENT_TYPES.length) return false;\n const header = response.headers.get(\"content-type\");\n if (!header) return false;\n const contentType = header.split(\";\")[0]?.trim().toLowerCase();\n if (!contentType) return false;\n return ALLOWED_CONTENT_TYPES.includes(contentType);\n};\n\nconst handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n const shouldCache =\n matchesExtension(request.url) || matchesContentType(response);\n if (shouldCache) {\n await putInCache(request, response);\n }\n return response;\n }\n\n if (response && response.status === 404) {\n const cached = await caches.match(request);\n return cached || response;\n }\n\n return response;\n } catch (error) {\n const cached = await caches.match(request);\n if (cached) return cached;\n throw error;\n }\n};\n\nconst putInCache = async (request, response) => {\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set(\"sw-cache-time\", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n\n await cache.put(request, responseToCache);\n limitCacheSize().catch(() => undefined);\n};\n\nconst cleanUpCache = async () => {\n if (MAX_AGE_MS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n const now = Date.now();\n\n for (const request of keys) {\n const response = await cache.match(request);\n if (!response) continue;\n\n const storedTime = response.headers.get(\"sw-cache-time\");\n const dateHeader = response.headers.get(\"date\");\n const timestamp = storedTime ? Number(storedTime) : dateHeader ? new Date(dateHeader).getTime() : NaN;\n\n if (!Number.isFinite(timestamp)) continue;\n if (now - timestamp > MAX_AGE_MS) {\n await cache.delete(request);\n }\n }\n};\n\nconst limitCacheSize = async () => {\n if (MAX_ITEMS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n\n if (keys.length <= MAX_ITEMS) return;\n const itemsToDelete = keys.slice(0, keys.length - MAX_ITEMS);\n for (const request of itemsToDelete) {\n await cache.delete(request);\n }\n};\n","(() => {\n if (!(\"serviceWorker\" in navigator)) return;\n window.addEventListener(\"load\", () => {\n navigator.serviceWorker.register(<%- swUrlJson %>).catch((error) => {\n console.warn(\"[vite-sw-cacher-plugin] SW registration failed\", error);\n });\n });\n})();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,iBAAgB;AAChB,oBAAuB;AACvB,wBAAyC;;;ACJzC;;;ACAA;;;AFgBA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,iBAAiB,KAAK,KAAK,KAAK;AACtC,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAE7B,IAAM,sBAAsB,CAAC,eAAoC;AAC/D,QAAM,OAAO,YAAY,SAAS,aAAa;AAC/C,QAAM,aAAa,KAAK,IAAI,CAAC,QAAQ;AACnC,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI,CAAC,QAAS,QAAO;AACrB,WAAO,QAAQ,WAAW,GAAG,IAAI,QAAQ,YAAY,IAAI,IAAI,QAAQ,YAAY,CAAC;AAAA,EACpF,CAAC;AAED,SAAO,MAAM,KAAK,IAAI,IAAI,WAAW,OAAO,OAAO,CAAC,CAAC;AACvD;AAEA,IAAM,wBAAwB,CAAC,YAA4B;AACzD,QAAM,UAAU,QAAQ,QAAQ,sBAAsB,MAAM;AAC5D,SAAO,IAAI,QAAQ,QAAQ,OAAO,IAAI,CAAC;AACzC;AAEA,IAAM,qBAAqB,CAAC,QAAsB,eAAiC;AACjF,MAAI,CAAC,WAAW,OAAQ,QAAO;AAC/B,QAAM,aAAa,WAAW,IAAI,CAAC,QAAQ,IAAI,YAAY,CAAC;AAC5D,QAAM,QAAQ,OAAO,OAAO,MAAM;AAClC,SAAO,MAAM,OAAO,CAAC,SAAS;AAC5B,UAAM,OAAO,KAAK,SAAS,YAAY;AACvC,WAAO,WAAW,KAAK,CAAC,QAAQ,KAAK,SAAS,GAAG,CAAC;AAAA,EACpD,CAAC,EAAE;AACL;AAEA,IAAM,WAAW,CAAC,MAAc,aAA6B;AAC3D,QAAM,iBAAiB,KAAK,SAAS,GAAG,IAAI,OAAO,GAAG,IAAI;AAC1D,SAAO,GAAG,cAAc,GAAG,QAAQ;AACrC;AAEA,IAAM,iBAAiB,CAAC,UAAkB,SACxC,WAAAA,QAAI,OAAO,UAAU,IAAI;AAE3B,IAAM,eAAe,OAAO,SAAkC;AAC5D,QAAM,SAAS,UAAM,sBAAO,MAAM;AAAA,IAChC,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,QAAQ,EAAE,UAAU,MAAM;AAAA,EAC5B,CAAC;AACD,SAAO,OAAO,QAAQ;AACxB;AAEA,IAAM,2BAA2B,OAAO,YAMjB;AACrB,QAAM,gBAAgB,QAAQ,UAAU,sBAAsB,QAAQ,OAAO,IAAI;AACjF,QAAM,mBAAmB,gBACrB,cAAc,KAAK,UAAU,aAAa,CAAC,MAC3C;AACJ,QAAM,sBAAsB,MAAM;AAAA,IAChC,IAAI;AAAA,MACF,QAAQ,WACL,IAAI,CAAC,YAAQ,kBAAAC,QAAe,GAAG,CAAC,EAChC,OAAO,CAAC,UAA2B,QAAQ,KAAK,CAAC,EACjD,IAAI,CAAC,UAAU,MAAM,YAAY,CAAC;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,SAAS,eAAe,YAAY;AAAA,IACxC,eAAe,KAAK,UAAU,QAAQ,SAAS;AAAA,IAC/C,OAAO,KAAK,IAAI,GAAG,QAAQ,KAAK;AAAA,IAChC,UAAU,QAAQ;AAAA,IAClB,gBAAgB,KAAK,UAAU,QAAQ,UAAU;AAAA,IACjD,yBAAyB,KAAK,UAAU,mBAAmB;AAAA,IAC3D;AAAA,EACF,CAAC;AAED,SAAO,aAAa,MAAM;AAC5B;AAEO,IAAM,qBAAqB,CAChC,UAAqC,CAAC,MAC3B;AACX,MAAI,iBAAwC;AAC5C,QAAM,aAAa;AAEnB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,eAAe,QAAQ;AACrB,uBAAiB;AAAA,IACnB;AAAA,IACA,MAAM,mBAAmB,MAAM;AAC7B,YAAM,OAAO,gBAAgB,QAAQ;AACrC,YAAM,QAAQ,SAAS,MAAM,UAAU;AACvC,YAAM,eAAe,eAAe,uBAAsB;AAAA,QACxD,WAAW,KAAK,UAAU,KAAK;AAAA,MACjC,CAAC;AACD,YAAM,uBAAuB,MAAM,aAAa,YAAY;AAE5D,aAAO;AAAA,QACL;AAAA,QACA,MAAM;AAAA,UACJ;AAAA,YACE,KAAK;AAAA,YACL,UAAU;AAAA,YACV,UAAU;AAAA,UACZ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,MAAM,eAAe,GAAG,QAAQ;AAC9B,YAAM,aAAa,oBAAoB,QAAQ,UAAU;AACzD,YAAM,cAAc,mBAAmB,QAAQ,UAAU;AACzD,YAAM,QAAQ,QAAQ,OAAO;AAC7B,YAAM,WACJ,QAAQ,iBAAiB,cAAc;AACzC,YAAM,YAAY,QAAQ,aAAa;AAEvC,YAAM,WAAW,MAAM,yBAAyB;AAAA,QAC9C;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,QAAQ;AAAA,MACnB,CAAC;AAED,WAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;","names":["ejs","lookupMimeType"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/templates/sw.ejs","../src/templates/inline-script.ejs"],"sourcesContent":["import type { OutputAsset, OutputBundle, OutputChunk } from \"rollup\";\nimport type { Plugin, ResolvedConfig } from \"vite\";\nimport ejs from \"ejs\";\nimport { minify } from \"terser\";\nimport { lookup as lookupMimeType } from \"mime-types\";\nimport swTemplate from \"./templates/sw.ejs\";\nimport inlineScriptTemplate from \"./templates/inline-script.ejs\";\n\nexport interface ViteSwCacherPluginOptions {\n extensions?: string[];\n pattern?: string;\n ttl?: number;\n maxItemsCount?: number;\n cacheName?: string;\n inlineSw?: boolean;\n}\n\nconst DEFAULT_EXTENSIONS = [\n \".html\",\n \".css\",\n \".js\",\n \".svg\",\n \".png\",\n \".jpeg\",\n \".jpg\",\n \".gif\",\n \".webp\",\n \".avif\",\n \".bmp\",\n \".ico\",\n \".tif\",\n \".tiff\",\n];\n\nconst DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;\nconst DEFAULT_CACHE_NAME = \"vite-sw-cacher-plugin\";\nconst DEFAULT_SW_FILE_NAME = \"sw-cacher.js\";\n\nconst normalizeExtensions = (extensions?: string[]): string[] => {\n const list = extensions?.length ? extensions : DEFAULT_EXTENSIONS;\n const normalized = list.map((ext) => {\n const trimmed = ext.trim();\n if (!trimmed) return \"\";\n return trimmed.startsWith(\".\") ? trimmed.toLowerCase() : `.${trimmed.toLowerCase()}`;\n });\n\n return Array.from(new Set(normalized.filter(Boolean)));\n};\n\nconst wildcardToRegexSource = (pattern: string): string => {\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return `^${escaped.replace(/\\*/g, \".*\")}$`;\n};\n\nconst countStaticOutputs = (bundle: OutputBundle, extensions: string[]): number => {\n if (!extensions.length) return 0;\n const normalized = extensions.map((ext) => ext.toLowerCase());\n const items = Object.values(bundle) as Array<OutputAsset | OutputChunk>;\n return items.filter((item) => {\n const name = item.fileName.toLowerCase();\n return normalized.some((ext) => name.endsWith(ext));\n }).length;\n};\n\nconst joinBase = (base: string, fileName: string): string => {\n const normalizedBase = base.endsWith(\"/\") ? base : `${base}/`;\n return `${normalizedBase}${fileName}`;\n};\n\nconst injectScriptIntoHtml = (html: string, script: string): string => {\n const tag = `<script>${script}</script>`;\n if (/<\\/head>/i.test(html)) {\n return html.replace(/<\\/head>/i, `${tag}</head>`);\n }\n return `${html}${tag}`;\n};\n\nconst toHtmlString = (source: OutputAsset[\"source\"]): string => {\n if (typeof source === \"string\") return source;\n return new TextDecoder().decode(source);\n};\n\nconst renderTemplate = (template: string, data: Record<string, unknown>): string =>\n ejs.render(template, data);\n\nconst minifyScript = async (code: string): Promise<string> => {\n const result = await minify(code, {\n compress: true,\n mangle: true,\n format: { comments: false },\n });\n return result.code ?? code;\n};\n\nconst buildServiceWorkerSource = async (options: {\n cacheName: string;\n ttlMs: number;\n maxItems: number;\n extensions: string[];\n pattern?: string;\n}): Promise<string> => {\n const patternSource = options.pattern ? wildcardToRegexSource(options.pattern) : null;\n const urlPatternSource = patternSource\n ? `new RegExp(${JSON.stringify(patternSource)})`\n : \"null\";\n const allowedContentTypes = Array.from(\n new Set(\n options.extensions\n .map((ext) => lookupMimeType(ext))\n .filter((value): value is string => Boolean(value))\n .map((value) => value.toLowerCase()),\n ),\n );\n\n const source = renderTemplate(swTemplate, {\n cacheNameJson: JSON.stringify(options.cacheName),\n ttlMs: Math.max(0, options.ttlMs),\n maxItems: options.maxItems,\n extensionsJson: JSON.stringify(options.extensions),\n allowedContentTypesJson: JSON.stringify(allowedContentTypes),\n urlPatternSource,\n });\n\n return minifyScript(source);\n};\n\nexport const viteSwCacherPlugin = (\n options: ViteSwCacherPluginOptions = {},\n): Plugin => {\n let resolvedConfig: ResolvedConfig | null = null;\n const swFileName = DEFAULT_SW_FILE_NAME;\n\n return {\n name: \"vite-sw-cacher-plugin\",\n apply: \"build\",\n configResolved(config) {\n resolvedConfig = config;\n },\n async transformIndexHtml(html) {\n if (options.inlineSw) return html;\n const base = resolvedConfig?.base ?? \"/\";\n const swUrl = joinBase(base, swFileName);\n const inlineScript = renderTemplate(inlineScriptTemplate, {\n inlineSw: false,\n swUrlJson: JSON.stringify(swUrl),\n });\n const minifiedInlineScript = await minifyScript(inlineScript);\n\n return {\n html,\n tags: [\n {\n tag: \"script\",\n injectTo: \"head\",\n children: minifiedInlineScript,\n },\n ],\n };\n },\n async generateBundle(_, bundle) {\n const extensions = normalizeExtensions(options.extensions);\n const staticCount = countStaticOutputs(bundle, extensions);\n const ttlMs = options.ttl ?? DEFAULT_TTL_MS;\n const maxItems =\n options.maxItemsCount ?? staticCount * 2;\n const cacheName = options.cacheName ?? DEFAULT_CACHE_NAME;\n const inlineSw = options.inlineSw ?? false;\n\n const swSource = await buildServiceWorkerSource({\n cacheName,\n ttlMs,\n maxItems,\n extensions,\n pattern: options.pattern,\n });\n\n if (inlineSw) {\n const inlineScript = renderTemplate(inlineScriptTemplate, {\n inlineSw: true,\n swCodeJson: JSON.stringify(swSource),\n });\n const minifiedInlineScript = await minifyScript(inlineScript);\n\n for (const item of Object.values(bundle)) {\n if (item.type !== \"asset\") continue;\n if (!item.fileName.toLowerCase().endsWith(\".html\")) continue;\n const html = toHtmlString(item.source);\n item.source = injectScriptIntoHtml(html, minifiedInlineScript);\n }\n return;\n }\n\n this.emitFile({\n type: \"asset\",\n fileName: swFileName,\n source: swSource,\n });\n },\n };\n};\n\nexport default viteSwCacherPlugin;\n","const CACHE_NAME = <%- cacheNameJson %>;\nconst MAX_AGE_MS = <%- ttlMs %>;\nconst MAX_ITEMS = <%- maxItems %>;\nconst EXTENSIONS = <%- extensionsJson %>;\nconst ALLOWED_CONTENT_TYPES = <%- allowedContentTypesJson %>;\nconst URL_PATTERN = <%- urlPatternSource %>;\nconst HTML_FALLBACK_KEY = \"__sw-cacher-html-fallback__\";\n\nself.addEventListener(\"install\", () => self.skipWaiting());\n\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(cleanUpCache().then(() => self.clients.claim()));\n});\n\nself.addEventListener(\"fetch\", (event) => {\n const { request } = event;\n if (!shouldHandleRequest(request)) return;\n\n event.respondWith(handleRequest(request));\n});\n\nconst shouldHandleRequest = (request) => {\n if (request.method !== \"GET\") return false;\n const url = request.url;\n return matchesPattern(url);\n};\n\nconst matchesPattern = (url) => {\n if (!URL_PATTERN) return true;\n return URL_PATTERN.test(url);\n};\n\nconst matchesExtension = (url) => {\n if (!EXTENSIONS.length) return true;\n try {\n const pathname = new URL(url).pathname.toLowerCase();\n return EXTENSIONS.some((ext) => pathname.endsWith(ext));\n } catch {\n return false;\n }\n};\n\nconst matchesContentType = (response) => {\n if (!ALLOWED_CONTENT_TYPES.length) return false;\n const header = response.headers.get(\"content-type\");\n if (!header) return false;\n const contentType = header.split(\";\")[0]?.trim().toLowerCase();\n if (!contentType) return false;\n return ALLOWED_CONTENT_TYPES.includes(contentType);\n};\n\nconst isNavigationRequest = (request) => {\n if (request.mode === \"navigate\") return true;\n const accept = request.headers.get(\"accept\") || \"\";\n return accept.includes(\"text/html\");\n};\n\nconst handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n const shouldCache =\n matchesExtension(request.url) || matchesContentType(response);\n if (shouldCache) {\n await putInCache(request, response);\n }\n await maybeStoreHtmlFallback(response);\n return response;\n }\n\n if (response && response.status === 404) {\n const cached = await caches.match(request);\n if (cached) return cached;\n if (isNavigationRequest(request)) {\n const fallback = await getHtmlFallback();\n if (fallback) return fallback;\n }\n return response;\n }\n\n return response;\n } catch (error) {\n const cached = await caches.match(request);\n if (cached) return cached;\n throw error;\n }\n};\n\nconst putInCache = async (request, response) => {\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set(\"sw-cache-time\", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n\n await cache.put(request, responseToCache);\n limitCacheSize().catch(() => undefined);\n};\n\nconst maybeStoreHtmlFallback = async (response) => {\n if (!matchesContentType(response)) return;\n const header = response.headers.get(\"content-type\") || \"\";\n const contentType = header.split(\";\")[0]?.trim().toLowerCase();\n if (contentType !== \"text/html\") return;\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set(\"sw-cache-time\", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n await cache.put(HTML_FALLBACK_KEY, responseToCache);\n};\n\nconst getHtmlFallback = async () => {\n const cache = await caches.open(CACHE_NAME);\n return cache.match(HTML_FALLBACK_KEY);\n};\n\nconst cleanUpCache = async () => {\n if (MAX_AGE_MS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n const now = Date.now();\n\n for (const request of keys) {\n const response = await cache.match(request);\n if (!response) continue;\n\n const storedTime = response.headers.get(\"sw-cache-time\");\n const dateHeader = response.headers.get(\"date\");\n const timestamp = storedTime ? Number(storedTime) : dateHeader ? new Date(dateHeader).getTime() : NaN;\n\n if (!Number.isFinite(timestamp)) continue;\n if (now - timestamp > MAX_AGE_MS) {\n await cache.delete(request);\n }\n }\n};\n\nconst limitCacheSize = async () => {\n if (MAX_ITEMS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n\n if (keys.length <= MAX_ITEMS) return;\n const itemsToDelete = keys.slice(0, keys.length - MAX_ITEMS);\n for (const request of itemsToDelete) {\n await cache.delete(request);\n }\n};\n","(() => {\n if (!(\"serviceWorker\" in navigator)) return;\n window.addEventListener(\"load\", () => {\n <% if (inlineSw) { %>\n const swCode = <%- swCodeJson %>;\n const blob = new Blob([swCode], { type: \"text/javascript\" });\n const swUrl = URL.createObjectURL(blob);\n navigator.serviceWorker\n .register(swUrl)\n .catch((error) => {\n console.warn(\"[vite-sw-cacher-plugin] SW registration failed\", error);\n })\n .finally(() => {\n URL.revokeObjectURL(swUrl);\n });\n <% } else { %>\n navigator.serviceWorker\n .register(<%- swUrlJson %>)\n .catch((error) => {\n console.warn(\"[vite-sw-cacher-plugin] SW registration failed\", error);\n });\n <% } %>\n });\n})();\n"],"mappings":"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","names":["ejs","lookupMimeType"]}
package/dist/index.d.cts CHANGED
@@ -6,6 +6,7 @@ interface ViteSwCacherPluginOptions {
6
6
  ttl?: number;
7
7
  maxItemsCount?: number;
8
8
  cacheName?: string;
9
+ inlineSw?: boolean;
9
10
  }
10
11
  declare const viteSwCacherPlugin: (options?: ViteSwCacherPluginOptions) => Plugin;
11
12
 
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ interface ViteSwCacherPluginOptions {
6
6
  ttl?: number;
7
7
  maxItemsCount?: number;
8
8
  cacheName?: string;
9
+ inlineSw?: boolean;
9
10
  }
10
11
  declare const viteSwCacherPlugin: (options?: ViteSwCacherPluginOptions) => Plugin;
11
12
 
package/dist/index.js CHANGED
@@ -4,10 +4,10 @@ import { minify } from "terser";
4
4
  import { lookup as lookupMimeType } from "mime-types";
5
5
 
6
6
  // src/templates/sw.ejs
7
- var sw_default = 'const CACHE_NAME = <%- cacheNameJson %>;\nconst MAX_AGE_MS = <%- ttlMs %>;\nconst MAX_ITEMS = <%- maxItems %>;\nconst EXTENSIONS = <%- extensionsJson %>;\nconst ALLOWED_CONTENT_TYPES = <%- allowedContentTypesJson %>;\nconst URL_PATTERN = <%- urlPatternSource %>;\n\nself.addEventListener("install", () => self.skipWaiting());\n\nself.addEventListener("activate", (event) => {\n event.waitUntil(cleanUpCache().then(() => self.clients.claim()));\n});\n\nself.addEventListener("fetch", (event) => {\n const { request } = event;\n if (!shouldHandleRequest(request)) return;\n\n event.respondWith(handleRequest(request));\n});\n\nconst shouldHandleRequest = (request) => {\n if (request.method !== "GET") return false;\n const url = request.url;\n return matchesPattern(url);\n};\n\nconst matchesPattern = (url) => {\n if (!URL_PATTERN) return true;\n return URL_PATTERN.test(url);\n};\n\nconst matchesExtension = (url) => {\n if (!EXTENSIONS.length) return true;\n try {\n const pathname = new URL(url).pathname.toLowerCase();\n return EXTENSIONS.some((ext) => pathname.endsWith(ext));\n } catch {\n return false;\n }\n};\n\nconst matchesContentType = (response) => {\n if (!ALLOWED_CONTENT_TYPES.length) return false;\n const header = response.headers.get("content-type");\n if (!header) return false;\n const contentType = header.split(";")[0]?.trim().toLowerCase();\n if (!contentType) return false;\n return ALLOWED_CONTENT_TYPES.includes(contentType);\n};\n\nconst handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n const shouldCache =\n matchesExtension(request.url) || matchesContentType(response);\n if (shouldCache) {\n await putInCache(request, response);\n }\n return response;\n }\n\n if (response && response.status === 404) {\n const cached = await caches.match(request);\n return cached || response;\n }\n\n return response;\n } catch (error) {\n const cached = await caches.match(request);\n if (cached) return cached;\n throw error;\n }\n};\n\nconst putInCache = async (request, response) => {\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set("sw-cache-time", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n\n await cache.put(request, responseToCache);\n limitCacheSize().catch(() => undefined);\n};\n\nconst cleanUpCache = async () => {\n if (MAX_AGE_MS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n const now = Date.now();\n\n for (const request of keys) {\n const response = await cache.match(request);\n if (!response) continue;\n\n const storedTime = response.headers.get("sw-cache-time");\n const dateHeader = response.headers.get("date");\n const timestamp = storedTime ? Number(storedTime) : dateHeader ? new Date(dateHeader).getTime() : NaN;\n\n if (!Number.isFinite(timestamp)) continue;\n if (now - timestamp > MAX_AGE_MS) {\n await cache.delete(request);\n }\n }\n};\n\nconst limitCacheSize = async () => {\n if (MAX_ITEMS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n\n if (keys.length <= MAX_ITEMS) return;\n const itemsToDelete = keys.slice(0, keys.length - MAX_ITEMS);\n for (const request of itemsToDelete) {\n await cache.delete(request);\n }\n};\n';
7
+ var sw_default = 'const CACHE_NAME = <%- cacheNameJson %>;\nconst MAX_AGE_MS = <%- ttlMs %>;\nconst MAX_ITEMS = <%- maxItems %>;\nconst EXTENSIONS = <%- extensionsJson %>;\nconst ALLOWED_CONTENT_TYPES = <%- allowedContentTypesJson %>;\nconst URL_PATTERN = <%- urlPatternSource %>;\nconst HTML_FALLBACK_KEY = "__sw-cacher-html-fallback__";\n\nself.addEventListener("install", () => self.skipWaiting());\n\nself.addEventListener("activate", (event) => {\n event.waitUntil(cleanUpCache().then(() => self.clients.claim()));\n});\n\nself.addEventListener("fetch", (event) => {\n const { request } = event;\n if (!shouldHandleRequest(request)) return;\n\n event.respondWith(handleRequest(request));\n});\n\nconst shouldHandleRequest = (request) => {\n if (request.method !== "GET") return false;\n const url = request.url;\n return matchesPattern(url);\n};\n\nconst matchesPattern = (url) => {\n if (!URL_PATTERN) return true;\n return URL_PATTERN.test(url);\n};\n\nconst matchesExtension = (url) => {\n if (!EXTENSIONS.length) return true;\n try {\n const pathname = new URL(url).pathname.toLowerCase();\n return EXTENSIONS.some((ext) => pathname.endsWith(ext));\n } catch {\n return false;\n }\n};\n\nconst matchesContentType = (response) => {\n if (!ALLOWED_CONTENT_TYPES.length) return false;\n const header = response.headers.get("content-type");\n if (!header) return false;\n const contentType = header.split(";")[0]?.trim().toLowerCase();\n if (!contentType) return false;\n return ALLOWED_CONTENT_TYPES.includes(contentType);\n};\n\nconst isNavigationRequest = (request) => {\n if (request.mode === "navigate") return true;\n const accept = request.headers.get("accept") || "";\n return accept.includes("text/html");\n};\n\nconst handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n const shouldCache =\n matchesExtension(request.url) || matchesContentType(response);\n if (shouldCache) {\n await putInCache(request, response);\n }\n await maybeStoreHtmlFallback(response);\n return response;\n }\n\n if (response && response.status === 404) {\n const cached = await caches.match(request);\n if (cached) return cached;\n if (isNavigationRequest(request)) {\n const fallback = await getHtmlFallback();\n if (fallback) return fallback;\n }\n return response;\n }\n\n return response;\n } catch (error) {\n const cached = await caches.match(request);\n if (cached) return cached;\n throw error;\n }\n};\n\nconst putInCache = async (request, response) => {\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set("sw-cache-time", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n\n await cache.put(request, responseToCache);\n limitCacheSize().catch(() => undefined);\n};\n\nconst maybeStoreHtmlFallback = async (response) => {\n if (!matchesContentType(response)) return;\n const header = response.headers.get("content-type") || "";\n const contentType = header.split(";")[0]?.trim().toLowerCase();\n if (contentType !== "text/html") return;\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set("sw-cache-time", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n await cache.put(HTML_FALLBACK_KEY, responseToCache);\n};\n\nconst getHtmlFallback = async () => {\n const cache = await caches.open(CACHE_NAME);\n return cache.match(HTML_FALLBACK_KEY);\n};\n\nconst cleanUpCache = async () => {\n if (MAX_AGE_MS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n const now = Date.now();\n\n for (const request of keys) {\n const response = await cache.match(request);\n if (!response) continue;\n\n const storedTime = response.headers.get("sw-cache-time");\n const dateHeader = response.headers.get("date");\n const timestamp = storedTime ? Number(storedTime) : dateHeader ? new Date(dateHeader).getTime() : NaN;\n\n if (!Number.isFinite(timestamp)) continue;\n if (now - timestamp > MAX_AGE_MS) {\n await cache.delete(request);\n }\n }\n};\n\nconst limitCacheSize = async () => {\n if (MAX_ITEMS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n\n if (keys.length <= MAX_ITEMS) return;\n const itemsToDelete = keys.slice(0, keys.length - MAX_ITEMS);\n for (const request of itemsToDelete) {\n await cache.delete(request);\n }\n};\n';
8
8
 
9
9
  // src/templates/inline-script.ejs
10
- var inline_script_default = '(() => {\n if (!("serviceWorker" in navigator)) return;\n window.addEventListener("load", () => {\n navigator.serviceWorker.register(<%- swUrlJson %>).catch((error) => {\n console.warn("[vite-sw-cacher-plugin] SW registration failed", error);\n });\n });\n})();\n';
10
+ var inline_script_default = '(() => {\n if (!("serviceWorker" in navigator)) return;\n window.addEventListener("load", () => {\n <% if (inlineSw) { %>\n const swCode = <%- swCodeJson %>;\n const blob = new Blob([swCode], { type: "text/javascript" });\n const swUrl = URL.createObjectURL(blob);\n navigator.serviceWorker\n .register(swUrl)\n .catch((error) => {\n console.warn("[vite-sw-cacher-plugin] SW registration failed", error);\n })\n .finally(() => {\n URL.revokeObjectURL(swUrl);\n });\n <% } else { %>\n navigator.serviceWorker\n .register(<%- swUrlJson %>)\n .catch((error) => {\n console.warn("[vite-sw-cacher-plugin] SW registration failed", error);\n });\n <% } %>\n });\n})();\n';
11
11
 
12
12
  // src/index.ts
13
13
  var DEFAULT_EXTENSIONS = [
@@ -55,6 +55,17 @@ var joinBase = (base, fileName) => {
55
55
  const normalizedBase = base.endsWith("/") ? base : `${base}/`;
56
56
  return `${normalizedBase}${fileName}`;
57
57
  };
58
+ var injectScriptIntoHtml = (html, script) => {
59
+ const tag = `<script>${script}</script>`;
60
+ if (/<\/head>/i.test(html)) {
61
+ return html.replace(/<\/head>/i, `${tag}</head>`);
62
+ }
63
+ return `${html}${tag}`;
64
+ };
65
+ var toHtmlString = (source) => {
66
+ if (typeof source === "string") return source;
67
+ return new TextDecoder().decode(source);
68
+ };
58
69
  var renderTemplate = (template, data) => ejs.render(template, data);
59
70
  var minifyScript = async (code) => {
60
71
  const result = await minify(code, {
@@ -92,9 +103,11 @@ var viteSwCacherPlugin = (options = {}) => {
92
103
  resolvedConfig = config;
93
104
  },
94
105
  async transformIndexHtml(html) {
106
+ if (options.inlineSw) return html;
95
107
  const base = resolvedConfig?.base ?? "/";
96
108
  const swUrl = joinBase(base, swFileName);
97
109
  const inlineScript = renderTemplate(inline_script_default, {
110
+ inlineSw: false,
98
111
  swUrlJson: JSON.stringify(swUrl)
99
112
  });
100
113
  const minifiedInlineScript = await minifyScript(inlineScript);
@@ -115,6 +128,7 @@ var viteSwCacherPlugin = (options = {}) => {
115
128
  const ttlMs = options.ttl ?? DEFAULT_TTL_MS;
116
129
  const maxItems = options.maxItemsCount ?? staticCount * 2;
117
130
  const cacheName = options.cacheName ?? DEFAULT_CACHE_NAME;
131
+ const inlineSw = options.inlineSw ?? false;
118
132
  const swSource = await buildServiceWorkerSource({
119
133
  cacheName,
120
134
  ttlMs,
@@ -122,6 +136,20 @@ var viteSwCacherPlugin = (options = {}) => {
122
136
  extensions,
123
137
  pattern: options.pattern
124
138
  });
139
+ if (inlineSw) {
140
+ const inlineScript = renderTemplate(inline_script_default, {
141
+ inlineSw: true,
142
+ swCodeJson: JSON.stringify(swSource)
143
+ });
144
+ const minifiedInlineScript = await minifyScript(inlineScript);
145
+ for (const item of Object.values(bundle)) {
146
+ if (item.type !== "asset") continue;
147
+ if (!item.fileName.toLowerCase().endsWith(".html")) continue;
148
+ const html = toHtmlString(item.source);
149
+ item.source = injectScriptIntoHtml(html, minifiedInlineScript);
150
+ }
151
+ return;
152
+ }
125
153
  this.emitFile({
126
154
  type: "asset",
127
155
  fileName: swFileName,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/templates/sw.ejs","../src/templates/inline-script.ejs"],"sourcesContent":["import type { OutputAsset, OutputBundle, OutputChunk } from \"rollup\";\nimport type { Plugin, ResolvedConfig } from \"vite\";\nimport ejs from \"ejs\";\nimport { minify } from \"terser\";\nimport { lookup as lookupMimeType } from \"mime-types\";\nimport swTemplate from \"./templates/sw.ejs\";\nimport inlineScriptTemplate from \"./templates/inline-script.ejs\";\n\nexport interface ViteSwCacherPluginOptions {\n extensions?: string[];\n pattern?: string;\n ttl?: number;\n maxItemsCount?: number;\n cacheName?: string;\n}\n\nconst DEFAULT_EXTENSIONS = [\n \".html\",\n \".css\",\n \".js\",\n \".svg\",\n \".png\",\n \".jpeg\",\n \".jpg\",\n \".gif\",\n \".webp\",\n \".avif\",\n \".bmp\",\n \".ico\",\n \".tif\",\n \".tiff\",\n];\n\nconst DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;\nconst DEFAULT_CACHE_NAME = \"vite-sw-cacher-plugin\";\nconst DEFAULT_SW_FILE_NAME = \"sw-cacher.js\";\n\nconst normalizeExtensions = (extensions?: string[]): string[] => {\n const list = extensions?.length ? extensions : DEFAULT_EXTENSIONS;\n const normalized = list.map((ext) => {\n const trimmed = ext.trim();\n if (!trimmed) return \"\";\n return trimmed.startsWith(\".\") ? trimmed.toLowerCase() : `.${trimmed.toLowerCase()}`;\n });\n\n return Array.from(new Set(normalized.filter(Boolean)));\n};\n\nconst wildcardToRegexSource = (pattern: string): string => {\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return `^${escaped.replace(/\\*/g, \".*\")}$`;\n};\n\nconst countStaticOutputs = (bundle: OutputBundle, extensions: string[]): number => {\n if (!extensions.length) return 0;\n const normalized = extensions.map((ext) => ext.toLowerCase());\n const items = Object.values(bundle) as Array<OutputAsset | OutputChunk>;\n return items.filter((item) => {\n const name = item.fileName.toLowerCase();\n return normalized.some((ext) => name.endsWith(ext));\n }).length;\n};\n\nconst joinBase = (base: string, fileName: string): string => {\n const normalizedBase = base.endsWith(\"/\") ? base : `${base}/`;\n return `${normalizedBase}${fileName}`;\n};\n\nconst renderTemplate = (template: string, data: Record<string, unknown>): string =>\n ejs.render(template, data);\n\nconst minifyScript = async (code: string): Promise<string> => {\n const result = await minify(code, {\n compress: true,\n mangle: true,\n format: { comments: false },\n });\n return result.code ?? code;\n};\n\nconst buildServiceWorkerSource = async (options: {\n cacheName: string;\n ttlMs: number;\n maxItems: number;\n extensions: string[];\n pattern?: string;\n}): Promise<string> => {\n const patternSource = options.pattern ? wildcardToRegexSource(options.pattern) : null;\n const urlPatternSource = patternSource\n ? `new RegExp(${JSON.stringify(patternSource)})`\n : \"null\";\n const allowedContentTypes = Array.from(\n new Set(\n options.extensions\n .map((ext) => lookupMimeType(ext))\n .filter((value): value is string => Boolean(value))\n .map((value) => value.toLowerCase()),\n ),\n );\n\n const source = renderTemplate(swTemplate, {\n cacheNameJson: JSON.stringify(options.cacheName),\n ttlMs: Math.max(0, options.ttlMs),\n maxItems: options.maxItems,\n extensionsJson: JSON.stringify(options.extensions),\n allowedContentTypesJson: JSON.stringify(allowedContentTypes),\n urlPatternSource,\n });\n\n return minifyScript(source);\n};\n\nexport const viteSwCacherPlugin = (\n options: ViteSwCacherPluginOptions = {},\n): Plugin => {\n let resolvedConfig: ResolvedConfig | null = null;\n const swFileName = DEFAULT_SW_FILE_NAME;\n\n return {\n name: \"vite-sw-cacher-plugin\",\n apply: \"build\",\n configResolved(config) {\n resolvedConfig = config;\n },\n async transformIndexHtml(html) {\n const base = resolvedConfig?.base ?? \"/\";\n const swUrl = joinBase(base, swFileName);\n const inlineScript = renderTemplate(inlineScriptTemplate, {\n swUrlJson: JSON.stringify(swUrl),\n });\n const minifiedInlineScript = await minifyScript(inlineScript);\n\n return {\n html,\n tags: [\n {\n tag: \"script\",\n injectTo: \"head\",\n children: minifiedInlineScript,\n },\n ],\n };\n },\n async generateBundle(_, bundle) {\n const extensions = normalizeExtensions(options.extensions);\n const staticCount = countStaticOutputs(bundle, extensions);\n const ttlMs = options.ttl ?? DEFAULT_TTL_MS;\n const maxItems =\n options.maxItemsCount ?? staticCount * 2;\n const cacheName = options.cacheName ?? DEFAULT_CACHE_NAME;\n\n const swSource = await buildServiceWorkerSource({\n cacheName,\n ttlMs,\n maxItems,\n extensions,\n pattern: options.pattern,\n });\n\n this.emitFile({\n type: \"asset\",\n fileName: swFileName,\n source: swSource,\n });\n },\n };\n};\n\nexport default viteSwCacherPlugin;\n","const CACHE_NAME = <%- cacheNameJson %>;\nconst MAX_AGE_MS = <%- ttlMs %>;\nconst MAX_ITEMS = <%- maxItems %>;\nconst EXTENSIONS = <%- extensionsJson %>;\nconst ALLOWED_CONTENT_TYPES = <%- allowedContentTypesJson %>;\nconst URL_PATTERN = <%- urlPatternSource %>;\n\nself.addEventListener(\"install\", () => self.skipWaiting());\n\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(cleanUpCache().then(() => self.clients.claim()));\n});\n\nself.addEventListener(\"fetch\", (event) => {\n const { request } = event;\n if (!shouldHandleRequest(request)) return;\n\n event.respondWith(handleRequest(request));\n});\n\nconst shouldHandleRequest = (request) => {\n if (request.method !== \"GET\") return false;\n const url = request.url;\n return matchesPattern(url);\n};\n\nconst matchesPattern = (url) => {\n if (!URL_PATTERN) return true;\n return URL_PATTERN.test(url);\n};\n\nconst matchesExtension = (url) => {\n if (!EXTENSIONS.length) return true;\n try {\n const pathname = new URL(url).pathname.toLowerCase();\n return EXTENSIONS.some((ext) => pathname.endsWith(ext));\n } catch {\n return false;\n }\n};\n\nconst matchesContentType = (response) => {\n if (!ALLOWED_CONTENT_TYPES.length) return false;\n const header = response.headers.get(\"content-type\");\n if (!header) return false;\n const contentType = header.split(\";\")[0]?.trim().toLowerCase();\n if (!contentType) return false;\n return ALLOWED_CONTENT_TYPES.includes(contentType);\n};\n\nconst handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n const shouldCache =\n matchesExtension(request.url) || matchesContentType(response);\n if (shouldCache) {\n await putInCache(request, response);\n }\n return response;\n }\n\n if (response && response.status === 404) {\n const cached = await caches.match(request);\n return cached || response;\n }\n\n return response;\n } catch (error) {\n const cached = await caches.match(request);\n if (cached) return cached;\n throw error;\n }\n};\n\nconst putInCache = async (request, response) => {\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set(\"sw-cache-time\", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n\n await cache.put(request, responseToCache);\n limitCacheSize().catch(() => undefined);\n};\n\nconst cleanUpCache = async () => {\n if (MAX_AGE_MS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n const now = Date.now();\n\n for (const request of keys) {\n const response = await cache.match(request);\n if (!response) continue;\n\n const storedTime = response.headers.get(\"sw-cache-time\");\n const dateHeader = response.headers.get(\"date\");\n const timestamp = storedTime ? Number(storedTime) : dateHeader ? new Date(dateHeader).getTime() : NaN;\n\n if (!Number.isFinite(timestamp)) continue;\n if (now - timestamp > MAX_AGE_MS) {\n await cache.delete(request);\n }\n }\n};\n\nconst limitCacheSize = async () => {\n if (MAX_ITEMS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n\n if (keys.length <= MAX_ITEMS) return;\n const itemsToDelete = keys.slice(0, keys.length - MAX_ITEMS);\n for (const request of itemsToDelete) {\n await cache.delete(request);\n }\n};\n","(() => {\n if (!(\"serviceWorker\" in navigator)) return;\n window.addEventListener(\"load\", () => {\n navigator.serviceWorker.register(<%- swUrlJson %>).catch((error) => {\n console.warn(\"[vite-sw-cacher-plugin] SW registration failed\", error);\n });\n });\n})();\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/templates/sw.ejs","../src/templates/inline-script.ejs"],"sourcesContent":["import type { OutputAsset, OutputBundle, OutputChunk } from \"rollup\";\nimport type { Plugin, ResolvedConfig } from \"vite\";\nimport ejs from \"ejs\";\nimport { minify } from \"terser\";\nimport { lookup as lookupMimeType } from \"mime-types\";\nimport swTemplate from \"./templates/sw.ejs\";\nimport inlineScriptTemplate from \"./templates/inline-script.ejs\";\n\nexport interface ViteSwCacherPluginOptions {\n extensions?: string[];\n pattern?: string;\n ttl?: number;\n maxItemsCount?: number;\n cacheName?: string;\n inlineSw?: boolean;\n}\n\nconst DEFAULT_EXTENSIONS = [\n \".html\",\n \".css\",\n \".js\",\n \".svg\",\n \".png\",\n \".jpeg\",\n \".jpg\",\n \".gif\",\n \".webp\",\n \".avif\",\n \".bmp\",\n \".ico\",\n \".tif\",\n \".tiff\",\n];\n\nconst DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;\nconst DEFAULT_CACHE_NAME = \"vite-sw-cacher-plugin\";\nconst DEFAULT_SW_FILE_NAME = \"sw-cacher.js\";\n\nconst normalizeExtensions = (extensions?: string[]): string[] => {\n const list = extensions?.length ? extensions : DEFAULT_EXTENSIONS;\n const normalized = list.map((ext) => {\n const trimmed = ext.trim();\n if (!trimmed) return \"\";\n return trimmed.startsWith(\".\") ? trimmed.toLowerCase() : `.${trimmed.toLowerCase()}`;\n });\n\n return Array.from(new Set(normalized.filter(Boolean)));\n};\n\nconst wildcardToRegexSource = (pattern: string): string => {\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return `^${escaped.replace(/\\*/g, \".*\")}$`;\n};\n\nconst countStaticOutputs = (bundle: OutputBundle, extensions: string[]): number => {\n if (!extensions.length) return 0;\n const normalized = extensions.map((ext) => ext.toLowerCase());\n const items = Object.values(bundle) as Array<OutputAsset | OutputChunk>;\n return items.filter((item) => {\n const name = item.fileName.toLowerCase();\n return normalized.some((ext) => name.endsWith(ext));\n }).length;\n};\n\nconst joinBase = (base: string, fileName: string): string => {\n const normalizedBase = base.endsWith(\"/\") ? base : `${base}/`;\n return `${normalizedBase}${fileName}`;\n};\n\nconst injectScriptIntoHtml = (html: string, script: string): string => {\n const tag = `<script>${script}</script>`;\n if (/<\\/head>/i.test(html)) {\n return html.replace(/<\\/head>/i, `${tag}</head>`);\n }\n return `${html}${tag}`;\n};\n\nconst toHtmlString = (source: OutputAsset[\"source\"]): string => {\n if (typeof source === \"string\") return source;\n return new TextDecoder().decode(source);\n};\n\nconst renderTemplate = (template: string, data: Record<string, unknown>): string =>\n ejs.render(template, data);\n\nconst minifyScript = async (code: string): Promise<string> => {\n const result = await minify(code, {\n compress: true,\n mangle: true,\n format: { comments: false },\n });\n return result.code ?? code;\n};\n\nconst buildServiceWorkerSource = async (options: {\n cacheName: string;\n ttlMs: number;\n maxItems: number;\n extensions: string[];\n pattern?: string;\n}): Promise<string> => {\n const patternSource = options.pattern ? wildcardToRegexSource(options.pattern) : null;\n const urlPatternSource = patternSource\n ? `new RegExp(${JSON.stringify(patternSource)})`\n : \"null\";\n const allowedContentTypes = Array.from(\n new Set(\n options.extensions\n .map((ext) => lookupMimeType(ext))\n .filter((value): value is string => Boolean(value))\n .map((value) => value.toLowerCase()),\n ),\n );\n\n const source = renderTemplate(swTemplate, {\n cacheNameJson: JSON.stringify(options.cacheName),\n ttlMs: Math.max(0, options.ttlMs),\n maxItems: options.maxItems,\n extensionsJson: JSON.stringify(options.extensions),\n allowedContentTypesJson: JSON.stringify(allowedContentTypes),\n urlPatternSource,\n });\n\n return minifyScript(source);\n};\n\nexport const viteSwCacherPlugin = (\n options: ViteSwCacherPluginOptions = {},\n): Plugin => {\n let resolvedConfig: ResolvedConfig | null = null;\n const swFileName = DEFAULT_SW_FILE_NAME;\n\n return {\n name: \"vite-sw-cacher-plugin\",\n apply: \"build\",\n configResolved(config) {\n resolvedConfig = config;\n },\n async transformIndexHtml(html) {\n if (options.inlineSw) return html;\n const base = resolvedConfig?.base ?? \"/\";\n const swUrl = joinBase(base, swFileName);\n const inlineScript = renderTemplate(inlineScriptTemplate, {\n inlineSw: false,\n swUrlJson: JSON.stringify(swUrl),\n });\n const minifiedInlineScript = await minifyScript(inlineScript);\n\n return {\n html,\n tags: [\n {\n tag: \"script\",\n injectTo: \"head\",\n children: minifiedInlineScript,\n },\n ],\n };\n },\n async generateBundle(_, bundle) {\n const extensions = normalizeExtensions(options.extensions);\n const staticCount = countStaticOutputs(bundle, extensions);\n const ttlMs = options.ttl ?? DEFAULT_TTL_MS;\n const maxItems =\n options.maxItemsCount ?? staticCount * 2;\n const cacheName = options.cacheName ?? DEFAULT_CACHE_NAME;\n const inlineSw = options.inlineSw ?? false;\n\n const swSource = await buildServiceWorkerSource({\n cacheName,\n ttlMs,\n maxItems,\n extensions,\n pattern: options.pattern,\n });\n\n if (inlineSw) {\n const inlineScript = renderTemplate(inlineScriptTemplate, {\n inlineSw: true,\n swCodeJson: JSON.stringify(swSource),\n });\n const minifiedInlineScript = await minifyScript(inlineScript);\n\n for (const item of Object.values(bundle)) {\n if (item.type !== \"asset\") continue;\n if (!item.fileName.toLowerCase().endsWith(\".html\")) continue;\n const html = toHtmlString(item.source);\n item.source = injectScriptIntoHtml(html, minifiedInlineScript);\n }\n return;\n }\n\n this.emitFile({\n type: \"asset\",\n fileName: swFileName,\n source: swSource,\n });\n },\n };\n};\n\nexport default viteSwCacherPlugin;\n","const CACHE_NAME = <%- cacheNameJson %>;\nconst MAX_AGE_MS = <%- ttlMs %>;\nconst MAX_ITEMS = <%- maxItems %>;\nconst EXTENSIONS = <%- extensionsJson %>;\nconst ALLOWED_CONTENT_TYPES = <%- allowedContentTypesJson %>;\nconst URL_PATTERN = <%- urlPatternSource %>;\nconst HTML_FALLBACK_KEY = \"__sw-cacher-html-fallback__\";\n\nself.addEventListener(\"install\", () => self.skipWaiting());\n\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(cleanUpCache().then(() => self.clients.claim()));\n});\n\nself.addEventListener(\"fetch\", (event) => {\n const { request } = event;\n if (!shouldHandleRequest(request)) return;\n\n event.respondWith(handleRequest(request));\n});\n\nconst shouldHandleRequest = (request) => {\n if (request.method !== \"GET\") return false;\n const url = request.url;\n return matchesPattern(url);\n};\n\nconst matchesPattern = (url) => {\n if (!URL_PATTERN) return true;\n return URL_PATTERN.test(url);\n};\n\nconst matchesExtension = (url) => {\n if (!EXTENSIONS.length) return true;\n try {\n const pathname = new URL(url).pathname.toLowerCase();\n return EXTENSIONS.some((ext) => pathname.endsWith(ext));\n } catch {\n return false;\n }\n};\n\nconst matchesContentType = (response) => {\n if (!ALLOWED_CONTENT_TYPES.length) return false;\n const header = response.headers.get(\"content-type\");\n if (!header) return false;\n const contentType = header.split(\";\")[0]?.trim().toLowerCase();\n if (!contentType) return false;\n return ALLOWED_CONTENT_TYPES.includes(contentType);\n};\n\nconst isNavigationRequest = (request) => {\n if (request.mode === \"navigate\") return true;\n const accept = request.headers.get(\"accept\") || \"\";\n return accept.includes(\"text/html\");\n};\n\nconst handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n const shouldCache =\n matchesExtension(request.url) || matchesContentType(response);\n if (shouldCache) {\n await putInCache(request, response);\n }\n await maybeStoreHtmlFallback(response);\n return response;\n }\n\n if (response && response.status === 404) {\n const cached = await caches.match(request);\n if (cached) return cached;\n if (isNavigationRequest(request)) {\n const fallback = await getHtmlFallback();\n if (fallback) return fallback;\n }\n return response;\n }\n\n return response;\n } catch (error) {\n const cached = await caches.match(request);\n if (cached) return cached;\n throw error;\n }\n};\n\nconst putInCache = async (request, response) => {\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set(\"sw-cache-time\", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n\n await cache.put(request, responseToCache);\n limitCacheSize().catch(() => undefined);\n};\n\nconst maybeStoreHtmlFallback = async (response) => {\n if (!matchesContentType(response)) return;\n const header = response.headers.get(\"content-type\") || \"\";\n const contentType = header.split(\";\")[0]?.trim().toLowerCase();\n if (contentType !== \"text/html\") return;\n const cache = await caches.open(CACHE_NAME);\n const headers = new Headers(response.headers);\n headers.set(\"sw-cache-time\", Date.now().toString());\n const body = await response.clone().arrayBuffer();\n const responseToCache = new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n await cache.put(HTML_FALLBACK_KEY, responseToCache);\n};\n\nconst getHtmlFallback = async () => {\n const cache = await caches.open(CACHE_NAME);\n return cache.match(HTML_FALLBACK_KEY);\n};\n\nconst cleanUpCache = async () => {\n if (MAX_AGE_MS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n const now = Date.now();\n\n for (const request of keys) {\n const response = await cache.match(request);\n if (!response) continue;\n\n const storedTime = response.headers.get(\"sw-cache-time\");\n const dateHeader = response.headers.get(\"date\");\n const timestamp = storedTime ? Number(storedTime) : dateHeader ? new Date(dateHeader).getTime() : NaN;\n\n if (!Number.isFinite(timestamp)) continue;\n if (now - timestamp > MAX_AGE_MS) {\n await cache.delete(request);\n }\n }\n};\n\nconst limitCacheSize = async () => {\n if (MAX_ITEMS <= 0) return;\n const cache = await caches.open(CACHE_NAME);\n const keys = await cache.keys();\n\n if (keys.length <= MAX_ITEMS) return;\n const itemsToDelete = keys.slice(0, keys.length - MAX_ITEMS);\n for (const request of itemsToDelete) {\n await cache.delete(request);\n }\n};\n","(() => {\n if (!(\"serviceWorker\" in navigator)) return;\n window.addEventListener(\"load\", () => {\n <% if (inlineSw) { %>\n const swCode = <%- swCodeJson %>;\n const blob = new Blob([swCode], { type: \"text/javascript\" });\n const swUrl = URL.createObjectURL(blob);\n navigator.serviceWorker\n .register(swUrl)\n .catch((error) => {\n console.warn(\"[vite-sw-cacher-plugin] SW registration failed\", error);\n })\n .finally(() => {\n URL.revokeObjectURL(swUrl);\n });\n <% } else { %>\n navigator.serviceWorker\n .register(<%- swUrlJson %>)\n .catch((error) => {\n console.warn(\"[vite-sw-cacher-plugin] SW registration failed\", error);\n });\n <% } %>\n });\n})();\n"],"mappings":";AAEA,OAAO,SAAS;AAChB,SAAS,cAAc;AACvB,SAAS,UAAU,sBAAsB;;;ACJzC;;;ACAA;;;AFiBA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,iBAAiB,KAAK,KAAK,KAAK;AACtC,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAE7B,IAAM,sBAAsB,CAAC,eAAoC;AAC/D,QAAM,OAAO,YAAY,SAAS,aAAa;AAC/C,QAAM,aAAa,KAAK,IAAI,CAAC,QAAQ;AACnC,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI,CAAC,QAAS,QAAO;AACrB,WAAO,QAAQ,WAAW,GAAG,IAAI,QAAQ,YAAY,IAAI,IAAI,QAAQ,YAAY,CAAC;AAAA,EACpF,CAAC;AAED,SAAO,MAAM,KAAK,IAAI,IAAI,WAAW,OAAO,OAAO,CAAC,CAAC;AACvD;AAEA,IAAM,wBAAwB,CAAC,YAA4B;AACzD,QAAM,UAAU,QAAQ,QAAQ,sBAAsB,MAAM;AAC5D,SAAO,IAAI,QAAQ,QAAQ,OAAO,IAAI,CAAC;AACzC;AAEA,IAAM,qBAAqB,CAAC,QAAsB,eAAiC;AACjF,MAAI,CAAC,WAAW,OAAQ,QAAO;AAC/B,QAAM,aAAa,WAAW,IAAI,CAAC,QAAQ,IAAI,YAAY,CAAC;AAC5D,QAAM,QAAQ,OAAO,OAAO,MAAM;AAClC,SAAO,MAAM,OAAO,CAAC,SAAS;AAC5B,UAAM,OAAO,KAAK,SAAS,YAAY;AACvC,WAAO,WAAW,KAAK,CAAC,QAAQ,KAAK,SAAS,GAAG,CAAC;AAAA,EACpD,CAAC,EAAE;AACL;AAEA,IAAM,WAAW,CAAC,MAAc,aAA6B;AAC3D,QAAM,iBAAiB,KAAK,SAAS,GAAG,IAAI,OAAO,GAAG,IAAI;AAC1D,SAAO,GAAG,cAAc,GAAG,QAAQ;AACrC;AAEA,IAAM,uBAAuB,CAAC,MAAc,WAA2B;AACrE,QAAM,MAAM,WAAW,MAAM;AAC7B,MAAI,YAAY,KAAK,IAAI,GAAG;AAC1B,WAAO,KAAK,QAAQ,aAAa,GAAG,GAAG,SAAS;AAAA,EAClD;AACA,SAAO,GAAG,IAAI,GAAG,GAAG;AACtB;AAEA,IAAM,eAAe,CAAC,WAA0C;AAC9D,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,SAAO,IAAI,YAAY,EAAE,OAAO,MAAM;AACxC;AAEA,IAAM,iBAAiB,CAAC,UAAkB,SACxC,IAAI,OAAO,UAAU,IAAI;AAE3B,IAAM,eAAe,OAAO,SAAkC;AAC5D,QAAM,SAAS,MAAM,OAAO,MAAM;AAAA,IAChC,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,QAAQ,EAAE,UAAU,MAAM;AAAA,EAC5B,CAAC;AACD,SAAO,OAAO,QAAQ;AACxB;AAEA,IAAM,2BAA2B,OAAO,YAMjB;AACrB,QAAM,gBAAgB,QAAQ,UAAU,sBAAsB,QAAQ,OAAO,IAAI;AACjF,QAAM,mBAAmB,gBACrB,cAAc,KAAK,UAAU,aAAa,CAAC,MAC3C;AACJ,QAAM,sBAAsB,MAAM;AAAA,IAChC,IAAI;AAAA,MACF,QAAQ,WACL,IAAI,CAAC,QAAQ,eAAe,GAAG,CAAC,EAChC,OAAO,CAAC,UAA2B,QAAQ,KAAK,CAAC,EACjD,IAAI,CAAC,UAAU,MAAM,YAAY,CAAC;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,SAAS,eAAe,YAAY;AAAA,IACxC,eAAe,KAAK,UAAU,QAAQ,SAAS;AAAA,IAC/C,OAAO,KAAK,IAAI,GAAG,QAAQ,KAAK;AAAA,IAChC,UAAU,QAAQ;AAAA,IAClB,gBAAgB,KAAK,UAAU,QAAQ,UAAU;AAAA,IACjD,yBAAyB,KAAK,UAAU,mBAAmB;AAAA,IAC3D;AAAA,EACF,CAAC;AAED,SAAO,aAAa,MAAM;AAC5B;AAEO,IAAM,qBAAqB,CAChC,UAAqC,CAAC,MAC3B;AACX,MAAI,iBAAwC;AAC5C,QAAM,aAAa;AAEnB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,eAAe,QAAQ;AACrB,uBAAiB;AAAA,IACnB;AAAA,IACA,MAAM,mBAAmB,MAAM;AAC7B,UAAI,QAAQ,SAAU,QAAO;AAC7B,YAAM,OAAO,gBAAgB,QAAQ;AACrC,YAAM,QAAQ,SAAS,MAAM,UAAU;AACvC,YAAM,eAAe,eAAe,uBAAsB;AAAA,QACxD,UAAU;AAAA,QACV,WAAW,KAAK,UAAU,KAAK;AAAA,MACjC,CAAC;AACD,YAAM,uBAAuB,MAAM,aAAa,YAAY;AAE5D,aAAO;AAAA,QACL;AAAA,QACA,MAAM;AAAA,UACJ;AAAA,YACE,KAAK;AAAA,YACL,UAAU;AAAA,YACV,UAAU;AAAA,UACZ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,MAAM,eAAe,GAAG,QAAQ;AAC9B,YAAM,aAAa,oBAAoB,QAAQ,UAAU;AACzD,YAAM,cAAc,mBAAmB,QAAQ,UAAU;AACzD,YAAM,QAAQ,QAAQ,OAAO;AAC7B,YAAM,WACJ,QAAQ,iBAAiB,cAAc;AACzC,YAAM,YAAY,QAAQ,aAAa;AACvC,YAAM,WAAW,QAAQ,YAAY;AAErC,YAAM,WAAW,MAAM,yBAAyB;AAAA,QAC9C;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,QAAQ;AAAA,MACnB,CAAC;AAED,UAAI,UAAU;AACZ,cAAM,eAAe,eAAe,uBAAsB;AAAA,UACxD,UAAU;AAAA,UACV,YAAY,KAAK,UAAU,QAAQ;AAAA,QACrC,CAAC;AACD,cAAM,uBAAuB,MAAM,aAAa,YAAY;AAE5D,mBAAW,QAAQ,OAAO,OAAO,MAAM,GAAG;AACxC,cAAI,KAAK,SAAS,QAAS;AAC3B,cAAI,CAAC,KAAK,SAAS,YAAY,EAAE,SAAS,OAAO,EAAG;AACpD,gBAAM,OAAO,aAAa,KAAK,MAAM;AACrC,eAAK,SAAS,qBAAqB,MAAM,oBAAoB;AAAA,QAC/D;AACA;AAAA,MACF;AAEA,WAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-sw-cacher-plugin",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Vite 7 plugin that injects a SW cacher",
5
5
  "type": "module",
6
6
  "author": {