@djangocfg/nextjs 2.1.516 → 2.1.518

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.
@@ -0,0 +1,79 @@
1
+ import { NextConfig } from 'next';
2
+
3
+ /**
4
+ * PWA (Progressive Web App) Plugin — dependency-free.
5
+ *
6
+ * History: this used to wrap `@serwist/next`. Serwist is a **webpack** plugin,
7
+ * and Next.js 16 defaults to Turbopack, which does not run webpack loaders and
8
+ * chokes on serwist's `fileURLToPath(new URL(".", import.meta.url))` during
9
+ * static tracing — failing the whole build even when PWA was never enabled
10
+ * (the mere `require('@serwist/next')` reference was enough). Since no app in
11
+ * this monorepo actually turned PWA on (`withPWA` is opt-in and unused), we
12
+ * dropped serwist entirely.
13
+ *
14
+ * What remains:
15
+ * - `createManifest` (in `./manifest`) — the web app manifest, which Next.js
16
+ * supports natively via `app/manifest.ts`; needs no library at all.
17
+ * - `withPWA` — a thin, Turbopack-safe pass-through kept for API stability.
18
+ * - `getServiceWorkerTemplate` — a vanilla service worker you can drop into
19
+ * `app/sw.ts`, no framework runtime required.
20
+ *
21
+ * If you want a full precache/offline story back, wire a real service worker
22
+ * (the template below, or Workbox) yourself — but do it in an app, not here, so
23
+ * one app's PWA choice can't break every consumer's build.
24
+ */
25
+
26
+ interface PWAPluginOptions {
27
+ /**
28
+ * Path to service worker source file (relative to project root).
29
+ * @default 'app/sw.ts'
30
+ */
31
+ swSrc?: string;
32
+ /**
33
+ * Destination for the compiled service worker.
34
+ * @default 'public/sw.js'
35
+ */
36
+ swDest?: string;
37
+ /**
38
+ * Disable PWA completely.
39
+ * @default true in development / static export, false otherwise
40
+ */
41
+ disable?: boolean;
42
+ /** Cache pages on navigation. Consumed by a custom SW, if any. @default true */
43
+ cacheOnNavigation?: boolean;
44
+ /** Reload the app when the device comes back online. @default true */
45
+ reloadOnOnline?: boolean;
46
+ }
47
+ /**
48
+ * Turbopack-safe PWA pass-through.
49
+ *
50
+ * This no longer injects a build plugin — Next.js's native manifest support
51
+ * plus a hand-written `app/sw.ts` (see {@link getServiceWorkerTemplate}) cover
52
+ * the PWA surface without a webpack-only dependency. Kept as a named export so
53
+ * existing `withPWA(config)` / `createBaseNextConfig({ pwa })` call sites keep
54
+ * type-checking and behave predictably (they return the config unchanged).
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * import { createBaseNextConfig } from '@djangocfg/nextjs/config';
59
+ * export default createBaseNextConfig({ pwa: { swSrc: 'app/sw.ts' } });
60
+ * ```
61
+ */
62
+ declare function withPWA(nextConfig: NextConfig, _options?: PWAPluginOptions): NextConfig;
63
+ /**
64
+ * Vanilla service-worker template — no library.
65
+ *
66
+ * Returns ready-to-use, framework-free service worker source. Copy it into
67
+ * `app/sw.ts` (or generate it) if you want offline support. It uses only the
68
+ * standard Service Worker + Cache APIs, so it builds under Turbopack with no
69
+ * extra dependency.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * import { getServiceWorkerTemplate } from '@djangocfg/nextjs/config';
74
+ * console.log(getServiceWorkerTemplate()); // → paste into app/sw.ts
75
+ * ```
76
+ */
77
+ declare function getServiceWorkerTemplate(): string;
78
+
79
+ export { type PWAPluginOptions as P, getServiceWorkerTemplate as g, withPWA as w };
@@ -1,3 +1,3 @@
1
1
  export { IconPaths, ManifestConfig, ProtocolHandler, ScreenshotConfig, SmartScreenshotInput, createManifest, createManifestMetadata, createScreenshot, createScreenshots, createViewport, generateManifest } from './manifest.mjs';
2
- export { P as PWAPluginOptions, c as createApiCacheRule, a as createCdnCacheRule, b as createStaticAssetRule, d as defaultRuntimeCaching, g as getServiceWorkerTemplate, w as withPWA } from '../plugin-DdmK3zAE.mjs';
2
+ export { P as PWAPluginOptions, g as getServiceWorkerTemplate, w as withPWA } from '../plugin-yhoLWYh6.mjs';
3
3
  import 'next';
@@ -127,108 +127,57 @@ function generateManifest(config) {
127
127
  }
128
128
 
129
129
  // src/pwa/plugin.ts
