UNPKG

vite-plugin-pwa

Version:
1,216 lines (1,200 loc) 39.1 kB
import { logWorkboxResult, normalizePath, resolveBasePath, slash } from "./chunk-G4TAN34B.js"; import { DEV_PWA_ASSETS_NAME, DEV_READY_NAME, DEV_REGISTER_SW_NAME, DEV_SW_NAME, DEV_SW_VIRTUAL, FILE_SW_REGISTER, PWA_ASSETS_HEAD_VIRTUAL, PWA_ASSETS_ICONS_VIRTUAL, PWA_INFO_VIRTUAL, RESOLVED_DEV_SW_VIRTUAL, RESOLVED_PWA_ASSETS_HEAD_VIRTUAL, RESOLVED_PWA_ASSETS_ICONS_VIRTUAL, RESOLVED_PWA_INFO_VIRTUAL, VIRTUAL_MODULES, VIRTUAL_MODULES_MAP, VIRTUAL_MODULES_RESOLVE_PREFIX, defaultInjectManifestVitePlugins, extractIcons, generateRegisterDevSW, generateRegisterSW, generateSWHMR, generateSimpleSWRegister, generateWebManifest, injectServiceWorker } from "./chunk-I2Z7IWCN.js"; import { cyan, yellow } from "./chunk-LKBIOQSP.js"; // src/api.ts import { existsSync } from "node:fs"; import { resolve as resolve2 } from "node:path"; // src/assets.ts import crypto from "node:crypto"; import fs from "node:fs"; import { resolve as resolveFs } from "node:path"; import { glob } from "tinyglobby"; function buildManifestEntry(publicDir, url) { return new Promise((resolve6, reject) => { const cHash = crypto.createHash("MD5"); const stream = fs.createReadStream(resolveFs(publicDir, url)); stream.on("error", (err) => { reject(err); }); stream.on("data", (chunk) => { cHash.update(chunk); }); stream.on("end", () => { return resolve6({ url, revision: `${cHash.digest("hex")}` }); }); }); } function lookupAdditionalManifestEntries(useInjectManifest, injectManifest, workbox) { return useInjectManifest ? injectManifest.additionalManifestEntries || [] : workbox.additionalManifestEntries || []; } function normalizeIconPath(path) { return path.startsWith("/") ? path.substring(1) : path; } function includeIcons(icons, globs) { Object.keys(icons).forEach((key) => { const icon = icons[key]; const src = normalizeIconPath(icon.src); if (!globs.includes(src)) globs.push(src); }); } async function configureStaticAssets(resolvedVitePWAOptions, viteConfig) { const { manifest, strategies, injectManifest, workbox, includeAssets, includeManifestIcons, manifestFilename } = resolvedVitePWAOptions; const useInjectManifest = strategies === "injectManifest"; const { publicDir } = viteConfig; const globs = []; const manifestEntries = lookupAdditionalManifestEntries( useInjectManifest, injectManifest, workbox ); if (includeAssets) { if (Array.isArray(includeAssets)) globs.push(...includeAssets.map(normalizeIconPath)); else globs.push(normalizeIconPath(includeAssets)); } if (includeManifestIcons && manifest) { manifest.icons && includeIcons(manifest.icons, globs); manifest.shortcuts && manifest.shortcuts.forEach((s) => { s.icons && includeIcons(s.icons, globs); }); } if (globs.length > 0) { let assets = await glob({ patterns: globs, cwd: publicDir, expandDirectories: false, onlyFiles: true }); if (manifestEntries.length > 0) { const included = manifestEntries.map((me) => { if (typeof me === "string") return me; else return me.url; }); assets = assets.filter((a) => !included.includes(a)); } const assetsEntries = await Promise.all(assets.map((a) => { return buildManifestEntry(publicDir, a); })); manifestEntries.push(...assetsEntries); } if (manifest) { const cHash = crypto.createHash("MD5"); cHash.update(generateWebManifestFile(resolvedVitePWAOptions)); manifestEntries.push({ url: manifestFilename, revision: `${cHash.digest("hex")}` }); } if (manifestEntries.length > 0) { if (useInjectManifest) injectManifest.additionalManifestEntries = manifestEntries; else workbox.additionalManifestEntries = manifestEntries; } } function generateWebManifestFile(options) { return `${JSON.stringify(options.manifest, null, options.minify ? 0 : 2)} `; } // src/modules.ts import { promises as fs2 } from "node:fs"; import { createRequire } from "node:module"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; var _dirname = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url)); var require2 = createRequire(_dirname); async function loadWorkboxBuild() { try { const workbox = await import("workbox-build"); return workbox.default ?? workbox; } catch { return require2("workbox-build"); } } async function generateRegisterSW2(options, mode, source = "register") { const sw = options.buildBase + options.filename; const scope = options.scope; const content = await fs2.readFile(resolve(_dirname, `client/${mode}/${source}.js`), "utf-8"); return content.replace(/__SW__/g, sw).replace("__SCOPE__", scope).replace("__SW_AUTO_UPDATE__", `${options.registerType === "autoUpdate"}`).replace("__SW_SELF_DESTROYING__", `${options.selfDestroying}`).replace("__TYPE__", `${options.devOptions.enabled ? options.devOptions.type : "classic"}`); } async function generateServiceWorker(version, options, viteOptions) { if (options.selfDestroying) { const selfDestroyingSW = ` self.addEventListener('install', (e) => { self.skipWaiting(); }); self.addEventListener('activate', (e) => { self.registration.unregister() .then(() => self.clients.matchAll()) .then((clients) => { clients.forEach((client) => { if (client instanceof WindowClient) client.navigate(client.url); }); return Promise.resolve(); }) .then(() => { self.caches.keys().then((cacheNames) => { Promise.all( cacheNames.map((cacheName) => { return self.caches.delete(cacheName); }), ); }) }); }); `; await fs2.writeFile(options.swDest.replace(/\\/g, "/"), selfDestroyingSW, { encoding: "utf8" }); return { count: 1, size: selfDestroyingSW.length, warnings: [], filePaths: [options.filename] }; } await options.integration?.beforeBuildServiceWorker?.(options); const { generateSW } = await loadWorkboxBuild(); const buildResult = await generateSW(options.workbox); logWorkboxResult( version, options.throwMaximumFileSizeToCacheInBytes, "generateSW", buildResult, viteOptions ); return buildResult; } async function generateInjectManifest(version, options, viteOptions) { const { selfDestroying } = options; if (selfDestroying) { await generateServiceWorker(version, options, viteOptions); return; } await import("./vite-build-BGK4YAIU.js").then(({ buildSW }) => buildSW(version, options, viteOptions, loadWorkboxBuild())); } // src/api.ts async function _generateSW({ options, version, viteConfig }) { if (options.disable) return; if (options.strategies === "injectManifest") await generateInjectManifest(version, options, viteConfig); else await generateServiceWorker(version, options, viteConfig); } function _generateBundle(ctx, bundle, pluginCtx) { const { options, viteConfig, useImportRegister } = ctx; if (options.disable || !bundle) return; if (options.manifest) { if (!options.manifest.theme_color) { console.warn([ "", `${cyan(`PWA v${ctx.version}`)}`, `${yellow('WARNING: "theme_color" is missing from the web manifest, your application will not be able to be installed')}` ].join("\n")); } emitFile({ fileName: options.manifestFilename, source: generateWebManifestFile(options) }, bundle, pluginCtx); } if (options.injectRegister === "auto") options.injectRegister = useImportRegister ? false : "script"; if ((options.injectRegister === "script" || options.injectRegister === "script-defer") && !existsSync(resolve2(viteConfig.publicDir, FILE_SW_REGISTER))) { emitFile({ fileName: FILE_SW_REGISTER, source: generateSimpleSWRegister(options, false) }, bundle, pluginCtx); } return bundle; } function emitFile(asset, bundle, pluginCtx) { if (pluginCtx) { pluginCtx.emitFile({ type: "asset", fileName: asset.fileName, source: asset.source }); } else { bundle[asset.fileName] = { // @ts-expect-error: for Vite 3 support, Vite 4 has removed `isAsset` property isAsset: true, type: "asset", // vite 6 deprecation: replaced with names name: void 0, // fix vite 6 build with manifest enabled names: [], source: asset.source, fileName: asset.fileName }; } } function createAPI(ctx) { return { get disabled() { return ctx?.options?.disable; }, get pwaInDevEnvironment() { return ctx?.devEnvironment === true; }, webManifestData() { const options = ctx?.options; if (!options || options.disable || !options.manifest || ctx.devEnvironment && !ctx.options.devOptions.enabled) return void 0; let url = options.manifestFilename; let manifest; if (ctx.devEnvironment && ctx.options.devOptions.enabled === true) { url = ctx.options.devOptions.webManifestUrl ?? options.manifestFilename; manifest = generateWebManifest(options, true); } else { manifest = generateWebManifest(options, false); } return { href: `${ctx.devEnvironment ? options.base : options.buildBase}${url}`, useCredentials: ctx.options.useCredentials, toLinkTag() { return manifest; } }; }, registerSWData() { const options = ctx?.options; if (!options || options.disable || ctx.devEnvironment && !ctx.options.devOptions.enabled) return void 0; const mode = options.injectRegister; if (!mode || ctx.useImportRegister) return void 0; let type = "classic"; let script; let shouldRegisterSW = options.injectRegister === "inline" || options.injectRegister === "script" || options.injectRegister === "script-defer"; if (ctx.devEnvironment && ctx.options.devOptions.enabled === true) { type = ctx.options.devOptions.type ?? "classic"; script = generateRegisterDevSW(ctx.options.base); shouldRegisterSW = true; } else if (shouldRegisterSW) { script = generateRegisterSW(options, false); } const base = ctx.devEnvironment ? options.base : options.buildBase; return { // hint when required shouldRegisterSW, inline: options.injectRegister === "inline", mode: mode === "auto" ? "script" : mode, scope: options.scope, inlinePath: `${base}${ctx.devEnvironment ? DEV_SW_NAME : options.filename}`, registerPath: `${base}${FILE_SW_REGISTER}`, type, toScriptTag() { return script; } }; }, generateBundle(bundle, pluginCtx) { return _generateBundle(ctx, bundle, pluginCtx); }, async generateSW() { return await _generateSW(ctx); }, extendManifestEntries(fn) { const { options } = ctx; if (options.disable) return; const configField = options.strategies === "generateSW" ? "workbox" : "injectManifest"; const result = fn(options[configField].additionalManifestEntries || []); if (result != null) options[configField].additionalManifestEntries = result; }, pwaAssetsGenerator() { return ctx.pwaAssetsGenerator; } }; } // src/context.ts import { readFileSync } from "node:fs"; import { dirname as dirname2, resolve as resolve3 } from "node:path"; import { fileURLToPath as fileURLToPath2 } from "node:url"; function createContext(userOptions) { const _dirname2 = typeof __dirname !== "undefined" ? __dirname : dirname2(fileURLToPath2(import.meta.url)); const { version } = JSON.parse( readFileSync(resolve3(_dirname2, "../package.json"), "utf-8") ); return { version, userOptions, options: void 0, viteConfig: void 0, useImportRegister: false, devEnvironment: false, pwaAssetsGenerator: Promise.resolve(void 0) }; } // src/plugins/build.ts function BuildPlugin(ctx) { const transformIndexHtmlHandler2 = (html) => { const { options, useImportRegister } = ctx; if (options.disable) return html; if (options.injectRegister === "auto") options.injectRegister = useImportRegister ? null : "script"; return injectServiceWorker(html, options, false); }; return { name: "vite-plugin-pwa:build", enforce: "post", apply: "build", transformIndexHtml: { order: "post", handler(html) { return transformIndexHtmlHandler2(html); }, enforce: "post", // deprecated since Vite 4 transform(html) { return transformIndexHtmlHandler2(html); } }, async generateBundle(_, bundle) { const pwaAssetsGenerator = await ctx.pwaAssetsGenerator; if (pwaAssetsGenerator) pwaAssetsGenerator.injectManifestIcons(); return _generateBundle(ctx, bundle, this); }, closeBundle: { sequential: true, order: ctx.userOptions?.integration?.closeBundleOrder, async handler() { if (!ctx.viteConfig.build.ssr) { const pwaAssetsGenerator = await ctx.pwaAssetsGenerator; if (pwaAssetsGenerator) await pwaAssetsGenerator.generate(); if (!ctx.options.disable) await _generateSW(ctx); } } }, async buildEnd(error) { if (error) throw error; } }; } // src/plugins/dev.ts import { existsSync as existsSync2, promises as fs3, mkdirSync } from "node:fs"; import { basename, resolve as resolve4 } from "node:path"; var swDevOptions = { swUrl: DEV_SW_NAME, swDevGenerated: false, registerSWGenerated: false, workboxPaths: /* @__PURE__ */ new Map() }; function DevPlugin(ctx) { const transformIndexHtmlHandler2 = (html) => { const { options } = ctx; if (options.disable || !options.devOptions.enabled) return html; html = injectServiceWorker(html, options, true); return html.replace( "</body>", `${generateRegisterDevSW(options.base)} </body>` ); }; return { name: "vite-plugin-pwa:dev-sw", apply: "serve", transformIndexHtml: { order: "post", async handler(html) { return transformIndexHtmlHandler2(html); }, enforce: "post", // deprecated since Vite 4 async transform(html) { return transformIndexHtmlHandler2(html); } }, configureServer(server) { ctx.devEnvironment = true; const { options } = ctx; if (!options.disable && options.devOptions.enabled) { server.ws.on(DEV_READY_NAME, createWSResponseHandler(server, ctx)); if (options.manifest) { const name = options.devOptions.webManifestUrl ?? `${options.base}${options.manifestFilename}`; server.middlewares.use(async (req, res, next) => { if (req.url === name) { const pwaAssetsGenerator = await ctx.pwaAssetsGenerator; pwaAssetsGenerator?.injectManifestIcons(); if (ctx.options.manifest && !ctx.options.manifest.theme_color) { console.warn([ "", `${cyan(`PWA v${ctx.version}`)}`, `${yellow('WARNING: "theme_color" is missing from the web manifest, your application will not be able to be installed')}` ].join("\n")); } res.statusCode = 200; res.setHeader("Content-Type", "application/manifest+json"); res.write(generateWebManifestFile(options), "utf-8"); res.end(); } else { next(); } }); } } }, resolveId(id) { if (id === DEV_SW_VIRTUAL) return RESOLVED_DEV_SW_VIRTUAL; const { options } = ctx; if (!options.disable && options.devOptions.enabled && options.strategies === "injectManifest" && !options.selfDestroying) { let name = id.startsWith(options.base) ? id.slice(options.base.length) : id; if (name.length && name[0] === "/") name = name.slice(1); return name === swDevOptions.swUrl || name === options.injectManifest.swSrc ? options.injectManifest.swSrc : void 0; } return void 0; }, async load(id) { if (id === RESOLVED_DEV_SW_VIRTUAL) return generateSWHMR(); const { options, viteConfig } = ctx; if (!options.disable && options.devOptions.enabled) { if (options.strategies === "injectManifest" && !options.selfDestroying) { const swSrc = normalizePath(options.injectManifest.swSrc); if (id === swSrc) { let content = await fs3.readFile(options.injectManifest.swSrc, "utf-8"); const resolvedIP = options.injectManifest.injectionPoint; if (resolvedIP) { const ip = new RegExp(resolvedIP, "g"); const navigateFallback = options.devOptions.navigateFallback; if (navigateFallback) content = content.replace(ip, `[{ url: '${navigateFallback}' }]`); else content = content.replace(ip, "[]"); } return content; } if (swDevOptions.workboxPaths.has(id)) return await fs3.readFile(swDevOptions.workboxPaths.get(id), "utf-8"); return void 0; } if (id.endsWith(swDevOptions.swUrl)) { const globDirectory = await resolveDevDistFolder(options, viteConfig); if (!existsSync2(globDirectory)) mkdirSync(globDirectory, { recursive: true }); const swDest = resolve4(globDirectory, "sw.js"); if (!swDevOptions.swDevGenerated) { let suppressWarnings; if (options.devOptions.suppressWarnings === true) { suppressWarnings = normalizePath(resolve4(globDirectory, "suppress-warnings.js")); await fs3.writeFile(suppressWarnings, "", "utf-8"); } const globPatterns = options.devOptions.suppressWarnings === true ? ["suppress-warnings.js"] : options.workbox.globPatterns; const navigateFallback = options.workbox.navigateFallback; const { filePaths } = await generateServiceWorker( ctx.version, Object.assign( {}, options, { swDest: options.selfDestroying ? swDest : options.swDest, workbox: { ...options.workbox, navigateFallbackAllowlist: options.devOptions.navigateFallbackAllowlist ?? [/^\/$/], runtimeCaching: options.devOptions.disableRuntimeConfig ? void 0 : options.workbox.runtimeCaching, // we only include navigateFallback: add revision to remove workbox-build warning additionalManifestEntries: navigateFallback ? [{ url: navigateFallback, revision: Math.random().toString(32) }] : void 0, cleanupOutdatedCaches: true, globDirectory: normalizePath(globDirectory), globPatterns, swDest: normalizePath(swDest) } } ), viteConfig ); filePaths.forEach((we) => { const name = basename(we); if (name !== "sw.js") swDevOptions.workboxPaths.set(normalizePath(`${options.base}${name}`), we); }); if (suppressWarnings) { swDevOptions.workboxPaths.set( normalizePath(`${options.base}${basename(suppressWarnings)}`), suppressWarnings ); } swDevOptions.swDevGenerated = true; } return await fs3.readFile(swDest, "utf-8"); } if (id.startsWith(options.base)) { const key = normalizePath(id); if (swDevOptions.workboxPaths.has(key)) return await fs3.readFile(swDevOptions.workboxPaths.get(key), "utf-8"); } else if (options.base !== "/") { const key = normalizePath(`${options.base}${id.length > 0 && id[0] === "/" ? id.slice(1) : id}`); if (swDevOptions.workboxPaths.has(key)) return await fs3.readFile(swDevOptions.workboxPaths.get(key), "utf-8"); } } } }; } async function resolveDevDistFolder(options, viteConfig) { return options.devOptions.resolveTempFolder ? await options.devOptions.resolveTempFolder() : resolve4(viteConfig.root, "dev-dist"); } async function createDevRegisterSW(options, viteConfig) { if (options.injectRegister === "script" || options.injectRegister === "script-defer") { const devDist = await resolveDevDistFolder(options, viteConfig); if (!existsSync2(devDist)) mkdirSync(devDist, { recursive: true }); const registerSW = resolve4(devDist, FILE_SW_REGISTER); if (!swDevOptions.registerSWGenerated) { await fs3.writeFile(registerSW, generateSimpleSWRegister(options, true), { encoding: "utf8" }); swDevOptions.registerSWGenerated = true; } swDevOptions.workboxPaths.set(normalizePath(`${options.base}${FILE_SW_REGISTER}`), registerSW); } } function createWSResponseHandler(server, ctx) { return async () => { const { options, useImportRegister } = ctx; const { injectRegister, scope, base } = options; if (!useImportRegister && injectRegister) { if (injectRegister === "auto") options.injectRegister = "script"; await createDevRegisterSW(options, ctx.viteConfig); server.ws.send({ type: "custom", event: DEV_REGISTER_SW_NAME, data: { mode: options.injectRegister, scope, inlinePath: `${base}${DEV_SW_NAME}`, registerPath: `${base}${FILE_SW_REGISTER}`, swType: options.devOptions.type } }); } }; } // src/plugins/info.ts function InfoPlugin(ctx, api) { return { name: "vite-plugin-pwa:info", enforce: "post", resolveId(id) { if (id === PWA_INFO_VIRTUAL) return RESOLVED_PWA_INFO_VIRTUAL; return void 0; }, load(id) { if (id === RESOLVED_PWA_INFO_VIRTUAL) return generatePwaInfo(ctx, api); } }; } function generatePwaInfo(ctx, api) { const webManifestData = api.webManifestData(); if (!webManifestData) return "export const pwaInfo = undefined;"; const { href, useCredentials, toLinkTag } = webManifestData; const registerSWData = api.registerSWData(); const entry = { pwaInDevEnvironment: api.pwaInDevEnvironment, webManifest: { href, useCredentials, linkTag: toLinkTag() } }; if (registerSWData) { const scriptTag = registerSWData.toScriptTag(); if (scriptTag) { const { inline, mode, inlinePath, registerPath, type, scope } = registerSWData; entry.registerSW = { inline, mode, inlinePath, registerPath, type, scope, scriptTag }; } } return `export const pwaInfo = ${JSON.stringify(entry)};`; } // src/options.ts import fs4 from "node:fs"; import { extname, resolve as resolve5 } from "node:path"; import process from "node:process"; // src/pwa-assets/options.ts function resolvePWAAssetsOptions(options) { if (!options) return false; const { disabled, preset = "minimal-2023", image = "public/favicon.svg", htmlPreset = "2023", overrideManifestIcons = false, includeHtmlHeadLinks = true, injectThemeColor = true, integration } = options ?? {}; const resolvedConfiguration = { disabled: true, config: false, preset: false, images: [image], htmlPreset, overrideManifestIcons, includeHtmlHeadLinks, injectThemeColor, integration }; if (disabled === true) return resolvedConfiguration; if ("config" in options && !!options.config) { resolvedConfiguration.disabled = false; resolvedConfiguration.config = options.config; return resolvedConfiguration; } if (preset === false) return resolvedConfiguration; resolvedConfiguration.disabled = false; resolvedConfiguration.preset = preset; return resolvedConfiguration; } // src/options.ts function resolveSwPaths(injectManifest, root, srcDir, outDir, filename) { const swSrc = resolve5(root, srcDir, filename); if (injectManifest && extname(filename) === ".ts" && fs4.existsSync(swSrc)) { const useFilename = `${filename.substring(0, filename.lastIndexOf("."))}.js`; return { swSrc, swDest: resolve5(root, outDir, useFilename), useFilename }; } return { swSrc, swDest: resolve5(root, outDir, filename) }; } async function resolveOptions(ctx) { const { userOptions: options, viteConfig } = ctx; const root = viteConfig.root; const pkg = fs4.existsSync("package.json") ? JSON.parse(fs4.readFileSync("package.json", "utf-8")) : {}; const { // prevent tsup replacing `process.env` // eslint-disable-next-line dot-notation mode = process["env"]["NODE_ENV"] || "production", srcDir = "public", outDir = viteConfig.build.outDir || "dist", injectRegister = "auto", registerType = "prompt", filename = "sw.js", manifestFilename = "manifest.webmanifest", strategies = "generateSW", minify = true, base = viteConfig.base, includeAssets = void 0, includeManifestIcons = true, useCredentials = false, disable = false, devOptions = { enabled: false, type: "classic", suppressWarnings: false }, selfDestroying = false, integration = {}, buildBase, pwaAssets, showMaximumFileSizeToCacheInBytesWarning = false } = options; const basePath = resolveBasePath(base); const { swSrc, swDest, useFilename } = resolveSwPaths( strategies === "injectManifest", root, srcDir, outDir, filename ); const outDirRoot = resolve5(root, outDir); const scope = options.scope || basePath; let assetsDir = slash(viteConfig.build.assetsDir ?? "assets"); if (assetsDir[assetsDir.length - 1] !== "/") assetsDir += "/"; const dontCacheBustURLsMatching = new RegExp(`^${assetsDir.replace(/^\.*\//, "")}`); const defaultWorkbox = { swDest, globDirectory: outDirRoot, offlineGoogleAnalytics: false, cleanupOutdatedCaches: true, dontCacheBustURLsMatching, mode, navigateFallback: "index.html" }; const defaultInjectManifest = { swSrc, swDest, globDirectory: outDirRoot, dontCacheBustURLsMatching, injectionPoint: "self.__WB_MANIFEST" }; const defaultManifest = { name: pkg.name, short_name: pkg.name, description: pkg.description, start_url: basePath, display: "standalone", background_color: "#ffffff", theme_color: "#42b883", lang: "en", scope }; const workbox = Object.assign({}, defaultWorkbox, options.workbox || {}); const manifest = typeof options.manifest === "boolean" && !options.manifest ? false : Object.assign({}, defaultManifest, options.manifest || {}); const { vitePlugins = defaultInjectManifestVitePlugins, plugins, rollupOptions = {}, rollupFormat = "es", target = viteConfig.build.target, minify: minifySW = viteConfig.build.minify, sourcemap = viteConfig.build.sourcemap, enableWorkboxModulesLogs, buildPlugins, envOptions = {}, ...userInjectManifest } = options.injectManifest || {}; const injectManifest = Object.assign({}, defaultInjectManifest, userInjectManifest); if ((injectRegister === "auto" || injectRegister == null) && registerType === "autoUpdate") { workbox.skipWaiting = true; workbox.clientsClaim = true; } if (strategies === "generateSW" && workbox.sourcemap === void 0) { const sourcemap2 = viteConfig.build?.sourcemap; workbox.sourcemap = sourcemap2 === true || sourcemap2 === "inline" || sourcemap2 === "hidden"; } if (devOptions.enabled && viteConfig.command === "serve") { if (strategies === "generateSW") devOptions.type = "classic"; } else { devOptions.enabled = false; devOptions.type = "classic"; } if (manifest) { if (manifest.icons) { manifest.icons = manifest.icons.map((icon) => { if (icon.purpose && Array.isArray(icon.purpose)) icon.purpose = icon.purpose.join(" "); return icon; }); } if (manifest.shortcuts) { manifest.shortcuts.forEach((shortcut) => { if (shortcut.icons) { shortcut.icons = shortcut.icons.map((icon) => { if (icon.purpose && Array.isArray(icon.purpose)) icon.purpose = icon.purpose.join(" "); return icon; }); } }); } } const { envDir = viteConfig.envDir, envPrefix = viteConfig.envPrefix } = envOptions; const resolvedVitePWAOptions = { base: basePath, mode, swSrc, swDest, srcDir, outDir, injectRegister, registerType, filename: useFilename || filename, manifestFilename, strategies, workbox, manifest, useCredentials, injectManifest, scope, minify, includeAssets, includeManifestIcons, disable, integration, devOptions, rollupFormat, vitePlugins, buildPlugins, selfDestroying, buildBase: buildBase ?? basePath, injectManifestRollupOptions: { plugins, rollupOptions, format: rollupFormat }, injectManifestBuildOptions: { target, minify: minifySW, sourcemap, enableWorkboxModulesLogs }, injectManifestEnvOptions: { envDir, envPrefix }, pwaAssets: resolvePWAAssetsOptions(pwaAssets), throwMaximumFileSizeToCacheInBytes: !showMaximumFileSizeToCacheInBytesWarning }; const calculateHash = !resolvedVitePWAOptions.disable && (resolvedVitePWAOptions.manifest || resolvedVitePWAOptions.includeAssets) && (viteConfig.command === "build" || resolvedVitePWAOptions.devOptions.enabled); if (calculateHash) await configureStaticAssets(resolvedVitePWAOptions, viteConfig); return resolvedVitePWAOptions; } // src/plugins/main.ts function MainPlugin(ctx, api) { return { name: "vite-plugin-pwa", enforce: "pre", config() { return { ssr: { // TODO: remove until workbox-window support native ESM noExternal: ["workbox-window"] } }; }, async configResolved(config) { ctx.useImportRegister = false; ctx.viteConfig = config; ctx.userOptions?.integration?.configureOptions?.(config, ctx.userOptions); ctx.options = await resolveOptions(ctx); if (ctx.options.pwaAssets && !ctx.options.pwaAssets.disabled) { ctx.pwaAssetsGenerator = import("./generator-Q3R7VIEH.js").then(({ loadInstructions }) => loadInstructions(ctx)).catch((e) => { console.error([ "", cyan(`PWA v${ctx.version}`), yellow("WARNING: you must install the following dev dependencies to use the PWA assets generator:"), yellow('- "@vite-pwa/assets-generator"'), yellow('- "sharp" (should be installed when installing @vite-pwa/assets-generator)'), yellow('- "sharp-ico" (should be installed when installing @vite-pwa/assets-generator)') ].join("\n"), e); return Promise.resolve(void 0); }); } }, resolveId(id) { return VIRTUAL_MODULES.includes(id) ? VIRTUAL_MODULES_RESOLVE_PREFIX + id : void 0; }, load(id) { if (id.startsWith(VIRTUAL_MODULES_RESOLVE_PREFIX)) id = id.slice(VIRTUAL_MODULES_RESOLVE_PREFIX.length); else return; if (VIRTUAL_MODULES.includes(id)) { ctx.useImportRegister = true; if (ctx.viteConfig.command === "serve" && ctx.options.devOptions.enabled) { return generateRegisterSW2( { ...ctx.options, filename: swDevOptions.swUrl }, "build", VIRTUAL_MODULES_MAP[id] ); } else { return generateRegisterSW2( ctx.options, !ctx.options.disable && ctx.viteConfig.command === "build" ? "build" : "dev", VIRTUAL_MODULES_MAP[id] ); } } }, api }; } // src/plugins/pwa-assets.ts function AssetsPlugin(ctx) { return { name: "vite-plugin-pwa:pwa-assets", enforce: "post", transformIndexHtml: { order: "post", async handler(html) { return await transformIndexHtmlHandler(html, ctx); }, enforce: "post", // deprecated since Vite 4 async transform(html) { return await transformIndexHtmlHandler(html, ctx); } }, resolveId(id) { switch (true) { case id === PWA_ASSETS_HEAD_VIRTUAL: return RESOLVED_PWA_ASSETS_HEAD_VIRTUAL; case id === PWA_ASSETS_ICONS_VIRTUAL: return RESOLVED_PWA_ASSETS_ICONS_VIRTUAL; default: return void 0; } }, async load(id) { if (id === RESOLVED_PWA_ASSETS_HEAD_VIRTUAL) { const pwaAssetsGenerator = await ctx.pwaAssetsGenerator; const head = pwaAssetsGenerator?.resolveHtmlAssets() ?? { links: [], themeColor: void 0 }; return `export const pwaAssetsHead = ${JSON.stringify(head)}`; } if (id === RESOLVED_PWA_ASSETS_ICONS_VIRTUAL) { const pwaAssetsGenerator = await ctx.pwaAssetsGenerator; const icons = extractIcons(pwaAssetsGenerator?.instructions()); return `export const pwaAssetsIcons = ${JSON.stringify(icons)}`; } }, async handleHotUpdate({ file, server }) { const pwaAssetsGenerator = await ctx.pwaAssetsGenerator; if (await pwaAssetsGenerator?.checkHotUpdate(file)) { const modules = []; const head = server.moduleGraph.getModuleById(RESOLVED_PWA_ASSETS_HEAD_VIRTUAL); head && modules.push(head); const icons = server.moduleGraph.getModuleById(RESOLVED_PWA_ASSETS_ICONS_VIRTUAL); icons && modules.push(icons); if (modules) return modules; server.ws.send({ type: "full-reload" }); return []; } }, configureServer(server) { server.ws.on(DEV_READY_NAME, createWSResponseHandler2(ctx, server)); server.middlewares.use(async (req, res, next) => { const url = req.url; if (!url) return next(); if (!/\.(?:ico|png|svg|webp)$/.test(url)) return next(); const pwaAssetsGenerator = await ctx.pwaAssetsGenerator; if (!pwaAssetsGenerator) return next(); const icon = await pwaAssetsGenerator.findIconAsset(url); if (!icon) return next(); if (icon.age > 0) { const ifModifiedSince = req.headers["if-modified-since"] ?? req.headers["If-Modified-Since"]; const useIfModifiedSince = ifModifiedSince ? Array.isArray(ifModifiedSince) ? ifModifiedSince[0] : ifModifiedSince : void 0; if (useIfModifiedSince && new Date(icon.lastModified).getTime() / 1e3 >= new Date(useIfModifiedSince).getTime() / 1e3) { res.statusCode = 304; res.end(); return; } } const buffer = await icon.buffer; res.setHeader("Age", icon.age / 1e3); res.setHeader("Content-Type", icon.mimeType); res.setHeader("Content-Length", buffer.length); res.setHeader("Last-Modified", new Date(icon.lastModified).toUTCString()); res.statusCode = 200; res.end(buffer); }); } }; } async function transformIndexHtmlHandler(html, ctx) { if (ctx.devEnvironment && ctx.options.devOptions.enabled) return html; const pwaAssetsGenerator = await ctx.pwaAssetsGenerator; if (!pwaAssetsGenerator) return html; return pwaAssetsGenerator.transformIndexHtml(html); } function createWSResponseHandler2(ctx, server) { return async () => { const pwaAssetsGenerator = await ctx.pwaAssetsGenerator; if (pwaAssetsGenerator) { const data = pwaAssetsGenerator.resolveHtmlAssets(); server.ws.send({ type: "custom", event: DEV_PWA_ASSETS_NAME, data }); } }; } // src/cache.ts var cachePreset = [ { urlPattern: /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i, handler: "CacheFirst", options: { cacheName: "google-fonts", expiration: { maxEntries: 4, maxAgeSeconds: 365 * 24 * 60 * 60 // 365 days } } }, { urlPattern: /\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i, handler: "StaleWhileRevalidate", options: { cacheName: "static-font-assets", expiration: { maxEntries: 4, maxAgeSeconds: 7 * 24 * 60 * 60 // 7 days } } }, { urlPattern: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i, handler: "StaleWhileRevalidate", options: { cacheName: "static-image-assets", expiration: { maxEntries: 64, maxAgeSeconds: 24 * 60 * 60 // 24 hours } } }, { urlPattern: /\.js$/i, handler: "StaleWhileRevalidate", options: { cacheName: "static-js-assets", expiration: { maxEntries: 32, maxAgeSeconds: 24 * 60 * 60 // 24 hours } } }, { urlPattern: /\.(?:css|less)$/i, handler: "StaleWhileRevalidate", options: { cacheName: "static-style-assets", expiration: { maxEntries: 32, maxAgeSeconds: 24 * 60 * 60 // 24 hours } } }, { urlPattern: /\.(?:json|xml|csv)$/i, handler: "NetworkFirst", options: { cacheName: "static-data-assets", expiration: { maxEntries: 32, maxAgeSeconds: 24 * 60 * 60 // 24 hours } } }, { urlPattern: /\/api\/.*$/i, handler: "NetworkFirst", method: "GET", options: { cacheName: "apis", expiration: { maxEntries: 16, maxAgeSeconds: 24 * 60 * 60 // 24 hours }, networkTimeoutSeconds: 10 // fall back to cache if api does not response within 10 seconds } }, { // eslint-disable-next-line regexp/no-useless-flag urlPattern: /.*/i, handler: "NetworkFirst", options: { cacheName: "others", expiration: { maxEntries: 32, maxAgeSeconds: 24 * 60 * 60 // 24 hours }, networkTimeoutSeconds: 10 } } ]; // src/index.ts function VitePWA(userOptions = {}) { const ctx = createContext(userOptions); const api = createAPI(ctx); return [ MainPlugin(ctx, api), InfoPlugin(ctx, api), BuildPlugin(ctx), DevPlugin(ctx), AssetsPlugin(ctx) ]; } export { VitePWA, cachePreset, defaultInjectManifestVitePlugins };