UNPKG

@mangadex-pub/vite-pwa-nuxt

Version:
206 lines (201 loc) 8.3 kB
import { defineNuxtModule, createResolver, addPluginTemplate, addComponent, extendWebpackConfig } from '@nuxt/kit'; import { VitePWA } from 'vite-plugin-pwa'; import { resolve } from 'pathe'; import { writeFile, lstat } from 'node:fs/promises'; function configurePWAOptions(options, nuxt) { if (!options.outDir) { const publicDir = nuxt.options.nitro?.output?.publicDir; options.outDir = publicDir ? resolve(publicDir) : resolve(nuxt.options.buildDir, "../.output/public"); } if (options.devOptions?.enabled) options.devOptions.resolveTempFolder = () => resolve(nuxt.options.buildDir, "dev-sw-dist"); let config; if (options.strategies === "injectManifest") { options.injectManifest = options.injectManifest ?? {}; config = options.injectManifest; } else { options.workbox = options.workbox ?? {}; if (options.registerType === "autoUpdate" && (options.client?.registerPlugin || options.injectRegister === "script" || options.injectRegister === "inline")) { options.workbox.clientsClaim = true; options.workbox.skipWaiting = true; } if (nuxt.options.dev) { options.workbox.navigateFallback = options.workbox.navigateFallback ?? nuxt.options.app.baseURL ?? "/"; if (options.devOptions?.enabled && !options.devOptions.navigateFallbackAllowlist) options.devOptions.navigateFallbackAllowlist = [nuxt.options.app.baseURL ? new RegExp(nuxt.options.app.baseURL) : /\//]; } config = options.workbox; } if (!nuxt.options.dev) config.manifestTransforms = [createManifestTransform(nuxt.options.app.baseURL ?? "/")]; } function createManifestTransform(base) { return async (entries) => { entries.filter((e) => e && e.url.endsWith(".html")).forEach((e) => { const url = e.url.startsWith("/") ? e.url.slice(1) : e.url; if (url === "index.html") { e.url = base; } else { const parts = url.split("/"); parts[parts.length - 1] = parts[parts.length - 1].replace(/\.html$/, ""); e.url = parts.length > 1 ? parts.slice(0, parts.length - 1).join("/") : parts[0]; } }); return { manifest: entries, warnings: [] }; }; } async function regeneratePWA(dir, path, api) { if (!api) return; await api.generateSW(); if (path) await writeWebManifest(dir, path, api); } async function isFile(path) { try { const stats = await lstat(path); return stats.isFile(); } catch { return false; } } async function writeWebManifest(dir, path, api) { const exists = await isFile(path); if (exists) return; const manifest = api.generateBundle({})?.[path]; if (manifest && "source" in manifest) await writeFile(resolve(dir, path), manifest.source, "utf-8"); } const module = defineNuxtModule({ meta: { name: "pwa", configKey: "pwa" }, defaults: (nuxt) => ({ base: nuxt.options.app.baseURL, scope: nuxt.options.app.baseURL, injectRegister: false, includeManifestIcons: false, registerPlugin: true, writePlugin: false, client: { registerPlugin: true, installPrompt: false, periodicSyncForUpdates: 0 } }), async setup(options, nuxt) { const resolver = createResolver(import.meta.url); let vitePwaClientPlugin; const resolveVitePluginPWAAPI = () => { return vitePwaClientPlugin?.api; }; const client = options.client ?? { registerPlugin: true, installPrompt: false, periodicSyncForUpdates: 0 }; if (client.registerPlugin) { addPluginTemplate({ src: resolver.resolve("../templates/pwa.client.ts"), write: nuxt.options.dev || options.writePlugin, options: { periodicSyncForUpdates: typeof client.periodicSyncForUpdates === "number" ? client.periodicSyncForUpdates : 0, installPrompt: typeof client.installPrompt === "undefined" || client.installPrompt === false ? void 0 : client.installPrompt === true || client.installPrompt.trim() === "" ? "vite-pwa:hide-install" : client.installPrompt.trim() } }); } await addComponent({ name: "VitePwaManifest", filePath: resolver.resolve("./runtime/VitePwaManifest") }); nuxt.hook("prepare:types", ({ references }) => { references.push({ types: "vite-plugin-pwa/client" }); }); nuxt.hook("nitro:init", (nitro) => { options.outDir = nitro.options.output.publicDir; options.injectManifest = options.injectManifest || {}; options.injectManifest.globDirectory = nitro.options.output.publicDir; }); nuxt.hook("vite:extend", ({ config }) => { const plugin = config.plugins?.find((p) => p && typeof p === "object" && "name" in p && p.name === "vite-plugin-pwa"); if (plugin) throw new Error("Remove vite-plugin-pwa plugin from Vite Plugins entry in Nuxt config file!"); }); nuxt.hook("vite:extendConfig", async (viteInlineConfig, { isClient }) => { viteInlineConfig.plugins = viteInlineConfig.plugins || []; const plugin = viteInlineConfig.plugins.find((p) => p && typeof p === "object" && "name" in p && p.name === "vite-plugin-pwa"); if (plugin) throw new Error("Remove vite-plugin-pwa plugin from Vite Plugins entry in Nuxt config file!"); configurePWAOptions(options, nuxt); const plugins = VitePWA(options); viteInlineConfig.plugins.push(plugins); if (isClient) vitePwaClientPlugin = plugins.find((p) => p.name === "vite-plugin-pwa"); }); extendWebpackConfig(() => { throw new Error("Webpack is not supported: @vite-pwa/nuxt module can only be used with Vite!"); }); if (nuxt.options.dev) { const webManifest = `${nuxt.options.app.baseURL}${options.devOptions?.webManifestUrl ?? options.manifestFilename ?? "manifest.webmanifest"}`; const devSw = `${nuxt.options.app.baseURL}dev-sw.js?dev-sw`; const workbox = `${nuxt.options.app.baseURL}workbox-`; const emptyHandle = (_req, _res, next) => { next(); }; nuxt.hook("vite:serverCreated", (viteServer, { isServer }) => { if (isServer) return; viteServer.middlewares.stack.push({ route: webManifest, handle: emptyHandle }); viteServer.middlewares.stack.push({ route: devSw, handle: emptyHandle }); }); if (!options.strategies || options.strategies === "generateSW") { nuxt.hook("vite:serverCreated", (viteServer, { isServer }) => { if (isServer) return; viteServer.middlewares.stack.push({ route: workbox, handle: emptyHandle }); }); nuxt.hook("close", async () => { }); } } else { if (!options.disable && options.registerWebManifestInRouteRules) { nuxt.hook("nitro:config", async (nitroConfig) => { nitroConfig.routeRules = nitroConfig.routeRules || {}; let swName = options.filename || "sw.js"; if (options.strategies === "injectManifest" && swName.endsWith(".ts")) swName = swName.replace(/\.ts$/, ".js"); nitroConfig.routeRules[`${nuxt.options.app.baseURL}${swName}`] = { headers: { "Cache-Control": "public, max-age=0, must-revalidate" } }; if (options.manifest) { nitroConfig.routeRules[`${nuxt.options.app.baseURL}${options.manifestFilename ?? "manifest.webmanifest"}`] = { headers: { "Content-Type": "application/manifest+json", "Cache-Control": "public, max-age=0, must-revalidate" } }; } }); } nuxt.hook("nitro:init", (nitro) => { nitro.hooks.hook("rollup:before", async () => { await regeneratePWA( options.outDir, !options.disable && options.manifest ? options.manifestFilename || "manifest.webmanifest" : void 0, resolveVitePluginPWAAPI() ); }); }); if (nuxt.options._generate) { nuxt.hook("close", async () => { await regeneratePWA( options.outDir, !options.disable && options.manifest ? options.manifestFilename || "manifest.webmanifest" : void 0, resolveVitePluginPWAAPI() ); }); } } } }); export { module as default };