130
- import { consola } from "consola";
131
- import { createRequire } from "module";
132
- var requirePeer = createRequire(import.meta.url);
133
- function withPWA(nextConfig, options = {}) {
134
- const isDev = process.env.NODE_ENV === "development";
135
- const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
136
- const shouldDisable = options.disable !== void 0 ? options.disable : isDev || isStaticBuild;
137
- const defaultOptions = {
138
- swSrc: "app/sw.ts",
139
- swDest: "public/sw.js",
140
- disable: shouldDisable,
141
- cacheOnNavigation: true,
142
- reloadOnOnline: true,
143
- ...options
144
- };
145
- try {
146
- if (!process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING) {
147
- process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING = "1";
148
- }
149
- const withSerwistInit = requirePeer("@serwist/next").default;
150
- const withSerwist = withSerwistInit({
151
- swSrc: defaultOptions.swSrc,
152
- swDest: defaultOptions.swDest,
153
- disable: defaultOptions.disable,
154
- cacheOnNavigation: defaultOptions.cacheOnNavigation,
155
- reloadOnOnline: defaultOptions.reloadOnOnline,
156
- ...defaultOptions.serwistOptions
157
- });
158
- return withSerwist(nextConfig);
159
- } catch (error) {
160
- consola.error("Failed to configure Serwist:", error);
161
- return nextConfig;
162
- }
130
+ function withPWA(nextConfig, _options = {}) {
131
+ return nextConfig;
163
132
  }
164
133
  function getServiceWorkerTemplate() {
165
134
  return `/**
166
- * Service Worker (Serwist)
167
- *
168
- * Modern PWA service worker using Serwist
135
+ * Service Worker (vanilla) \u2014 standard Cache + Fetch APIs, no library.
169
136
  */
170
137
 
171
- import { defaultCache } from '@serwist/next/worker';
172
- import { Serwist } from 'serwist';
173
-
138
+ /// <reference lib="webworker" />
174
139
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
140
  declare const self: any;
176
141
 
177
- const serwist = new Serwist({
178
- // Precache entries injected by Serwist build plugin
179
- precacheEntries: self.__SW_MANIFEST,
180
-
181
- // Skip waiting - activate new SW immediately
182
- skipWaiting: true,
183
-
184
- // Take control of all clients immediately
185
- clientsClaim: true,
142
+ const CACHE = 'app-cache-v1';
143
+ const OFFLINE_URL = '/_offline';
186
144
 
187
- // Enable navigation preload for faster loads
188
- navigationPreload: true,
189
-
190
- // Use default Next.js runtime caching strategies
191
- runtimeCaching: defaultCache,
145
+ // Precache the offline fallback on install.
146
+ self.addEventListener('install', (event: any) => {
147
+ event.waitUntil(
148
+ caches.open(CACHE).then((cache: Cache) => cache.addAll([OFFLINE_URL]))
149
+ );
150
+ self.skipWaiting();
151
+ });
192
152
 
193
- // Fallback pages for offline
194
- fallbacks: {
195
- entries: [
196
- {
197
- url: '/_offline',
198
- matcher({ request }) {
199
- return request.destination === 'document';
200
- },
201
- },
202
- ],
203
- },
153
+ // Drop stale caches on activate and take control immediately.
154
+ self.addEventListener('activate', (event: any) => {
155
+ event.waitUntil(
156
+ caches.keys().then((keys: string[]) =>
157
+ Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
158
+ )
159
+ );
160
+ self.clients.claim();
204
161
  });
205
162
 
206
- serwist.addEventListeners();
163
+ // Network-first for navigations, with an offline fallback.
164
+ self.addEventListener('fetch', (event: any) => {
165
+ const { request } = event;
166
+ if (request.mode !== 'navigate') return;
167
+ event.respondWith(
168
+ fetch(request).catch(() =>
169
+ caches.match(OFFLINE_URL).then((r: Response | undefined) => r ?? Response.error())
170
+ )
171
+ );
172
+ });
207
173
  `;
208
174
  }
209
- var defaultRuntimeCaching = [];
210
- function createApiCacheRule() {
211
- consola.warn("createApiCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker");
212
- return {};
213
- }
214
- function createStaticAssetRule() {
215
- consola.warn("createStaticAssetRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker");
216
- return {};
217
- }
218
- function createCdnCacheRule() {
219
- consola.warn("createCdnCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker");
220
- return {};
221
- }
222
175
  export {
223
- createApiCacheRule,
224
- createCdnCacheRule,
225
176
  createManifest,
226
177
  createManifestMetadata,
227
178
  createScreenshot,
228
179
  createScreenshots,
229
- createStaticAssetRule,
230
180
  createViewport,
231
- defaultRuntimeCaching,
232
181
  generateManifest,
233
182
  getServiceWorkerTemplate,
234
183
  withPWA
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pwa/manifest.ts","../../src/pwa/plugin.ts"],"sourcesContent":["/**\n * PWA Manifest Metadata Utilities\n *\n * Helper functions for creating Next.js metadata for PWA manifest\n */\n\nimport type { Metadata, MetadataRoute, Viewport } from 'next';\n\nexport interface ManifestConfig {\n name: string;\n shortName?: string;\n description?: string;\n themeColor?: string;\n backgroundColor?: string;\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n orientation?: 'portrait' | 'landscape' | 'any';\n startUrl?: string;\n scope?: string;\n lang?: string;\n dir?: 'ltr' | 'rtl' | 'auto';\n icons?: {\n src: string;\n sizes: string;\n type?: string;\n purpose?: string;\n }[];\n}\n\n/**\n * Icon paths configuration\n */\nexport interface IconPaths {\n logo192?: string;\n logo384?: string;\n logo512?: string;\n}\n\n/**\n * Protocol handler configuration\n * Allows your PWA to register as a handler for custom protocols\n *\n * @example\n * ```typescript\n * {\n * protocol: \"web+music\",\n * url: \"/play?track=%s\"\n * }\n * ```\n */\nexport interface ProtocolHandler {\n /** Protocol scheme (e.g., \"web+music\", \"mailto\", \"magnet\") */\n protocol: string;\n /** URL template with %s placeholder for the protocol parameter */\n url: string;\n}\n\n/**\n * Create viewport configuration for Next.js app\n *\n * @example\n * ```typescript\n * export const viewport: Viewport = createViewport({\n * themeColor: '#ffffff',\n * });\n * ```\n */\nexport function createViewport(config: { themeColor?: string }): Viewport {\n return {\n width: 'device-width',\n initialScale: 1,\n maximumScale: 1,\n themeColor: config.themeColor || '#000000',\n };\n}\n\n/**\n * Create manifest metadata for Next.js app\n *\n * Note: themeColor and viewport should be exported separately using createViewport()\n *\n * @example\n * ```typescript\n * export const metadata: Metadata = {\n * ...createManifestMetadata({\n * name: 'My App',\n * shortName: 'App',\n * description: 'My awesome app',\n * }),\n * };\n *\n * export const viewport: Viewport = createViewport({\n * themeColor: '#ffffff',\n * });\n * ```\n */\nexport function createManifestMetadata(config: ManifestConfig): Metadata {\n return {\n manifest: '/manifest.json',\n appleWebApp: {\n capable: true,\n statusBarStyle: 'default',\n title: config.shortName || config.name,\n },\n applicationName: config.name,\n formatDetection: {\n telephone: false,\n },\n };\n}\n\n/**\n * Create Next.js manifest function\n *\n * Use this in your app/manifest.ts file\n *\n * @example\n * ```typescript\n * // app/manifest.ts\n * import { createManifest } from '@djangocfg/nextjs/config';\n * import { settings } from '@core/settings';\n *\n * export default createManifest({\n * name: settings.app.name,\n * description: settings.app.description,\n * icons: {\n * logo192: settings.app.icons.logo192,\n * logo384: settings.app.icons.logo384,\n * logo512: settings.app.icons.logo512,\n * },\n * });\n * ```\n */\nexport interface ScreenshotConfig {\n src: string;\n sizes: string;\n type?: string;\n form_factor?: 'narrow' | 'wide';\n label?: string;\n}\n\n/**\n * Smart screenshot configuration\n * Automatically detects everything from path or uses defaults\n */\nexport interface SmartScreenshotInput {\n src: string;\n /** Form factor (auto-detected from filename if contains 'desktop'/'mobile', or use default) */\n form_factor?: 'narrow' | 'wide';\n /** Optional label (auto-generated from form_factor) */\n label?: string;\n /** Optional width (defaults based on form_factor) */\n width?: number;\n /** Optional height (defaults based on form_factor) */\n height?: number;\n}\n\n/**\n * Create screenshot config with smart defaults\n * Automatically detects type, sizes, form_factor from path or uses sensible defaults\n *\n * @example\n * ```typescript\n * // Minimal - everything auto-detected\n * createScreenshot({ src: '/screenshots/desktop-view.png' })\n * // → form_factor: 'wide', sizes: '1920x1080', type: 'image/png', label: 'Desktop screenshot'\n *\n * createScreenshot({ src: '/screenshots/mobile.png' })\n * // → form_factor: 'narrow', sizes: '390x844', type: 'image/png', label: 'Mobile screenshot'\n *\n * // With custom dimensions\n * createScreenshot({ src: '/screenshots/tablet.png', width: 1024, height: 768 })\n * ```\n */\nexport function createScreenshot(input: SmartScreenshotInput | string): ScreenshotConfig {\n // Allow string shorthand\n const config = typeof input === 'string' ? { src: input } : input;\n let { src, width, height, label, form_factor } = config;\n\n // Auto-detect image type from extension\n const ext = src.split('.').pop()?.toLowerCase();\n const typeMap: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n };\n const type = ext ? typeMap[ext] || 'image/png' : 'image/png';\n\n // Try to parse dimensions from filename (e.g., \"1920x1080.png\" or \"screenshot-390x844.png\")\n const filename = src.toLowerCase();\n const dimensionMatch = filename.match(/(\\d{3,4})x(\\d{3,4})/);\n if (dimensionMatch && !width && !height) {\n width = parseInt(dimensionMatch[1], 10);\n height = parseInt(dimensionMatch[2], 10);\n }\n\n // Auto-detect form_factor from filename if not provided\n let autoFormFactor: 'narrow' | 'wide' = 'wide'; // Default to wide\n if (filename.includes('mobile') || filename.includes('phone') || filename.includes('narrow')) {\n autoFormFactor = 'narrow';\n } else if (filename.includes('desktop') || filename.includes('laptop') || filename.includes('wide')) {\n autoFormFactor = 'wide';\n } else if (width && height) {\n // Calculate from dimensions if provided or parsed\n const aspectRatio = width / height;\n autoFormFactor = aspectRatio > 1.2 ? 'wide' : 'narrow';\n }\n\n const finalFormFactor = form_factor || autoFormFactor;\n\n // Default dimensions based on form_factor (only if not parsed from filename)\n const defaultDimensions = finalFormFactor === 'wide'\n ? { width: 1920, height: 1080 } // Desktop default\n : { width: 390, height: 844 }; // Mobile default (iPhone 14)\n\n const finalWidth = width || defaultDimensions.width;\n const finalHeight = height || defaultDimensions.height;\n\n // Auto-generate label\n const autoLabel = finalFormFactor === 'wide'\n ? 'Desktop screenshot'\n : 'Mobile screenshot';\n\n return {\n src,\n sizes: `${finalWidth}x${finalHeight}`,\n type,\n form_factor: finalFormFactor,\n label: label || autoLabel,\n };\n}\n\n/**\n * Create multiple screenshots from array\n * Supports string shorthand or full config objects\n *\n * @example\n * ```typescript\n * // Minimal - just paths\n * createScreenshots([\n * '/screenshots/desktop.png', // Auto: wide, 1920x1080\n * '/screenshots/mobile.png', // Auto: narrow, 390x844\n * ])\n *\n * // Mixed\n * createScreenshots([\n * '/screenshots/desktop.png',\n * { src: '/screenshots/tablet.png', width: 1024, height: 768 },\n * ])\n * ```\n */\nexport function createScreenshots(inputs: Array<SmartScreenshotInput | string>): ScreenshotConfig[] {\n return inputs.map(createScreenshot);\n}\n\nexport function createManifest(config: {\n name: string;\n shortName?: string;\n description?: string;\n themeColor?: string;\n backgroundColor?: string;\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n orientation?: 'portrait' | 'landscape' | 'any';\n id?: string;\n startUrl?: string;\n scope?: string;\n lang?: string;\n dir?: 'ltr' | 'rtl' | 'auto';\n icons?: IconPaths | ManifestConfig['icons'];\n screenshots?: ScreenshotConfig[];\n protocol_handlers?: ProtocolHandler[];\n}): () => MetadataRoute.Manifest {\n return () => {\n // Convert IconPaths to manifest icons format\n let manifestIcons: MetadataRoute.Manifest['icons'];\n\n if (Array.isArray(config.icons)) {\n // Already in manifest format\n manifestIcons = config.icons as MetadataRoute.Manifest['icons'];\n } else if (config.icons) {\n // Convert IconPaths to manifest icons\n const { logo192, logo384, logo512 } = config.icons as IconPaths;\n manifestIcons = [\n ...(logo192\n ? [\n {\n src: logo192,\n sizes: '192x192',\n type: 'image/png',\n purpose: 'maskable' as const,\n },\n ]\n : []),\n ...(logo384\n ? [\n {\n src: logo384,\n sizes: '384x384',\n type: 'image/png',\n },\n ]\n : []),\n ...(logo512\n ? [\n {\n src: logo512,\n sizes: '512x512',\n type: 'image/png',\n },\n ]\n : []),\n ];\n }\n\n const manifest: MetadataRoute.Manifest = {\n name: config.name,\n short_name: config.shortName || config.name,\n description: config.description || config.name,\n id: config.id || config.startUrl || '/',\n start_url: config.startUrl || '/',\n scope: config.scope || '/',\n display: config.display || 'standalone',\n orientation: config.orientation || 'portrait',\n background_color: config.backgroundColor || '#000000',\n theme_color: config.themeColor || '#ffffff',\n lang: config.lang || 'en',\n dir: config.dir || 'ltr',\n icons: manifestIcons,\n // Removed forced gcm_sender_id to avoid potential conflicts with VAPID\n // gcm_sender_id: '103953800507',\n };\n\n // Add screenshots if provided (for Richer PWA Install UI)\n if (config.screenshots && config.screenshots.length > 0) {\n (manifest as any).screenshots = config.screenshots;\n }\n\n // Add protocol handlers if provided\n if (config.protocol_handlers && config.protocol_handlers.length > 0) {\n (manifest as any).protocol_handlers = config.protocol_handlers;\n }\n\n return manifest;\n };\n}\n\n/**\n * Generate manifest.json content (legacy)\n *\n * @deprecated Use createManifest() instead\n */\nexport function generateManifest(config: ManifestConfig): Record<string, any> {\n return createManifest(config)();\n}\n","/**\n * PWA (Progressive Web App) Plugin\n *\n * Configures Serwist for service worker and offline support\n * Modern PWA solution for Next.js 15+ with App Router\n *\n * @see https://serwist.pages.dev/\n */\n\nimport type { NextConfig } from 'next';\nimport { consola } from 'consola';\nimport { createRequire } from 'module';\n\n// Serwist is an optional peer loaded synchronously at config time, which\n// `import()` cannot do. `createRequire` is the ESM-safe equivalent of `require`.\nconst requirePeer = createRequire(import.meta.url);\n\nexport interface PWAPluginOptions {\n /**\n * Destination directory for service worker files\n * @default 'public'\n * @deprecated Use swDest instead\n */\n dest?: string;\n\n /**\n * Path to service worker source file (relative to project root)\n * @default 'app/sw.ts'\n */\n swSrc?: string;\n\n /**\n * Destination for compiled service worker\n * @default 'public/sw.js'\n */\n swDest?: string;\n\n /**\n * Disable PWA completely\n * @default false in production, true in development\n * @example disable: process.env.NODE_ENV === 'development'\n */\n disable?: boolean;\n\n /**\n * Cache on navigation - cache pages when navigating\n * @default true\n */\n cacheOnNavigation?: boolean;\n\n /**\n * Reload app when device goes back online\n * @default true\n */\n reloadOnOnline?: boolean;\n\n /**\n * Additional Serwist options\n * @see https://serwist.pages.dev/docs/next/configuring\n */\n serwistOptions?: Record<string, any>;\n}\n\n/**\n * Add PWA configuration to Next.js config using Serwist\n *\n * @example Basic usage\n * ```ts\n * import { createBaseNextConfig, withPWA } from '@djangocfg/nextjs/config';\n *\n * const nextConfig = createBaseNextConfig({...});\n *\n * export default withPWA(nextConfig, {\n * swSrc: 'app/sw.ts',\n * disable: process.env.NODE_ENV === 'development',\n * });\n * ```\n *\n * @example Integrated with createBaseNextConfig\n * ```ts\n * import { createBaseNextConfig } from '@djangocfg/nextjs/config';\n *\n * const config = createBaseNextConfig({\n * pwa: {\n * swSrc: 'app/sw.ts',\n * disable: false,\n * },\n * });\n *\n * export default config;\n * ```\n */\nexport function withPWA(\n nextConfig: NextConfig,\n options: PWAPluginOptions = {}\n): NextConfig {\n const isDev = process.env.NODE_ENV === 'development';\n const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';\n\n // Determine if PWA should be disabled:\n // - Explicitly disabled via options\n // - In development mode (default)\n // - Static build (output: 'export' doesn't support service workers)\n const shouldDisable = options.disable !== undefined\n ? options.disable\n : (isDev || isStaticBuild);\n\n const defaultOptions: PWAPluginOptions = {\n swSrc: 'app/sw.ts',\n swDest: 'public/sw.js',\n disable: shouldDisable,\n cacheOnNavigation: true,\n reloadOnOnline: true,\n ...options,\n };\n\n try {\n // Suppress Turbopack warning - it only applies to dev mode, not production builds\n // The warning is misleading when running `next build` with Turbopack\n // See: https://github.com/serwist/serwist/issues/54\n if (!process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING) {\n process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING = '1';\n }\n\n const withSerwistInit = requirePeer('@serwist/next').default;\n\n const withSerwist = withSerwistInit({\n swSrc: defaultOptions.swSrc,\n swDest: defaultOptions.swDest,\n disable: defaultOptions.disable,\n cacheOnNavigation: defaultOptions.cacheOnNavigation,\n reloadOnOnline: defaultOptions.reloadOnOnline,\n ...defaultOptions.serwistOptions,\n });\n\n return withSerwist(nextConfig);\n } catch (error) {\n consola.error('Failed to configure Serwist:', error);\n return nextConfig;\n }\n}\n\n/**\n * Get service worker template content\n *\n * Returns ready-to-use service worker code for app/sw.ts\n *\n * @example\n * ```ts\n * import { getServiceWorkerTemplate } from '@djangocfg/nextjs/config';\n *\n * // Copy this to your app/sw.ts file\n * console.log(getServiceWorkerTemplate());\n * ```\n */\nexport function getServiceWorkerTemplate(): string {\n return `/**\n * Service Worker (Serwist)\n *\n * Modern PWA service worker using Serwist\n */\n\nimport { defaultCache } from '@serwist/next/worker';\nimport { Serwist } from 'serwist';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ndeclare const self: any;\n\nconst serwist = new Serwist({\n // Precache entries injected by Serwist build plugin\n precacheEntries: self.__SW_MANIFEST,\n\n // Skip waiting - activate new SW immediately\n skipWaiting: true,\n\n // Take control of all clients immediately\n clientsClaim: true,\n\n // Enable navigation preload for faster loads\n navigationPreload: true,\n\n // Use default Next.js runtime caching strategies\n runtimeCaching: defaultCache,\n\n // Fallback pages for offline\n fallbacks: {\n entries: [\n {\n url: '/_offline',\n matcher({ request }) {\n return request.destination === 'document';\n },\n },\n ],\n },\n});\n\nserwist.addEventListeners();\n`;\n}\n\n// Backward compatibility exports (deprecated)\nexport const defaultRuntimeCaching = [];\nexport function createApiCacheRule() {\n consola.warn('createApiCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker');\n return {};\n}\nexport function createStaticAssetRule() {\n consola.warn('createStaticAssetRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker');\n return {};\n}\nexport function createCdnCacheRule() {\n consola.warn('createCdnCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker');\n return {};\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/pwa/manifest.ts","../../src/pwa/plugin.ts"],"sourcesContent":["/**\n * PWA Manifest Metadata Utilities\n *\n * Helper functions for creating Next.js metadata for PWA manifest\n */\n\nimport type { Metadata, MetadataRoute, Viewport } from 'next';\n\nexport interface ManifestConfig {\n name: string;\n shortName?: string;\n description?: string;\n themeColor?: string;\n backgroundColor?: string;\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n orientation?: 'portrait' | 'landscape' | 'any';\n startUrl?: string;\n scope?: string;\n lang?: string;\n dir?: 'ltr' | 'rtl' | 'auto';\n icons?: {\n src: string;\n sizes: string;\n type?: string;\n purpose?: string;\n }[];\n}\n\n/**\n * Icon paths configuration\n */\nexport interface IconPaths {\n logo192?: string;\n logo384?: string;\n logo512?: string;\n}\n\n/**\n * Protocol handler configuration\n * Allows your PWA to register as a handler for custom protocols\n *\n * @example\n * ```typescript\n * {\n * protocol: \"web+music\",\n * url: \"/play?track=%s\"\n * }\n * ```\n */\nexport interface ProtocolHandler {\n /** Protocol scheme (e.g., \"web+music\", \"mailto\", \"magnet\") */\n protocol: string;\n /** URL template with %s placeholder for the protocol parameter */\n url: string;\n}\n\n/**\n * Create viewport configuration for Next.js app\n *\n * @example\n * ```typescript\n * export const viewport: Viewport = createViewport({\n * themeColor: '#ffffff',\n * });\n * ```\n */\nexport function createViewport(config: { themeColor?: string }): Viewport {\n return {\n width: 'device-width',\n initialScale: 1,\n maximumScale: 1,\n themeColor: config.themeColor || '#000000',\n };\n}\n\n/**\n * Create manifest metadata for Next.js app\n *\n * Note: themeColor and viewport should be exported separately using createViewport()\n *\n * @example\n * ```typescript\n * export const metadata: Metadata = {\n * ...createManifestMetadata({\n * name: 'My App',\n * shortName: 'App',\n * description: 'My awesome app',\n * }),\n * };\n *\n * export const viewport: Viewport = createViewport({\n * themeColor: '#ffffff',\n * });\n * ```\n */\nexport function createManifestMetadata(config: ManifestConfig): Metadata {\n return {\n manifest: '/manifest.json',\n appleWebApp: {\n capable: true,\n statusBarStyle: 'default',\n title: config.shortName || config.name,\n },\n applicationName: config.name,\n formatDetection: {\n telephone: false,\n },\n };\n}\n\n/**\n * Create Next.js manifest function\n *\n * Use this in your app/manifest.ts file\n *\n * @example\n * ```typescript\n * // app/manifest.ts\n * import { createManifest } from '@djangocfg/nextjs/config';\n * import { settings } from '@core/settings';\n *\n * export default createManifest({\n * name: settings.app.name,\n * description: settings.app.description,\n * icons: {\n * logo192: settings.app.icons.logo192,\n * logo384: settings.app.icons.logo384,\n * logo512: settings.app.icons.logo512,\n * },\n * });\n * ```\n */\nexport interface ScreenshotConfig {\n src: string;\n sizes: string;\n type?: string;\n form_factor?: 'narrow' | 'wide';\n label?: string;\n}\n\n/**\n * Smart screenshot configuration\n * Automatically detects everything from path or uses defaults\n */\nexport interface SmartScreenshotInput {\n src: string;\n /** Form factor (auto-detected from filename if contains 'desktop'/'mobile', or use default) */\n form_factor?: 'narrow' | 'wide';\n /** Optional label (auto-generated from form_factor) */\n label?: string;\n /** Optional width (defaults based on form_factor) */\n width?: number;\n /** Optional height (defaults based on form_factor) */\n height?: number;\n}\n\n/**\n * Create screenshot config with smart defaults\n * Automatically detects type, sizes, form_factor from path or uses sensible defaults\n *\n * @example\n * ```typescript\n * // Minimal - everything auto-detected\n * createScreenshot({ src: '/screenshots/desktop-view.png' })\n * // → form_factor: 'wide', sizes: '1920x1080', type: 'image/png', label: 'Desktop screenshot'\n *\n * createScreenshot({ src: '/screenshots/mobile.png' })\n * // → form_factor: 'narrow', sizes: '390x844', type: 'image/png', label: 'Mobile screenshot'\n *\n * // With custom dimensions\n * createScreenshot({ src: '/screenshots/tablet.png', width: 1024, height: 768 })\n * ```\n */\nexport function createScreenshot(input: SmartScreenshotInput | string): ScreenshotConfig {\n // Allow string shorthand\n const config = typeof input === 'string' ? { src: input } : input;\n let { src, width, height, label, form_factor } = config;\n\n // Auto-detect image type from extension\n const ext = src.split('.').pop()?.toLowerCase();\n const typeMap: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n };\n const type = ext ? typeMap[ext] || 'image/png' : 'image/png';\n\n // Try to parse dimensions from filename (e.g., \"1920x1080.png\" or \"screenshot-390x844.png\")\n const filename = src.toLowerCase();\n const dimensionMatch = filename.match(/(\\d{3,4})x(\\d{3,4})/);\n if (dimensionMatch && !width && !height) {\n width = parseInt(dimensionMatch[1], 10);\n height = parseInt(dimensionMatch[2], 10);\n }\n\n // Auto-detect form_factor from filename if not provided\n let autoFormFactor: 'narrow' | 'wide' = 'wide'; // Default to wide\n if (filename.includes('mobile') || filename.includes('phone') || filename.includes('narrow')) {\n autoFormFactor = 'narrow';\n } else if (filename.includes('desktop') || filename.includes('laptop') || filename.includes('wide')) {\n autoFormFactor = 'wide';\n } else if (width && height) {\n // Calculate from dimensions if provided or parsed\n const aspectRatio = width / height;\n autoFormFactor = aspectRatio > 1.2 ? 'wide' : 'narrow';\n }\n\n const finalFormFactor = form_factor || autoFormFactor;\n\n // Default dimensions based on form_factor (only if not parsed from filename)\n const defaultDimensions = finalFormFactor === 'wide'\n ? { width: 1920, height: 1080 } // Desktop default\n : { width: 390, height: 844 }; // Mobile default (iPhone 14)\n\n const finalWidth = width || defaultDimensions.width;\n const finalHeight = height || defaultDimensions.height;\n\n // Auto-generate label\n const autoLabel = finalFormFactor === 'wide'\n ? 'Desktop screenshot'\n : 'Mobile screenshot';\n\n return {\n src,\n sizes: `${finalWidth}x${finalHeight}`,\n type,\n form_factor: finalFormFactor,\n label: label || autoLabel,\n };\n}\n\n/**\n * Create multiple screenshots from array\n * Supports string shorthand or full config objects\n *\n * @example\n * ```typescript\n * // Minimal - just paths\n * createScreenshots([\n * '/screenshots/desktop.png', // Auto: wide, 1920x1080\n * '/screenshots/mobile.png', // Auto: narrow, 390x844\n * ])\n *\n * // Mixed\n * createScreenshots([\n * '/screenshots/desktop.png',\n * { src: '/screenshots/tablet.png', width: 1024, height: 768 },\n * ])\n * ```\n */\nexport function createScreenshots(inputs: Array<SmartScreenshotInput | string>): ScreenshotConfig[] {\n return inputs.map(createScreenshot);\n}\n\nexport function createManifest(config: {\n name: string;\n shortName?: string;\n description?: string;\n themeColor?: string;\n backgroundColor?: string;\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n orientation?: 'portrait' | 'landscape' | 'any';\n id?: string;\n startUrl?: string;\n scope?: string;\n lang?: string;\n dir?: 'ltr' | 'rtl' | 'auto';\n icons?: IconPaths | ManifestConfig['icons'];\n screenshots?: ScreenshotConfig[];\n protocol_handlers?: ProtocolHandler[];\n}): () => MetadataRoute.Manifest {\n return () => {\n // Convert IconPaths to manifest icons format\n let manifestIcons: MetadataRoute.Manifest['icons'];\n\n if (Array.isArray(config.icons)) {\n // Already in manifest format\n manifestIcons = config.icons as MetadataRoute.Manifest['icons'];\n } else if (config.icons) {\n // Convert IconPaths to manifest icons\n const { logo192, logo384, logo512 } = config.icons as IconPaths;\n manifestIcons = [\n ...(logo192\n ? [\n {\n src: logo192,\n sizes: '192x192',\n type: 'image/png',\n purpose: 'maskable' as const,\n },\n ]\n : []),\n ...(logo384\n ? [\n {\n src: logo384,\n sizes: '384x384',\n type: 'image/png',\n },\n ]\n : []),\n ...(logo512\n ? [\n {\n src: logo512,\n sizes: '512x512',\n type: 'image/png',\n },\n ]\n : []),\n ];\n }\n\n const manifest: MetadataRoute.Manifest = {\n name: config.name,\n short_name: config.shortName || config.name,\n description: config.description || config.name,\n id: config.id || config.startUrl || '/',\n start_url: config.startUrl || '/',\n scope: config.scope || '/',\n display: config.display || 'standalone',\n orientation: config.orientation || 'portrait',\n background_color: config.backgroundColor || '#000000',\n theme_color: config.themeColor || '#ffffff',\n lang: config.lang || 'en',\n dir: config.dir || 'ltr',\n icons: manifestIcons,\n // Removed forced gcm_sender_id to avoid potential conflicts with VAPID\n // gcm_sender_id: '103953800507',\n };\n\n // Add screenshots if provided (for Richer PWA Install UI)\n if (config.screenshots && config.screenshots.length > 0) {\n (manifest as any).screenshots = config.screenshots;\n }\n\n // Add protocol handlers if provided\n if (config.protocol_handlers && config.protocol_handlers.length > 0) {\n (manifest as any).protocol_handlers = config.protocol_handlers;\n }\n\n return manifest;\n };\n}\n\n/**\n * Generate manifest.json content (legacy)\n *\n * @deprecated Use createManifest() instead\n */\nexport function generateManifest(config: ManifestConfig): Record<string, any> {\n return createManifest(config)();\n}\n","/**\n * PWA (Progressive Web App) Plugin — dependency-free.\n *\n * History: this used to wrap `@serwist/next`. Serwist is a **webpack** plugin,\n * and Next.js 16 defaults to Turbopack, which does not run webpack loaders and\n * chokes on serwist's `fileURLToPath(new URL(\".\", import.meta.url))` during\n * static tracing — failing the whole build even when PWA was never enabled\n * (the mere `require('@serwist/next')` reference was enough). Since no app in\n * this monorepo actually turned PWA on (`withPWA` is opt-in and unused), we\n * dropped serwist entirely.\n *\n * What remains:\n * - `createManifest` (in `./manifest`) — the web app manifest, which Next.js\n * supports natively via `app/manifest.ts`; needs no library at all.\n * - `withPWA` — a thin, Turbopack-safe pass-through kept for API stability.\n * - `getServiceWorkerTemplate` — a vanilla service worker you can drop into\n * `app/sw.ts`, no framework runtime required.\n *\n * If you want a full precache/offline story back, wire a real service worker\n * (the template below, or Workbox) yourself — but do it in an app, not here, so\n * one app's PWA choice can't break every consumer's build.\n */\n\nimport type { NextConfig } from 'next';\n\nexport interface PWAPluginOptions {\n /**\n * Path to service worker source file (relative to project root).\n * @default 'app/sw.ts'\n */\n swSrc?: string;\n\n /**\n * Destination for the compiled service worker.\n * @default 'public/sw.js'\n */\n swDest?: string;\n\n /**\n * Disable PWA completely.\n * @default true in development / static export, false otherwise\n */\n disable?: boolean;\n\n /** Cache pages on navigation. Consumed by a custom SW, if any. @default true */\n cacheOnNavigation?: boolean;\n\n /** Reload the app when the device comes back online. @default true */\n reloadOnOnline?: boolean;\n}\n\n/**\n * Turbopack-safe PWA pass-through.\n *\n * This no longer injects a build plugin — Next.js's native manifest support\n * plus a hand-written `app/sw.ts` (see {@link getServiceWorkerTemplate}) cover\n * the PWA surface without a webpack-only dependency. Kept as a named export so\n * existing `withPWA(config)` / `createBaseNextConfig({ pwa })` call sites keep\n * type-checking and behave predictably (they return the config unchanged).\n *\n * @example\n * ```ts\n * import { createBaseNextConfig } from '@djangocfg/nextjs/config';\n * export default createBaseNextConfig({ pwa: { swSrc: 'app/sw.ts' } });\n * ```\n */\nexport function withPWA(\n nextConfig: NextConfig,\n _options: PWAPluginOptions = {}\n): NextConfig {\n return nextConfig;\n}\n\n/**\n * Vanilla service-worker template — no library.\n *\n * Returns ready-to-use, framework-free service worker source. Copy it into\n * `app/sw.ts` (or generate it) if you want offline support. It uses only the\n * standard Service Worker + Cache APIs, so it builds under Turbopack with no\n * extra dependency.\n *\n * @example\n * ```ts\n * import { getServiceWorkerTemplate } from '@djangocfg/nextjs/config';\n * console.log(getServiceWorkerTemplate()); // → paste into app/sw.ts\n * ```\n */\nexport function getServiceWorkerTemplate(): string {\n return `/**\n * Service Worker (vanilla) — standard Cache + Fetch APIs, no library.\n */\n\n/// <reference lib=\"webworker\" />\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ndeclare const self: any;\n\nconst CACHE = 'app-cache-v1';\nconst OFFLINE_URL = '/_offline';\n\n// Precache the offline fallback on install.\nself.addEventListener('install', (event: any) => {\n event.waitUntil(\n caches.open(CACHE).then((cache: Cache) => cache.addAll([OFFLINE_URL]))\n );\n self.skipWaiting();\n});\n\n// Drop stale caches on activate and take control immediately.\nself.addEventListener('activate', (event: any) => {\n event.waitUntil(\n caches.keys().then((keys: string[]) =>\n Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))\n )\n );\n self.clients.claim();\n});\n\n// Network-first for navigations, with an offline fallback.\nself.addEventListener('fetch', (event: any) => {\n const { request } = event;\n if (request.mode !== 'navigate') return;\n event.respondWith(\n fetch(request).catch(() =>\n caches.match(OFFLINE_URL).then((r: Response | undefined) => r ?? Response.error())\n )\n );\n});\n`;\n}\n"],"mappings":"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","names":[]}
@@ -1,41 +1,34 @@
1
1
  /**
2
- * Service Worker Utilities
2
+ * Service Worker Utilities — vanilla, dependency-free.
3
3
  *
4
- * Ready-to-use Serwist configuration for Next.js PWA
4
+ * Was a Serwist wrapper; serwist is a webpack-only plugin incompatible with
5
+ * Next.js 16's Turbopack, so this is now a hand-written service worker built on
6
+ * the standard Service Worker + Cache APIs. Same `createServiceWorker(options)`
7
+ * entry point, no library, Turbopack-safe.
5
8
  */
6
9
  interface ServiceWorkerOptions {
7
- /**
8
- * Offline fallback URL
9
- * @default '/_offline'
10
- */
10
+ /** Offline fallback URL. @default '/_offline' */
11
11
  offlineFallback?: string;
12
- /**
13
- * Skip waiting - activate new SW immediately
14
- * @default true
15
- */
12
+ /** Activate a new SW immediately instead of waiting. @default true */
16
13
  skipWaiting?: boolean;
17
- /**
18
- * Take control of all clients immediately
19
- * @default true
20
- */
14
+ /** Take control of open clients immediately. @default true */
21
15
  clientsClaim?: boolean;
22
- /**
23
- * Enable navigation preload for faster loads
24
- * @default true
25
- */
26
- navigationPreload?: boolean;
16
+ /** Name of the runtime cache bucket. @default 'app-cache-v1' */
17
+ cacheName?: string;
27
18
  }
28
19
  /**
29
- * Create and initialize Serwist service worker
20
+ * Register a minimal offline-capable service worker.
21
+ *
22
+ * Network-first for navigations with an offline fallback page; stale caches are
23
+ * cleared on activate. No precache manifest (that was a serwist build-time
24
+ * injection) — add your own `cache.addAll([...])` in the install step if you
25
+ * need assets precached.
30
26
  *
31
27
  * @example
32
28
  * ```ts
33
29
  * // app/sw.ts
34
30
  * import { createServiceWorker } from '@djangocfg/nextjs/worker';
35
- *
36
- * createServiceWorker({
37
- * offlineFallback: '/_offline',
38
- * });
31
+ * createServiceWorker({ offlineFallback: '/_offline' });
39
32
  * ```
40
33
  */
41
34
  declare function createServiceWorker(options?: ServiceWorkerOptions): void;
@@ -1,39 +1,36 @@
1
1
  // src/pwa/worker/index.ts
2
- import { Serwist } from "serwist";
3
- import { defaultCache } from "@serwist/next/worker";
4
2
  function createServiceWorker(options = {}) {
5
3
  const {
6
4
  offlineFallback = "/_offline",
7
5
  skipWaiting = true,
8
6
  clientsClaim = true,
9
- navigationPreload = true
7
+ cacheName = "app-cache-v1"
10
8
  } = options;
11
- const self = globalThis;
12
- console.log("SW: Worker Initialized \u{1F680}");
13
- const serwist = new Serwist({
14
- // Precache entries injected by Serwist build plugin
15
- precacheEntries: self.__SW_MANIFEST,
16
- // Skip waiting - activate new SW immediately
17
- skipWaiting,
18
- // Take control of all clients immediately
19
- clientsClaim,
20
- // Enable navigation preload for faster loads
21
- navigationPreload,
22
- // Use default Next.js runtime caching strategies
23
- runtimeCaching: defaultCache,
24
- // Fallback pages for offline
25
- fallbacks: {
26
- entries: [
27
- {
28
- url: offlineFallback,
29
- matcher({ request }) {
30
- return request.destination === "document";
31
- }
32
- }
33
- ]
34
- }
9
+ const sw = self;
10
+ sw.addEventListener("install", (event) => {
11
+ event.waitUntil(
12
+ caches.open(cacheName).then((cache) => cache.addAll([offlineFallback]))
13
+ );
14
+ if (skipWaiting) sw.skipWaiting();
15
+ });
16
+ sw.addEventListener("activate", (event) => {
17
+ event.waitUntil(
18
+ caches.keys().then(
19
+ (keys) => Promise.all(keys.filter((k) => k !== cacheName).map((k) => caches.delete(k)))
20
+ ).then(() => {
21
+ if (clientsClaim) return sw.clients.claim();
22
+ })
23
+ );
24
+ });
25
+ sw.addEventListener("fetch", (event) => {
26
+ const { request } = event;
27
+ if (request.mode !== "navigate") return;
28
+ event.respondWith(
29
+ fetch(request).catch(
30
+ () => caches.match(offlineFallback).then((r) => r ?? Response.error())
31
+ )
32
+ );
35
33
  });
36
- serwist.addEventListeners();
37
34
  }
38
35
  export {
39
36
  createServiceWorker
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/pwa/worker/index.ts"],"sourcesContent":["/**\n * Service Worker Utilities\n *\n * Ready-to-use Serwist configuration for Next.js PWA\n */\n\n/// <reference lib=\"webworker\" />\n\nimport { Serwist } from 'serwist';\n\nimport { defaultCache } from '@serwist/next/worker';\n\nexport interface ServiceWorkerOptions {\n /**\n * Offline fallback URL\n * @default '/_offline'\n */\n offlineFallback?: string;\n\n /**\n * Skip waiting - activate new SW immediately\n * @default true\n */\n skipWaiting?: boolean;\n\n /**\n * Take control of all clients immediately\n * @default true\n */\n clientsClaim?: boolean;\n\n /**\n * Enable navigation preload for faster loads\n * @default true\n */\n navigationPreload?: boolean;\n}\n\n/**\n * Create and initialize Serwist service worker\n *\n * @example\n * ```ts\n * // app/sw.ts\n * import { createServiceWorker } from '@djangocfg/nextjs/worker';\n *\n * createServiceWorker({\n * offlineFallback: '/_offline',\n * });\n * ```\n */\nexport function createServiceWorker(options: ServiceWorkerOptions = {}) {\n const {\n offlineFallback = '/_offline',\n skipWaiting = true,\n clientsClaim = true,\n navigationPreload = true,\n } = options;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const self = globalThis as any;\n console.log('SW: Worker Initialized 🚀');\n\n const serwist = new Serwist({\n // Precache entries injected by Serwist build plugin\n precacheEntries: self.__SW_MANIFEST,\n\n // Skip waiting - activate new SW immediately\n skipWaiting,\n\n // Take control of all clients immediately\n clientsClaim,\n\n // Enable navigation preload for faster loads\n navigationPreload,\n\n // Use default Next.js runtime caching strategies\n runtimeCaching: defaultCache,\n\n // Fallback pages for offline\n fallbacks: {\n entries: [\n {\n url: offlineFallback,\n matcher({ request }) {\n return request.destination === 'document';\n },\n },\n ],\n },\n });\n\n serwist.addEventListeners();\n}\n"],"mappings":";AAQA,SAAS,eAAe;AAExB,SAAS,oBAAoB;AAyCtB,SAAS,oBAAoB,UAAgC,CAAC,GAAG;AACtE,QAAM;AAAA,IACJ,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,oBAAoB;AAAA,EACtB,IAAI;AAGJ,QAAM,OAAO;AACb,UAAQ,IAAI,kCAA2B;AAEvC,QAAM,UAAU,IAAI,QAAQ;AAAA;AAAA,IAE1B,iBAAiB,KAAK;AAAA;AAAA,IAGtB;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA,gBAAgB;AAAA;AAAA,IAGhB,WAAW;AAAA,MACT,SAAS;AAAA,QACP;AAAA,UACE,KAAK;AAAA,UACL,QAAQ,EAAE,QAAQ,GAAG;AACnB,mBAAO,QAAQ,gBAAgB;AAAA,UACjC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,UAAQ,kBAAkB;AAC5B;","names":[]}
1
+ {"version":3,"sources":["../../../src/pwa/worker/index.ts"],"sourcesContent":["/**\n * Service Worker Utilities — vanilla, dependency-free.\n *\n * Was a Serwist wrapper; serwist is a webpack-only plugin incompatible with\n * Next.js 16's Turbopack, so this is now a hand-written service worker built on\n * the standard Service Worker + Cache APIs. Same `createServiceWorker(options)`\n * entry point, no library, Turbopack-safe.\n */\n\n/// <reference lib=\"webworker\" />\n\nexport interface ServiceWorkerOptions {\n /** Offline fallback URL. @default '/_offline' */\n offlineFallback?: string;\n /** Activate a new SW immediately instead of waiting. @default true */\n skipWaiting?: boolean;\n /** Take control of open clients immediately. @default true */\n clientsClaim?: boolean;\n /** Name of the runtime cache bucket. @default 'app-cache-v1' */\n cacheName?: string;\n}\n\n/**\n * Register a minimal offline-capable service worker.\n *\n * Network-first for navigations with an offline fallback page; stale caches are\n * cleared on activate. No precache manifest (that was a serwist build-time\n * injection) — add your own `cache.addAll([...])` in the install step if you\n * need assets precached.\n *\n * @example\n * ```ts\n * // app/sw.ts\n * import { createServiceWorker } from '@djangocfg/nextjs/worker';\n * createServiceWorker({ offlineFallback: '/_offline' });\n * ```\n */\nexport function createServiceWorker(options: ServiceWorkerOptions = {}): void {\n const {\n offlineFallback = '/_offline',\n skipWaiting = true,\n clientsClaim = true,\n cacheName = 'app-cache-v1',\n } = options;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const sw = self as any;\n\n sw.addEventListener('install', (event: any) => {\n event.waitUntil(\n caches.open(cacheName).then((cache: Cache) => cache.addAll([offlineFallback]))\n );\n if (skipWaiting) sw.skipWaiting();\n });\n\n sw.addEventListener('activate', (event: any) => {\n event.waitUntil(\n caches\n .keys()\n .then((keys: string[]) =>\n Promise.all(keys.filter((k) => k !== cacheName).map((k) => caches.delete(k)))\n )\n .then(() => {\n if (clientsClaim) return sw.clients.claim();\n })\n );\n });\n\n sw.addEventListener('fetch', (event: any) => {\n const { request } = event;\n if (request.mode !== 'navigate') return;\n event.respondWith(\n fetch(request).catch(() =>\n caches\n .match(offlineFallback)\n .then((r: Response | undefined) => r ?? Response.error())\n )\n );\n });\n}\n"],"mappings":";AAqCO,SAAS,oBAAoB,UAAgC,CAAC,GAAS;AAC5E,QAAM;AAAA,IACJ,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,YAAY;AAAA,EACd,IAAI;AAGJ,QAAM,KAAK;AAEX,KAAG,iBAAiB,WAAW,CAAC,UAAe;AAC7C,UAAM;AAAA,MACJ,OAAO,KAAK,SAAS,EAAE,KAAK,CAAC,UAAiB,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;AAAA,IAC/E;AACA,QAAI,YAAa,IAAG,YAAY;AAAA,EAClC,CAAC;AAED,KAAG,iBAAiB,YAAY,CAAC,UAAe;AAC9C,UAAM;AAAA,MACJ,OACG,KAAK,EACL;AAAA,QAAK,CAAC,SACL,QAAQ,IAAI,KAAK,OAAO,CAAC,MAAM,MAAM,SAAS,EAAE,IAAI,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC,CAAC;AAAA,MAC9E,EACC,KAAK,MAAM;AACV,YAAI,aAAc,QAAO,GAAG,QAAQ,MAAM;AAAA,MAC5C,CAAC;AAAA,IACL;AAAA,EACF,CAAC;AAED,KAAG,iBAAiB,SAAS,CAAC,UAAe;AAC3C,UAAM,EAAE,QAAQ,IAAI;AACpB,QAAI,QAAQ,SAAS,WAAY;AACjC,UAAM;AAAA,MACJ,MAAM,OAAO,EAAE;AAAA,QAAM,MACnB,OACG,MAAM,eAAe,EACrB,KAAK,CAAC,MAA4B,KAAK,SAAS,MAAM,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/nextjs",
3
- "version": "2.1.516",
3
+ "version": "2.1.518",
4
4
  "description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -149,9 +149,9 @@
149
149
  "ai-docs": "tsx src/ai/cli.ts"
150
150
  },
151
151
  "peerDependencies": {
152
- "@djangocfg/devtools": "^2.1.516",
153
- "@djangocfg/i18n": "^2.1.516",
154
- "@djangocfg/ui-core": "^2.1.516",
152
+ "@djangocfg/devtools": "^2.1.518",
153
+ "@djangocfg/i18n": "^2.1.518",
154
+ "@djangocfg/ui-core": "^2.1.518",
155
155
  "next": "^16.2.2"
156
156
  },
157
157
  "peerDependenciesMeta": {
@@ -163,21 +163,18 @@
163
163
  }
164
164
  },
165
165
  "dependencies": {
166
- "@serwist/next": "^9.5.11",
167
- "@serwist/sw": "^9.5.11",
168
166
  "chalk": "^5.6.2",
169
167
  "conf": "^15.1.0",
170
168
  "consola": "^3.4.2",
171
169
  "next-intl": "^4.13.2",
172
- "semver": "^7.8.5",
173
- "serwist": "^9.5.11"
170
+ "semver": "^7.8.5"
174
171
  },
175
172
  "devDependencies": {
176
- "@djangocfg/devtools": "^2.1.516",
177
- "@djangocfg/i18n": "^2.1.516",
178
- "@djangocfg/layouts": "^2.1.516",
179
- "@djangocfg/typescript-config": "^2.1.516",
180
- "@djangocfg/ui-core": "^2.1.516",
173
+ "@djangocfg/devtools": "^2.1.518",
174
+ "@djangocfg/i18n": "^2.1.518",
175
+ "@djangocfg/layouts": "^2.1.518",
176
+ "@djangocfg/typescript-config": "^2.1.518",
177
+ "@djangocfg/ui-core": "^2.1.518",
181
178
  "@types/node": "^25.9.5",
182
179
  "@types/react": "19.2.15",
183
180
  "@types/react-dom": "19.2.3",
@@ -4,4 +4,4 @@
4
4
 
5
5
  export { DevStartupPlugin, resetDevStartupState, type DevStartupPluginOptions } from './devStartup';
6
6
  export { addCompressionPlugins, isCompressionAvailable, type CompressionPluginOptions } from './compression';
7
- export { withPWA, defaultRuntimeCaching, type PWAPluginOptions } from '../../pwa/plugin';
7
+ export { withPWA, type PWAPluginOptions } from '../../pwa/plugin';