vite-sw-cacher-plugin 0.0.2 → 0.0.3

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/index.cjs CHANGED
@@ -36,9 +36,10 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
  var import_ejs = __toESM(require("ejs"), 1);
38
38
  var import_terser = require("terser");
39
+ var import_mime_types = require("mime-types");
39
40
 
40
41
  // src/templates/sw.ejs
41
- var sw_default = 'const CACHE_NAME = <%- cacheNameJson %>;\nconst MAX_AGE_MS = <%- ttlMs %>;\nconst MAX_ITEMS = <%- maxItems %>;\nconst EXTENSIONS = <%- extensionsJson %>;\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) && matchesExtension(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 handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n await putInCache(request, response);\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 %>;\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
43
 
43
44
  // src/templates/inline-script.ejs
44
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';
@@ -101,11 +102,17 @@ var minifyScript = async (code) => {
101
102
  var buildServiceWorkerSource = async (options) => {
102
103
  const patternSource = options.pattern ? wildcardToRegexSource(options.pattern) : null;
103
104
  const urlPatternSource = patternSource ? `new RegExp(${JSON.stringify(patternSource)})` : "null";
105
+ const allowedContentTypes = Array.from(
106
+ new Set(
107
+ options.extensions.map((ext) => (0, import_mime_types.lookup)(ext)).filter((value) => Boolean(value)).map((value) => value.toLowerCase())
108
+ )
109
+ );
104
110
  const source = renderTemplate(sw_default, {
105
111
  cacheNameJson: JSON.stringify(options.cacheName),
106
112
  ttlMs: Math.max(0, options.ttlMs),
107
113
  maxItems: options.maxItems,
108
114
  extensionsJson: JSON.stringify(options.extensions),
115
+ allowedContentTypesJson: JSON.stringify(allowedContentTypes),
109
116
  urlPatternSource
110
117
  });
111
118
  return minifyScript(source);
@@ -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 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\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 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 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) && matchesExtension(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 handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n await putInCache(request, response);\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;;;ACHvB;;;ACAA;;;AFeA,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;AAEJ,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;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"]}
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":["ejs","lookupMimeType"]}
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  // src/index.ts
2
2
  import ejs from "ejs";
3
3
  import { minify } from "terser";
4
+ import { lookup as lookupMimeType } from "mime-types";
4
5
 
5
6
  // src/templates/sw.ejs
6
- var sw_default = 'const CACHE_NAME = <%- cacheNameJson %>;\nconst MAX_AGE_MS = <%- ttlMs %>;\nconst MAX_ITEMS = <%- maxItems %>;\nconst EXTENSIONS = <%- extensionsJson %>;\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) && matchesExtension(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 handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n await putInCache(request, response);\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 %>;\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
8
 
8
9
  // src/templates/inline-script.ejs
9
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';
@@ -66,11 +67,17 @@ var minifyScript = async (code) => {
66
67
  var buildServiceWorkerSource = async (options) => {
67
68
  const patternSource = options.pattern ? wildcardToRegexSource(options.pattern) : null;
68
69
  const urlPatternSource = patternSource ? `new RegExp(${JSON.stringify(patternSource)})` : "null";
70
+ const allowedContentTypes = Array.from(
71
+ new Set(
72
+ options.extensions.map((ext) => lookupMimeType(ext)).filter((value) => Boolean(value)).map((value) => value.toLowerCase())
73
+ )
74
+ );
69
75
  const source = renderTemplate(sw_default, {
70
76
  cacheNameJson: JSON.stringify(options.cacheName),
71
77
  ttlMs: Math.max(0, options.ttlMs),
72
78
  maxItems: options.maxItems,
73
79
  extensionsJson: JSON.stringify(options.extensions),
80
+ allowedContentTypesJson: JSON.stringify(allowedContentTypes),
74
81
  urlPatternSource
75
82
  });
76
83
  return minifyScript(source);
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 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\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 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 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) && matchesExtension(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 handleRequest = async (request) => {\n try {\n const response = await fetch(request);\n\n if (response && response.ok) {\n await putInCache(request, response);\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}\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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-sw-cacher-plugin",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Vite 7 plugin that injects a SW cacher",
5
5
  "type": "module",
6
6
  "author": {
@@ -17,6 +17,7 @@
17
17
  "types": "dist/index.d.ts",
18
18
  "dependencies": {
19
19
  "ejs": "^3.1.10",
20
+ "mime-types": "^2.1.35",
20
21
  "terser": "^5.36.0"
21
22
  },
22
23
  "files": [
@@ -34,6 +35,7 @@
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/ejs": "^3.1.5",
38
+ "@types/mime-types": "^2.1.4",
37
39
  "@types/node": "^22.10.0",
38
40
  "rollup": "^4.0.0",
39
41
  "tsup": "^8.0.0",