@vite-pwa/nuxt
Version:
Zero-config PWA for Nuxt 3
735 lines (719 loc) • 27.2 kB
JavaScript
import { addPluginTemplate, addTypeTemplate, addImports, createResolver, getNuxtVersion, resolveAlias, addPlugin, addComponent, extendWebpackConfig, addDevServerHandler, defineNuxtModule } from '@nuxt/kit';
import { lstat, writeFile, mkdir } from 'node:fs/promises';
import { join } from 'node:path';
import { VitePWA } from 'vite-plugin-pwa';
import { createHash } from 'node:crypto';
import { createReadStream } from 'node:fs';
import { resolve } from 'pathe';
const version = "1.1.0";
function configurePWAOptions(ctx, nitroConfig) {
const { nuxt3_8, options, nuxt } = ctx;
if (!options.outDir) {
const publicDir = nitroConfig.output?.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.strategies = "generateSW";
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) {
const baseURL = nuxt.options.app.baseURL;
options.devOptions.navigateFallbackAllowlist = [baseURL ? new RegExp(`^${baseURL.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")}$`) : /^\/$/];
}
}
if (!("navigateFallback" in options.workbox))
options.workbox.navigateFallback = nuxt.options.app.baseURL ?? "/";
config = options.workbox;
}
let buildAssetsDir = nuxt.options.app.buildAssetsDir ?? "_nuxt/";
if (buildAssetsDir[0] === "/")
buildAssetsDir = buildAssetsDir.slice(1);
if (buildAssetsDir[buildAssetsDir.length - 1] !== "/")
buildAssetsDir += "/";
if (!("dontCacheBustURLsMatching" in config))
config.dontCacheBustURLsMatching = new RegExp(buildAssetsDir);
if (nuxt.options.experimental.payloadExtraction) {
const enableGlobPatterns = nuxt.options.nitro.static || nuxt.options._generate || (!!nitroConfig.prerender?.routes?.length || Object.values(nitroConfig.routeRules ?? {}).some((r) => r.prerender));
if (enableGlobPatterns) {
config.globPatterns = config.globPatterns ?? [];
config.globPatterns.push("**/_payload.json");
if (options.strategies === "generateSW" && options.experimental?.enableWorkboxPayloadQueryParams) {
options.workbox.runtimeCaching = options.workbox.runtimeCaching ?? [];
options.workbox.runtimeCaching.push({
urlPattern: /\/_payload\.json\?/,
handler: "NetworkOnly",
options: {
plugins: [{
/* this callback will be called when the fetch call fails */
handlerDidError: async ({ request }) => {
const url = new URL(request.url);
url.search = "";
return Response.redirect(url.href, 302);
},
/* this callback will prevent caching the response */
cacheWillUpdate: async () => null
}]
}
});
}
}
}
let appManifestFolder;
if (nuxt3_8 && nuxt.options.experimental.appManifest) {
config.globPatterns = config.globPatterns ?? [];
appManifestFolder = `${buildAssetsDir}builds/`;
config.globPatterns.push(`${appManifestFolder}**/*.json`);
}
if (!nuxt.options.dev && !config.manifestTransforms)
config.manifestTransforms = [createManifestTransform(nuxt.options.app.baseURL ?? "/", options.outDir, appManifestFolder)];
if (options.pwaAssets) {
options.pwaAssets.integration = {
baseUrl: nuxt.options.app.baseURL ?? "/",
publicDir: ctx.publicDirFolder,
outDir: options.outDir
};
}
const integration = options.integration ?? {};
const {
beforeBuildServiceWorker: original,
...rest
} = integration;
options.integration = {
...rest,
async beforeBuildServiceWorker(resolvedPwaOptions) {
await original?.(resolvedPwaOptions);
await nuxt.callHook("pwa:beforeBuildServiceWorker", resolvedPwaOptions);
}
};
}
function createManifestTransform(base, publicFolder, appManifestFolder) {
return async (entries) => {
entries.filter((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];
}
});
if (appManifestFolder) {
const regExp = /(\/)?[0-9a-f]{8}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{12}\.json$/i;
entries.filter((e) => e.url.startsWith(appManifestFolder) && regExp.test(e.url)).forEach((e) => {
e.revision = null;
});
const latest = `${appManifestFolder}latest.json`;
const latestJson = resolve(publicFolder, latest);
const data = await lstat(latestJson).catch(() => void 0);
if (data?.isFile()) {
const revision = await new Promise((resolve2, reject) => {
const cHash = createHash("MD5");
const stream = createReadStream(latestJson);
stream.on("error", (err) => {
reject(err);
});
stream.on("data", (chunk) => cHash.update(chunk));
stream.on("end", () => {
resolve2(cHash.digest("hex"));
});
});
const latestEntry = entries.find((e) => e.url === latest);
if (latestEntry)
latestEntry.revision = revision;
else
entries.push({ url: latest, revision, size: data.size });
} else {
entries = entries.filter((e) => e.url !== latest);
}
}
return { manifest: entries, warnings: [] };
};
}
function addPwaTypeTemplate(filename, isNuxt4, content) {
if (content?.length) {
return addTypeTemplate({
write: true,
filename: `pwa-icons/${filename}.d.ts`,
getContents: () => content
}).dst;
} else {
return addTypeTemplate({
write: true,
getContents: () => generatePwaImageType(filename, isNuxt4),
filename: `pwa-icons/${filename}.d.ts`
}).dst;
}
}
function pwaIcons(types) {
return `// Generated by @vite-pwa/nuxt
import type { AppleSplashScreenLink, FaviconLink, HtmlLink, IconAsset } from '@vite-pwa/assets-generator/api'
export interface PWAAssetIconImage {
width?: number
height?: number
key: string
src: string
}
export type PWAAssetIcon<T extends HtmlLink> = Omit<IconAsset<T>, 'buffer'> & {
asImage: PWAAssetIconImage
}
export interface PWAIcons {
transparent: Record<${generateTypes(types?.transparent)}, PWAAssetIcon<HtmlLink>>
maskable: Record<${generateTypes(types?.maskable)}, PWAAssetIcon<HtmlLink>>
favicon: Record<${generateTypes(types?.favicon)}, PWAAssetIcon<FaviconLink>>
apple: Record<${generateTypes(types?.apple)}, PWAAssetIcon<HtmlLink>>
appleSplashScreen: Record<${generateTypes(types?.appleSplashScreen)}, PWAAssetIcon<AppleSplashScreenLink>>
}
declare module '#app' {
interface NuxtApp {
$pwaIcons?: PWAIcons
}
}
declare module 'vue' {
interface ComponentCustomProperties {
$pwaIcons?: PWAIcons
}
}
export {}
`;
}
function generatePwaImageType(filename, isNuxt4, names) {
const propsName = `${filename}Props`;
if (isNuxt4) {
return `// Generated by @vite-pwa/nuxt
export interface ${propsName} {
image: ${generateTypes(names)}
alt?: string
width?: number
height?: number
crossorigin?: '' | 'anonymous' | 'use-credentials'
loading?: 'lazy' | 'eager'
decoding?: 'async' | 'auto' | 'sync'
nonce?: string
}
`;
}
return `// Generated by @vite-pwa/nuxt
export interface ${propsName} {
image: ${generateTypes(names)}
alt?: string
width?: number
height?: number
crossorigin?: '' | 'anonymous' | 'use-credentials'
loading?: 'lazy' | 'eager'
decoding?: 'async' | 'auto' | 'sync'
nonce?: string
}
type __VLS_NonUndefinedable<T> = T extends undefined ? never : T
type __VLS_TypePropsToRuntimeProps<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? {
type: import('vue').PropType<__VLS_NonUndefinedable<T[K]>>
} : {
type: import('vue').PropType<T[K]>
required: true
}
}
declare const _default: import('vue').DefineComponent<__VLS_TypePropsToRuntimeProps<${propsName}>, {}, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<__VLS_TypePropsToRuntimeProps<${propsName}>>>, {}, {}>
export default _default
`;
}
function generateTypes(types) {
return types?.length ? types.map((name) => `'${name}'`).join(" | ") : "string";
}
function addPWAIconsPluginTemplate(pwaAssetsEnabled) {
if (pwaAssetsEnabled) {
addPluginTemplate({
filename: "pwa-icons-plugin.ts",
name: "vite-pwa:nuxt:pwa-icons-plugin",
write: true,
getContents: () => `// Generated by @vite-pwa/nuxt
import { defineNuxtPlugin } from '#imports'
import { pwaAssetsIcons } from 'virtual:pwa-assets/icons'
import type { PWAAssetIcon, PWAIcons } from '#build/pwa-icons/pwa-icons'
export default defineNuxtPlugin(() => {
return {
provide: {
pwaIcons: {
transparent: configureEntry('transparent'),
maskable: configureEntry('maskable'),
favicon: configureEntry('favicon'),
apple: configureEntry('apple'),
appleSplashScreen: configureEntry('appleSplashScreen')
} satisfies PWAIcons
}
}
})
function configureEntry<K extends keyof PWAIcons>(key: K) {
return Object.values(pwaAssetsIcons[key] ?? {}).reduce((acc, icon) => {
const entry: PWAAssetIcon<any> = {
...icon,
asImage: {
src: icon.url,
key: \`\${key}-\${icon.name}\`
}
}
if (icon.width && icon.height) {
entry.asImage.width = icon.width
entry.asImage.height = icon.height
}
;(acc as unknown as any)[icon.name] = entry
return acc
}, {} as PWAIcons[typeof key])
}
`
});
} else {
addPluginTemplate({
filename: "pwa-icons-plugin.ts",
name: "vite-pwa:nuxt:pwa-icons-plugin",
write: true,
getContents: () => `// Generated by @vite-pwa/nuxt
import { defineNuxtPlugin } from '#imports'
import type { PWAIcons } from '#build/pwa-icons/pwa-icons'
export default defineNuxtPlugin(() => {
return {
provide: {
pwaIcons: {
transparent: {},
maskable: {},
favicon: {},
apple: {},
appleSplashScreen: {}
} satisfies PWAIcons
}
}
})
`
});
}
}
async function registerPwaIconsTypes(ctx, runtimeDir) {
const { options, nuxt, resolver } = ctx;
const pwaAssets = options.pwaAssets && !options.pwaAssets.disabled;
let dts;
if (pwaAssets) {
try {
const { preparePWAIconTypes } = await import('../chunks/pwa-icons.mjs');
dts = await preparePWAIconTypes(ctx);
} catch {
dts = void 0;
}
}
nuxt.options.alias["#pwa"] = resolver.resolve(runtimeDir, "composables/index");
nuxt.options.build.transpile.push("#pwa");
addImports([
"usePWA",
"useTransparentPwaIcon",
"useMaskablePwaIcon",
"useFaviconPwaIcon",
"useApplePwaIcon",
"useAppleSplashScreenPwaIcon"
].map((key) => ({
name: key,
as: key,
from: resolver.resolve(runtimeDir, "composables/index")
})));
const templates = [];
const isNuxt4 = ctx.nuxt4;
const dtsContent = dts?.dts;
if (dtsContent) {
templates.push(addTypeTemplate({
write: true,
filename: "pwa-icons/pwa-icons.d.ts",
getContents: () => dtsContent
}).dst);
} else {
templates.push(addTypeTemplate({
write: true,
filename: "pwa-icons/pwa-icons.d.ts",
getContents: () => pwaIcons()
}).dst);
}
templates.push(addPwaTypeTemplate("PwaTransparentImage", isNuxt4, dts?.transparent));
templates.push(addPwaTypeTemplate("PwaMaskableImage", isNuxt4, dts?.maskable));
templates.push(addPwaTypeTemplate("PwaFaviconImage", isNuxt4, dts?.favicon));
templates.push(addPwaTypeTemplate("PwaAppleImage", isNuxt4, dts?.apple));
templates.push(addPwaTypeTemplate("PwaAppleSplashScreenImage", isNuxt4, dts?.appleSplashScreen));
if (ctx.nuxt4) {
ctx.nuxt.hook("prepare:types", (context) => {
const nodeReferences = context.nodeReferences;
for (const path of templates) {
nodeReferences.push({ path });
}
});
}
return !!dts;
}
async function regeneratePWA(_dir, pwaAssets, api) {
if (pwaAssets) {
const pwaAssetsGenerator = await api?.pwaAssetsGenerator();
if (pwaAssetsGenerator)
await pwaAssetsGenerator.generate();
}
if (!api || api.disabled)
return;
await api.generateSW();
}
async function writeWebManifest(dir, path, api, pwaAssets) {
if (pwaAssets) {
const pwaAssetsGenerator = await api.pwaAssetsGenerator();
if (pwaAssetsGenerator)
pwaAssetsGenerator.injectManifestIcons();
}
const manifest = api.generateBundle({})?.[path];
if (manifest && "source" in manifest)
await writeFile(resolve(dir, path), manifest.source, "utf-8");
}
async function doSetup(options, nuxt) {
const resolver = createResolver(import.meta.url);
const nuxtVersion = getNuxtVersion(nuxt).split(".").map((v) => Number.parseInt(v));
const nuxt3_8 = nuxtVersion.length > 1 && (nuxtVersion[0] > 3 || nuxtVersion[0] === 3 && nuxtVersion[1] >= 8);
const nuxt4 = nuxtVersion.length > 1 && nuxtVersion[0] >= 4;
const future = nuxt.options.future;
const ctx = {
nuxt,
nuxt3_8,
nuxt4,
// included at v3.12.0, then removed and included back again for v5 compatibility: we only need the layout to configure correctly the pwa assets image
// https://github.com/nuxt/nuxt/releases/tag/v3.12.0
nuxt4Compat: nuxt4 ? true : "compatibilityVersion" in future && typeof future.compatibilityVersion === "number" && future.compatibilityVersion >= 4,
resolver: createResolver(import.meta.url),
options,
publicDirFolder: resolveAlias("public")
};
let vitePwaClientPlugin;
const resolveVitePluginPWAAPI = () => {
return vitePwaClientPlugin?.api;
};
const client = options.client ?? {
registerPlugin: true,
installPrompt: false,
periodicSyncForUpdates: 0
};
const runtimeDir = resolver.resolve("../runtime");
nuxt.options.build.transpile.push(runtimeDir);
if (client.registerPlugin) {
addPlugin({
src: resolver.resolve(runtimeDir, "plugins/pwa.client"),
mode: "client"
});
}
const pwaAssetsEnabled = !!options.pwaAssets && options.pwaAssets.disabled !== true;
addPWAIconsPluginTemplate(pwaAssetsEnabled);
if (ctx.nuxt4) {
await Promise.all([
addComponent({
name: "VitePwaManifest",
filePath: resolver.resolve(runtimeDir, "components/VitePwaManifest")
}),
addComponent({
name: "NuxtPwaManifest",
filePath: resolver.resolve(runtimeDir, "components/VitePwaManifest")
}),
addComponent({
name: "NuxtPwaAssets",
filePath: resolver.resolve(runtimeDir, "components/NuxtPwaAssets")
}),
addComponent({
name: "PwaAppleImage",
filePath: resolver.resolve(runtimeDir, "components/nuxt4/PwaAppleImage")
}),
addComponent({
name: "PwaAppleSplashScreenImage",
filePath: resolver.resolve(runtimeDir, "components/nuxt4/PwaAppleSplashScreenImage")
}),
addComponent({
name: "PwaFaviconImage",
filePath: resolver.resolve(runtimeDir, "components/nuxt4/PwaFaviconImage")
}),
addComponent({
name: "PwaMaskableImage",
filePath: resolver.resolve(runtimeDir, "components/nuxt4/PwaMaskableImage")
}),
addComponent({
name: "PwaTransparentImage",
filePath: resolver.resolve(runtimeDir, "components/nuxt4/PwaTransparentImage")
})
]);
} else {
await Promise.all([
addComponent({
name: "VitePwaManifest",
filePath: resolver.resolve(runtimeDir, "components/VitePwaManifest")
}),
addComponent({
name: "NuxtPwaManifest",
filePath: resolver.resolve(runtimeDir, "components/VitePwaManifest")
}),
addComponent({
name: "NuxtPwaAssets",
filePath: resolver.resolve(runtimeDir, "components/NuxtPwaAssets")
}),
addComponent({
name: "PwaAppleImage",
filePath: resolver.resolve(runtimeDir, "components/PwaAppleImage.vue")
}),
addComponent({
name: "PwaAppleSplashScreenImage",
filePath: resolver.resolve(runtimeDir, "components/PwaAppleSplashScreenImage.vue")
}),
addComponent({
name: "PwaFaviconImage",
filePath: resolver.resolve(runtimeDir, "components/PwaFaviconImage.vue")
}),
addComponent({
name: "PwaMaskableImage",
filePath: resolver.resolve(runtimeDir, "components/PwaMaskableImage.vue")
}),
addComponent({
name: "PwaTransparentImage",
filePath: resolver.resolve(runtimeDir, "components/PwaTransparentImage.vue")
})
]);
}
nuxt.hook("prepare:types", ({ references }) => {
references.push({ path: resolver.resolve(runtimeDir, "plugins/types") });
references.push({ types: "@vite-pwa/nuxt/configuration" });
references.push({ types: "vite-plugin-pwa/vue" });
references.push({ types: "vite-plugin-pwa/info" });
references.push({ types: "vite-plugin-pwa/pwa-assets" });
});
const pwaAssets = await registerPwaIconsTypes(ctx, runtimeDir);
const manifestDir = join(nuxt.options.buildDir, "manifests");
nuxt.options.nitro.publicAssets = nuxt.options.nitro.publicAssets || [];
nuxt.options.nitro.publicAssets.push({
dir: manifestDir,
maxAge: 0
});
nuxt.hook("nitro:init", (nitro) => {
configurePWAOptions(ctx, nitro.options);
});
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!");
if (options.manifest && isClient) {
viteInlineConfig.plugins.push({
name: "vite-pwa-nuxt:webmanifest:build",
apply: "build",
async writeBundle(_options, bundle) {
if (options.disable || !bundle)
return;
const api = resolveVitePluginPWAAPI();
if (api) {
await mkdir(manifestDir, { recursive: true });
await writeWebManifest(manifestDir, options.manifestFilename || "manifest.webmanifest", api, pwaAssets);
}
}
});
}
if (isClient) {
viteInlineConfig.plugins = viteInlineConfig.plugins || [];
const configuration = "virtual:nuxt-pwa-configuration";
const resolvedConfiguration = `\0${configuration}`;
viteInlineConfig.plugins.push({
name: "vite-pwa-nuxt:configuration",
enforce: "pre",
resolveId(id) {
if (id === configuration)
return resolvedConfiguration;
},
load(id) {
if (id === resolvedConfiguration) {
const display = typeof options.manifest !== "boolean" ? options.manifest?.display ?? "standalone" : "standalone";
const installPrompt = typeof client.installPrompt === "undefined" || client.installPrompt === false ? void 0 : client.installPrompt === true || client.installPrompt.trim() === "" ? "vite-pwa:hide-install" : client.installPrompt.trim();
return `export const enabled = ${client.registerPlugin}
export const display = '${display}'
export const installPrompt = ${JSON.stringify(installPrompt)}
export const periodicSyncForUpdates = ${typeof client.periodicSyncForUpdates === "number" ? client.periodicSyncForUpdates : 0}
`;
}
}
});
}
const plugins = [...VitePWA(options).filter((p) => p.name !== "vite-plugin-pwa:build")];
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.pwaAssets) {
viteServer.middlewares.stack.push({
route: "",
// @ts-expect-error just ignore
handle: async (req, res, next) => {
const url = req.url;
if (!url)
return next();
if (!/\.(?:ico|png|svg|webp)$/.test(url))
return next();
const pwaAssetsGenerator = await resolveVitePluginPWAAPI()?.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);
}
});
}
});
if (!options.strategies || options.strategies === "generateSW") {
nuxt.hook("vite:serverCreated", (viteServer, { isServer }) => {
if (isServer)
return;
viteServer.middlewares.stack.push({ route: workbox, handle: emptyHandle });
});
if (options.devOptions?.suppressWarnings) {
const suppressWarnings = `${nuxt.options.app.baseURL}suppress-warnings.js`;
nuxt.hook("vite:serverCreated", (viteServer, { isServer }) => {
if (isServer)
return;
viteServer.middlewares.stack.push({ route: suppressWarnings, handle: emptyHandle });
});
}
const { sourcemap = nuxt.options.sourcemap.client === true } = options.workbox ?? {};
if (sourcemap) {
const swMap = `${nuxt.options.app.baseURL}${options.filename ?? "sw.js"}.map`;
const resolvedSwMapFile = join(nuxt.options.buildDir, "dev-sw-dist", swMap);
const worboxMap = `${nuxt.options.app.baseURL}workbox-`;
addDevServerHandler({
route: "",
handler: await import('h3').then(({ defineLazyEventHandler }) => defineLazyEventHandler(async () => {
const { dev } = await import('../chunks/dev.mjs');
return dev(
swMap,
resolvedSwMapFile,
worboxMap,
nuxt.options.buildDir,
nuxt.options.app.baseURL
);
}))
});
}
}
} 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"
}
};
}
});
}
if (nuxt3_8) {
nuxt.hook("nitro:build:public-assets", async () => {
await regeneratePWA(
options.outDir,
pwaAssets,
resolveVitePluginPWAAPI()
);
});
} else {
nuxt.hook("nitro:init", (nitro) => {
nitro.hooks.hook("rollup:before", async () => {
await regeneratePWA(
options.outDir,
pwaAssets,
resolveVitePluginPWAAPI()
);
});
});
if (nuxt.options.nitro.static || nuxt.options._generate) {
nuxt.hook("close", async () => {
await regeneratePWA(
options.outDir,
pwaAssets,
resolveVitePluginPWAAPI()
);
});
}
}
}
}
const module = defineNuxtModule({
meta: {
name: "pwa",
configKey: "pwa",
compatibility: {
nuxt: ">=3.6.5"
},
version
},
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) {
await doSetup(options, nuxt);
}
});
export { generatePwaImageType as g, module as m, pwaIcons as p };