@djangocfg/nextjs 2.1.515 → 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,194 @@
1
+ import { MetadataRoute, Metadata, Viewport } from 'next';
2
+
3
+ /**
4
+ * PWA Manifest Metadata Utilities
5
+ *
6
+ * Helper functions for creating Next.js metadata for PWA manifest
7
+ */
8
+
9
+ interface ManifestConfig {
10
+ name: string;
11
+ shortName?: string;
12
+ description?: string;
13
+ themeColor?: string;
14
+ backgroundColor?: string;
15
+ display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
16
+ orientation?: 'portrait' | 'landscape' | 'any';
17
+ startUrl?: string;
18
+ scope?: string;
19
+ lang?: string;
20
+ dir?: 'ltr' | 'rtl' | 'auto';
21
+ icons?: {
22
+ src: string;
23
+ sizes: string;
24
+ type?: string;
25
+ purpose?: string;
26
+ }[];
27
+ }
28
+ /**
29
+ * Icon paths configuration
30
+ */
31
+ interface IconPaths {
32
+ logo192?: string;
33
+ logo384?: string;
34
+ logo512?: string;
35
+ }
36
+ /**
37
+ * Protocol handler configuration
38
+ * Allows your PWA to register as a handler for custom protocols
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * {
43
+ * protocol: "web+music",
44
+ * url: "/play?track=%s"
45
+ * }
46
+ * ```
47
+ */
48
+ interface ProtocolHandler {
49
+ /** Protocol scheme (e.g., "web+music", "mailto", "magnet") */
50
+ protocol: string;
51
+ /** URL template with %s placeholder for the protocol parameter */
52
+ url: string;
53
+ }
54
+ /**
55
+ * Create viewport configuration for Next.js app
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * export const viewport: Viewport = createViewport({
60
+ * themeColor: '#ffffff',
61
+ * });
62
+ * ```
63
+ */
64
+ declare function createViewport(config: {
65
+ themeColor?: string;
66
+ }): Viewport;
67
+ /**
68
+ * Create manifest metadata for Next.js app
69
+ *
70
+ * Note: themeColor and viewport should be exported separately using createViewport()
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * export const metadata: Metadata = {
75
+ * ...createManifestMetadata({
76
+ * name: 'My App',
77
+ * shortName: 'App',
78
+ * description: 'My awesome app',
79
+ * }),
80
+ * };
81
+ *
82
+ * export const viewport: Viewport = createViewport({
83
+ * themeColor: '#ffffff',
84
+ * });
85
+ * ```
86
+ */
87
+ declare function createManifestMetadata(config: ManifestConfig): Metadata;
88
+ /**
89
+ * Create Next.js manifest function
90
+ *
91
+ * Use this in your app/manifest.ts file
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * // app/manifest.ts
96
+ * import { createManifest } from '@djangocfg/nextjs/config';
97
+ * import { settings } from '@core/settings';
98
+ *
99
+ * export default createManifest({
100
+ * name: settings.app.name,
101
+ * description: settings.app.description,
102
+ * icons: {
103
+ * logo192: settings.app.icons.logo192,
104
+ * logo384: settings.app.icons.logo384,
105
+ * logo512: settings.app.icons.logo512,
106
+ * },
107
+ * });
108
+ * ```
109
+ */
110
+ interface ScreenshotConfig {
111
+ src: string;
112
+ sizes: string;
113
+ type?: string;
114
+ form_factor?: 'narrow' | 'wide';
115
+ label?: string;
116
+ }
117
+ /**
118
+ * Smart screenshot configuration
119
+ * Automatically detects everything from path or uses defaults
120
+ */
121
+ interface SmartScreenshotInput {
122
+ src: string;
123
+ /** Form factor (auto-detected from filename if contains 'desktop'/'mobile', or use default) */
124
+ form_factor?: 'narrow' | 'wide';
125
+ /** Optional label (auto-generated from form_factor) */
126
+ label?: string;
127
+ /** Optional width (defaults based on form_factor) */
128
+ width?: number;
129
+ /** Optional height (defaults based on form_factor) */
130
+ height?: number;
131
+ }
132
+ /**
133
+ * Create screenshot config with smart defaults
134
+ * Automatically detects type, sizes, form_factor from path or uses sensible defaults
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * // Minimal - everything auto-detected
139
+ * createScreenshot({ src: '/screenshots/desktop-view.png' })
140
+ * // → form_factor: 'wide', sizes: '1920x1080', type: 'image/png', label: 'Desktop screenshot'
141
+ *
142
+ * createScreenshot({ src: '/screenshots/mobile.png' })
143
+ * // → form_factor: 'narrow', sizes: '390x844', type: 'image/png', label: 'Mobile screenshot'
144
+ *
145
+ * // With custom dimensions
146
+ * createScreenshot({ src: '/screenshots/tablet.png', width: 1024, height: 768 })
147
+ * ```
148
+ */
149
+ declare function createScreenshot(input: SmartScreenshotInput | string): ScreenshotConfig;
150
+ /**
151
+ * Create multiple screenshots from array
152
+ * Supports string shorthand or full config objects
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * // Minimal - just paths
157
+ * createScreenshots([
158
+ * '/screenshots/desktop.png', // Auto: wide, 1920x1080
159
+ * '/screenshots/mobile.png', // Auto: narrow, 390x844
160
+ * ])
161
+ *
162
+ * // Mixed
163
+ * createScreenshots([
164
+ * '/screenshots/desktop.png',
165
+ * { src: '/screenshots/tablet.png', width: 1024, height: 768 },
166
+ * ])
167
+ * ```
168
+ */
169
+ declare function createScreenshots(inputs: Array<SmartScreenshotInput | string>): ScreenshotConfig[];
170
+ declare function createManifest(config: {
171
+ name: string;
172
+ shortName?: string;
173
+ description?: string;
174
+ themeColor?: string;
175
+ backgroundColor?: string;
176
+ display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
177
+ orientation?: 'portrait' | 'landscape' | 'any';
178
+ id?: string;
179
+ startUrl?: string;
180
+ scope?: string;
181
+ lang?: string;
182
+ dir?: 'ltr' | 'rtl' | 'auto';
183
+ icons?: IconPaths | ManifestConfig['icons'];
184
+ screenshots?: ScreenshotConfig[];
185
+ protocol_handlers?: ProtocolHandler[];
186
+ }): () => MetadataRoute.Manifest;
187
+ /**
188
+ * Generate manifest.json content (legacy)
189
+ *
190
+ * @deprecated Use createManifest() instead
191
+ */
192
+ declare function generateManifest(config: ManifestConfig): Record<string, any>;
193
+
194
+ export { type IconPaths, type ManifestConfig, type ProtocolHandler, type ScreenshotConfig, type SmartScreenshotInput, createManifest, createManifestMetadata, createScreenshot, createScreenshots, createViewport, generateManifest };
@@ -0,0 +1,136 @@
1
+ // src/pwa/manifest.ts
2
+ function createViewport(config) {
3
+ return {
4
+ width: "device-width",
5
+ initialScale: 1,
6
+ maximumScale: 1,
7
+ themeColor: config.themeColor || "#000000"
8
+ };
9
+ }
10
+ function createManifestMetadata(config) {
11
+ return {
12
+ manifest: "/manifest.json",
13
+ appleWebApp: {
14
+ capable: true,
15
+ statusBarStyle: "default",
16
+ title: config.shortName || config.name
17
+ },
18
+ applicationName: config.name,
19
+ formatDetection: {
20
+ telephone: false
21
+ }
22
+ };
23
+ }
24
+ function createScreenshot(input) {
25
+ const config = typeof input === "string" ? { src: input } : input;
26
+ let { src, width, height, label, form_factor } = config;
27
+ const ext = src.split(".").pop()?.toLowerCase();
28
+ const typeMap = {
29
+ png: "image/png",
30
+ jpg: "image/jpeg",
31
+ jpeg: "image/jpeg",
32
+ webp: "image/webp",
33
+ svg: "image/svg+xml"
34
+ };
35
+ const type = ext ? typeMap[ext] || "image/png" : "image/png";
36
+ const filename = src.toLowerCase();
37
+ const dimensionMatch = filename.match(/(\d{3,4})x(\d{3,4})/);
38
+ if (dimensionMatch && !width && !height) {
39
+ width = parseInt(dimensionMatch[1], 10);
40
+ height = parseInt(dimensionMatch[2], 10);
41
+ }
42
+ let autoFormFactor = "wide";
43
+ if (filename.includes("mobile") || filename.includes("phone") || filename.includes("narrow")) {
44
+ autoFormFactor = "narrow";
45
+ } else if (filename.includes("desktop") || filename.includes("laptop") || filename.includes("wide")) {
46
+ autoFormFactor = "wide";
47
+ } else if (width && height) {
48
+ const aspectRatio = width / height;
49
+ autoFormFactor = aspectRatio > 1.2 ? "wide" : "narrow";
50
+ }
51
+ const finalFormFactor = form_factor || autoFormFactor;
52
+ const defaultDimensions = finalFormFactor === "wide" ? { width: 1920, height: 1080 } : { width: 390, height: 844 };
53
+ const finalWidth = width || defaultDimensions.width;
54
+ const finalHeight = height || defaultDimensions.height;
55
+ const autoLabel = finalFormFactor === "wide" ? "Desktop screenshot" : "Mobile screenshot";
56
+ return {
57
+ src,
58
+ sizes: `${finalWidth}x${finalHeight}`,
59
+ type,
60
+ form_factor: finalFormFactor,
61
+ label: label || autoLabel
62
+ };
63
+ }
64
+ function createScreenshots(inputs) {
65
+ return inputs.map(createScreenshot);
66
+ }
67
+ function createManifest(config) {
68
+ return () => {
69
+ let manifestIcons;
70
+ if (Array.isArray(config.icons)) {
71
+ manifestIcons = config.icons;
72
+ } else if (config.icons) {
73
+ const { logo192, logo384, logo512 } = config.icons;
74
+ manifestIcons = [
75
+ ...logo192 ? [
76
+ {
77
+ src: logo192,
78
+ sizes: "192x192",
79
+ type: "image/png",
80
+ purpose: "maskable"
81
+ }
82
+ ] : [],
83
+ ...logo384 ? [
84
+ {
85
+ src: logo384,
86
+ sizes: "384x384",
87
+ type: "image/png"
88
+ }
89
+ ] : [],
90
+ ...logo512 ? [
91
+ {
92
+ src: logo512,
93
+ sizes: "512x512",
94
+ type: "image/png"
95
+ }
96
+ ] : []
97
+ ];
98
+ }
99
+ const manifest = {
100
+ name: config.name,
101
+ short_name: config.shortName || config.name,
102
+ description: config.description || config.name,
103
+ id: config.id || config.startUrl || "/",
104
+ start_url: config.startUrl || "/",
105
+ scope: config.scope || "/",
106
+ display: config.display || "standalone",
107
+ orientation: config.orientation || "portrait",
108
+ background_color: config.backgroundColor || "#000000",
109
+ theme_color: config.themeColor || "#ffffff",
110
+ lang: config.lang || "en",
111
+ dir: config.dir || "ltr",
112
+ icons: manifestIcons
113
+ // Removed forced gcm_sender_id to avoid potential conflicts with VAPID
114
+ // gcm_sender_id: '103953800507',
115
+ };
116
+ if (config.screenshots && config.screenshots.length > 0) {
117
+ manifest.screenshots = config.screenshots;
118
+ }
119
+ if (config.protocol_handlers && config.protocol_handlers.length > 0) {
120
+ manifest.protocol_handlers = config.protocol_handlers;
121
+ }
122
+ return manifest;
123
+ };
124
+ }
125
+ function generateManifest(config) {
126
+ return createManifest(config)();
127
+ }
128
+ export {
129
+ createManifest,
130
+ createManifestMetadata,
131
+ createScreenshot,
132
+ createScreenshots,
133
+ createViewport,
134
+ generateManifest
135
+ };
136
+ //# sourceMappingURL=manifest.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/pwa/manifest.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"],"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.515",
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",
@@ -64,6 +64,11 @@
64
64
  "import": "./dist/pwa/index.mjs",
