import { VitePWA } from 'vite-plugin-pwa'; import { mkdir, rm, readFile, writeFile, lstat } from 'node:fs/promises'; import { join } from 'node:path'; import fg from 'fast-glob'; function configureSvelteKitOptions(kit, viteOptions, options) { const { base = viteOptions.base ?? "/", adapterFallback, outDir = `${viteOptions.root}/.svelte-kit` } = kit; if (typeof options.includeManifestIcons === "undefined") options.includeManifestIcons = false; let config; if (options.strategies === "injectManifest") { if (!options.srcDir) options.srcDir = "src"; if (!options.filename) options.filename = "service-worker.js"; options.injectManifest = options.injectManifest ?? {}; config = options.injectManifest; } else { options.workbox = options.workbox ?? {}; if (!options.workbox.navigateFallback) options.workbox.navigateFallback = adapterFallback ?? base; config = options.workbox; } if (!config.globDirectory) config.globDirectory = `${outDir}/output`; if (!config.modifyURLPrefix) config.globPatterns = buildGlobPatterns(config.globPatterns); config.globIgnores = buildGlobIgnores(config.globIgnores); if (!config.manifestTransforms) { config.manifestTransforms = [createManifestTransform( base, options.strategies === "injectManifest" ? void 0 : options.manifestFilename ?? "manifest.webmanifest", kit )]; } } function createManifestTransform(base, webManifestName, options) { return async (entries) => { const defaultAdapterFallback = "prerendered/fallback.html"; const suffix = options?.trailingSlash === "always" ? "/" : ""; let adapterFallback = options?.adapterFallback; let excludeFallback = false; if (!adapterFallback) { adapterFallback = defaultAdapterFallback; excludeFallback = true; } const manifest = entries.filter(({ url }) => !(excludeFallback && url === defaultAdapterFallback)).map((e) => { let url = e.url; if (url.startsWith("client/")) url = url.slice(7); else if (url.startsWith("prerendered/pages/")) url = url.slice(18); else if (url === defaultAdapterFallback) url = adapterFallback; if (url.endsWith(".html")) { if (url.startsWith("/")) url = url.slice(1); if (url === "index.html") { url = base; } else { const idx = url.lastIndexOf("/"); if (idx > -1) { if (url.endsWith("/index.html")) url = `${url.slice(0, idx)}${suffix}`; else url = `${url.substring(0, url.lastIndexOf("."))}${suffix}`; } else { url = `${url.substring(0, url.lastIndexOf("."))}${suffix}`; } } } e.url = url; return e; }); if (!webManifestName) return { manifest }; return { manifest: manifest.filter((e) => e.url !== webManifestName) }; }; } function buildGlobPatterns(globPatterns) { if (globPatterns) { if (!globPatterns.some((g) => g.startsWith("prerendered/"))) globPatterns.push("prerendered/**/*.html"); if (!globPatterns.some((g) => g.startsWith("client/"))) globPatterns.push("client/**/*.{js,css,ico,png,svg,webp,webmanifest}"); if (!globPatterns.some((g) => g.includes("webmanifest"))) globPatterns.push("client/*.webmanifest"); return globPatterns; } return ["client/**/*.{js,css,ico,png,svg,webp,webmanifest}", "prerendered/**/*.html"]; } function buildGlobIgnores(globIgnores) { if (globIgnores) { if (!globIgnores.some((g) => g.startsWith("server/"))) globIgnores.push("server/*.*"); return globIgnores; } return ["server/*.*"]; } function SvelteKitPlugin(options, apiResolver) { let viteConfig; return { name: "vite-plugin-pwa:sveltekit:build", apply: "build", enforce: "pre", configResolved(config) { viteConfig = config; }, generateBundle(_, bundle) { if (viteConfig.build.ssr) return; apiResolver()?.generateBundle(bundle); }, closeBundle: { sequential: true, enforce: "pre", async handler() { const api = apiResolver(); if (api && !api.disabled && viteConfig.build.ssr) { const webManifest = options.manifestFilename ?? "manifest.webmanifest"; let swName = options.filename ?? "sw.js"; const outDir = options.outDir ?? `${viteConfig.root}/.svelte-kit/output`; const clientOutputDir = join(outDir, "client"); await mkdir(clientOutputDir, { recursive: true }); if (!options.strategies || options.strategies === "generateSW" || options.selfDestroying) { let path; let existsFile; if (options.selfDestroying && options.strategies === "injectManifest") { if (swName.endsWith(".ts")) swName = swName.replace(/\.ts$/, ".js"); path = join(clientOutputDir, "service-worker.js").replace("\\/g", "/"); existsFile = await isFile(path); if (existsFile) await rm(path); } await api.generateSW(); const serverOutputDir = join(outDir, "server"); path = join(serverOutputDir, swName).replace(/\\/g, "/"); existsFile = await isFile(path); if (existsFile) { const sw = await readFile(path, "utf-8"); await writeFile( join(clientOutputDir, swName).replace("\\/g", "/"), sw, "utf-8" ); await rm(path); } const result = await fg( ["workbox-*.js"], { cwd: serverOutputDir, onlyFiles: true, unique: true } ); if (result && result.length > 0) { path = join(serverOutputDir, result[0]).replace(/\\/g, "/"); await writeFile( join(clientOutputDir, result[0]).replace("\\/g", "/"), await readFile(path, "utf-8"), "utf-8" ); await rm(path); } path = join(serverOutputDir, webManifest).replace(/\\/g, "/"); existsFile = await isFile(path); if (existsFile) await rm(path); return; } if (swName.endsWith(".ts")) swName = swName.replace(/\.ts$/, ".js"); const injectManifestOptions = { globDirectory: outDir.replace(/\\/g, "/"), ...options.injectManifest ?? {}, swSrc: join(clientOutputDir, "service-worker.js").replace(/\\/g, "/"), swDest: join(clientOutputDir, "service-worker.js").replace(/\\/g, "/") }; const [injectManifest, logWorkboxResult] = await Promise.all([ import('workbox-build').then((m) => m.injectManifest), import('./chunks/log.mjs').then((m) => m.logWorkboxResult) ]); const buildResult = await injectManifest(injectManifestOptions); logWorkboxResult("injectManifest", buildResult, viteConfig); if (swName !== "service-worker.js") { await writeFile( join(clientOutputDir, swName).replace("\\/g", "/"), await readFile(injectManifestOptions.swSrc, "utf-8"), "utf-8" ); await rm(injectManifestOptions.swDest); } } } } }; } async function isFile(path) { try { const stats = await lstat(path); return stats.isFile(); } catch { return false; } } function SvelteKitPWA(userOptions = {}) { if (!userOptions.integration) userOptions.integration = {}; userOptions.integration.closeBundleOrder = "pre"; userOptions.integration.configureOptions = (viteConfig, options) => configureSvelteKitOptions( userOptions.kit ?? {}, viteConfig, options ); const plugins = VitePWA(userOptions); const plugin = plugins.find((p) => p && typeof p === "object" && "name" in p && p.name === "vite-plugin-pwa"); const resolveVitePluginPWAAPI = () => { return plugin?.api; }; return [ // remove the build plugin: we're using a custom one ...plugins.filter((p) => p && typeof p === "object" && "name" in p && p.name !== "vite-plugin-pwa:build"), SvelteKitPlugin(userOptions, resolveVitePluginPWAAPI) ]; } export { SvelteKitPWA };