65
65
  "default": "./dist/pwa/index.mjs"
66
66
  },
67
+ "./pwa/manifest": {
68
+ "types": "./dist/pwa/manifest.d.mts",
69
+ "import": "./dist/pwa/manifest.mjs",
70
+ "default": "./dist/pwa/manifest.mjs"
71
+ },
67
72
  "./pwa/worker": {
68
73
  "types": "./dist/pwa/worker/index.d.mts",
69
74
  "import": "./dist/pwa/worker/index.mjs",
@@ -144,9 +149,9 @@
144
149
  "ai-docs": "tsx src/ai/cli.ts"
145
150
  },
146
151
  "peerDependencies": {
147
- "@djangocfg/devtools": "^2.1.515",
148
- "@djangocfg/i18n": "^2.1.515",
149
- "@djangocfg/ui-core": "^2.1.515",
152
+ "@djangocfg/devtools": "^2.1.518",
153
+ "@djangocfg/i18n": "^2.1.518",
154
+ "@djangocfg/ui-core": "^2.1.518",
150
155
  "next": "^16.2.2"
151
156
  },
152
157
  "peerDependenciesMeta": {
@@ -158,21 +163,18 @@
158
163
  }
159
164
  },
160
165
  "dependencies": {
161
- "@serwist/next": "^9.5.11",
162
- "@serwist/sw": "^9.5.11",
163
166
  "chalk": "^5.6.2",
164
167
  "conf": "^15.1.0",
165
168
  "consola": "^3.4.2",
166
169
  "next-intl": "^4.13.2",
167
- "semver": "^7.8.5",
168
- "serwist": "^9.5.11"
170
+ "semver": "^7.8.5"
169
171
  },
170
172
  "devDependencies": {
171
- "@djangocfg/devtools": "^2.1.515",
172
- "@djangocfg/i18n": "^2.1.515",
173
- "@djangocfg/layouts": "^2.1.515",
174
- "@djangocfg/typescript-config": "^2.1.515",
175
- "@djangocfg/ui-core": "^2.1.515",
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",
176
178
  "@types/node": "^25.9.5",
177
179
  "@types/react": "19.2.15",
178
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';