@djangocfg/nextjs 2.1.36 → 2.1.37
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.
- package/README.md +146 -1
- package/dist/config/index.d.mts +7 -428
- package/dist/config/index.mjs +80 -396
- package/dist/config/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +80 -396
- package/dist/index.mjs.map +1 -1
- package/dist/plugin-DuRJ_Jq6.d.mts +100 -0
- package/dist/pwa/cli.d.mts +1 -0
- package/dist/pwa/cli.mjs +140 -0
- package/dist/pwa/cli.mjs.map +1 -0
- package/dist/pwa/index.d.mts +274 -0
- package/dist/pwa/index.mjs +327 -0
- package/dist/pwa/index.mjs.map +1 -0
- package/dist/pwa/server/index.d.mts +86 -0
- package/dist/pwa/server/index.mjs +175 -0
- package/dist/pwa/server/index.mjs.map +1 -0
- package/dist/pwa/server/routes.d.mts +2 -0
- package/dist/pwa/server/routes.mjs +149 -0
- package/dist/pwa/server/routes.mjs.map +1 -0
- package/dist/pwa/worker/index.d.mts +56 -0
- package/dist/pwa/worker/index.mjs +97 -0
- package/dist/pwa/worker/index.mjs.map +1 -0
- package/dist/routes-DXA29sS_.d.mts +68 -0
- package/package.json +39 -8
- package/src/config/createNextConfig.ts +9 -13
- package/src/config/index.ts +2 -20
- package/src/config/plugins/devStartup.ts +35 -36
- package/src/config/plugins/index.ts +1 -1
- package/src/config/utils/index.ts +0 -1
- package/src/index.ts +4 -0
- package/src/pwa/cli.ts +171 -0
- package/src/pwa/index.ts +9 -0
- package/src/pwa/manifest.ts +355 -0
- package/src/pwa/notifications.ts +192 -0
- package/src/pwa/plugin.ts +194 -0
- package/src/pwa/server/index.ts +23 -0
- package/src/pwa/server/push.ts +166 -0
- package/src/pwa/server/routes.ts +137 -0
- package/src/pwa/worker/index.ts +174 -0
- package/src/pwa/worker/package.json +3 -0
- package/src/config/plugins/pwa.ts +0 -616
- package/src/config/utils/manifest.ts +0 -214
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// src/pwa/server/routes.ts
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
|
|
4
|
+
// src/pwa/server/push.ts
|
|
5
|
+
import webpush from "web-push";
|
|
6
|
+
import { consola } from "consola";
|
|
7
|
+
var vapidConfigured = false;
|
|
8
|
+
function getVapidKeys() {
|
|
9
|
+
const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;
|
|
10
|
+
const privateKey = process.env.VAPID_PRIVATE_KEY;
|
|
11
|
+
const mailto = process.env.VAPID_MAILTO || "mailto:noreply@example.com";
|
|
12
|
+
return { publicKey, privateKey, mailto };
|
|
13
|
+
}
|
|
14
|
+
function configureVapid(options) {
|
|
15
|
+
const { publicKey, privateKey, mailto } = options || getVapidKeys();
|
|
16
|
+
if (!publicKey || !privateKey) {
|
|
17
|
+
consola.warn(
|
|
18
|
+
"\u26A0\uFE0F VAPID keys not configured!\n Generate keys: npx web-push generate-vapid-keys\n Add to .env.local:\n NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key\n VAPID_PRIVATE_KEY=your_private_key\n Push notifications will not work without VAPID keys."
|
|
19
|
+
);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
webpush.setVapidDetails(mailto, publicKey, privateKey);
|
|
24
|
+
vapidConfigured = true;
|
|
25
|
+
consola.success("\u2705 VAPID keys configured for push notifications");
|
|
26
|
+
return true;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
consola.error("Failed to configure VAPID keys:", error.message);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function sendPushNotification(subscription, notification) {
|
|
33
|
+
if (!vapidConfigured) {
|
|
34
|
+
const configured = configureVapid();
|
|
35
|
+
if (!configured) {
|
|
36
|
+
throw new Error("VAPID keys not configured. Cannot send push notification.");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const payload = JSON.stringify({
|
|
40
|
+
title: notification.title,
|
|
41
|
+
body: notification.body || "",
|
|
42
|
+
icon: notification.icon,
|
|
43
|
+
badge: notification.badge,
|
|
44
|
+
data: notification.data,
|
|
45
|
+
tag: notification.tag,
|
|
46
|
+
requireInteraction: notification.requireInteraction
|
|
47
|
+
});
|
|
48
|
+
const result = await webpush.sendNotification(subscription, payload);
|
|
49
|
+
console.log("\u2705 Push Sent to FCM:", {
|
|
50
|
+
statusCode: result.statusCode,
|
|
51
|
+
headers: result.headers,
|
|
52
|
+
bodyLength: result.body?.length
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function validateSubscription(subscription) {
|
|
56
|
+
return subscription && typeof subscription === "object" && typeof subscription.endpoint === "string" && subscription.keys && typeof subscription.keys.p256dh === "string" && typeof subscription.keys.auth === "string";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/pwa/server/routes.ts
|
|
60
|
+
var subscriptions = /* @__PURE__ */ new Set();
|
|
61
|
+
async function handleSubscribe(request) {
|
|
62
|
+
try {
|
|
63
|
+
const subscription = await request.json();
|
|
64
|
+
if (!validateSubscription(subscription)) {
|
|
65
|
+
return NextResponse.json(
|
|
66
|
+
{ error: "Invalid subscription format" },
|
|
67
|
+
{ status: 400 }
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
subscriptions.add(JSON.stringify(subscription));
|
|
71
|
+
console.log("\u2705 Push subscription saved:", {
|
|
72
|
+
endpoint: subscription.endpoint.substring(0, 50) + "...",
|
|
73
|
+
total: subscriptions.size
|
|
74
|
+
});
|
|
75
|
+
return NextResponse.json({
|
|
76
|
+
success: true,
|
|
77
|
+
message: "Subscription saved",
|
|
78
|
+
totalSubscriptions: subscriptions.size
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error("Subscription error:", error);
|
|
82
|
+
return NextResponse.json(
|
|
83
|
+
{
|
|
84
|
+
error: "Failed to save subscription",
|
|
85
|
+
details: error.message
|
|
86
|
+
},
|
|
87
|
+
{ status: 500 }
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function handleGetSubscriptions() {
|
|
92
|
+
return NextResponse.json({
|
|
93
|
+
totalSubscriptions: subscriptions.size,
|
|
94
|
+
subscriptions: Array.from(subscriptions).map((sub) => {
|
|
95
|
+
const parsed = JSON.parse(sub);
|
|
96
|
+
return {
|
|
97
|
+
endpoint: parsed.endpoint.substring(0, 50) + "..."
|
|
98
|
+
};
|
|
99
|
+
})
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async function handleSend(request) {
|
|
103
|
+
try {
|
|
104
|
+
const { subscription, notification } = await request.json();
|
|
105
|
+
if (!subscription) {
|
|
106
|
+
return NextResponse.json(
|
|
107
|
+
{ error: "Subscription is required" },
|
|
108
|
+
{ status: 400 }
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (!validateSubscription(subscription)) {
|
|
112
|
+
return NextResponse.json(
|
|
113
|
+
{ error: "Invalid subscription format" },
|
|
114
|
+
{ status: 400 }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
configureVapid();
|
|
118
|
+
await sendPushNotification(subscription, {
|
|
119
|
+
title: notification?.title || "Test Notification",
|
|
120
|
+
body: notification?.body || "This is a test push notification",
|
|
121
|
+
icon: notification?.icon,
|
|
122
|
+
badge: notification?.badge,
|
|
123
|
+
data: notification?.data
|
|
124
|
+
});
|
|
125
|
+
return NextResponse.json({
|
|
126
|
+
success: true,
|
|
127
|
+
message: "Push notification sent"
|
|
128
|
+
});
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error("Push notification error:", error);
|
|
131
|
+
return NextResponse.json(
|
|
132
|
+
{
|
|
133
|
+
error: "Failed to send push notification",
|
|
134
|
+
details: error.message
|
|
135
|
+
},
|
|
136
|
+
{ status: 500 }
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
var POST = handleSubscribe;
|
|
141
|
+
var GET = handleGetSubscriptions;
|
|
142
|
+
export {
|
|
143
|
+
GET,
|
|
144
|
+
POST,
|
|
145
|
+
handleGetSubscriptions,
|
|
146
|
+
handleSend,
|
|
147
|
+
handleSubscribe
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=routes.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/pwa/server/routes.ts","../../../src/pwa/server/push.ts"],"sourcesContent":["/**\n * Ready-to-use Push Notification Route Handlers\n *\n * Import these in your app/api/push/ routes\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { configureVapid, sendPushNotification, validateSubscription } from './push';\n\n// In-memory storage для demo (в production используй БД)\nconst subscriptions = new Set<string>();\n\n/**\n * POST /api/push/subscribe\n * Save push subscription\n *\n * @example\n * ```typescript\n * // app/api/push/subscribe/route.ts\n * export { POST } from '@djangocfg/nextjs/pwa/server/routes';\n * ```\n */\nexport async function handleSubscribe(request: NextRequest) {\n try {\n const subscription = await request.json();\n\n if (!validateSubscription(subscription)) {\n return NextResponse.json(\n { error: 'Invalid subscription format' },\n { status: 400 }\n );\n }\n\n // Сохраняем subscription (в demo просто в памяти)\n subscriptions.add(JSON.stringify(subscription));\n\n console.log('✅ Push subscription saved:', {\n endpoint: subscription.endpoint.substring(0, 50) + '...',\n total: subscriptions.size,\n });\n\n return NextResponse.json({\n success: true,\n message: 'Subscription saved',\n totalSubscriptions: subscriptions.size,\n });\n } catch (error: any) {\n console.error('Subscription error:', error);\n\n return NextResponse.json(\n {\n error: 'Failed to save subscription',\n details: error.message,\n },\n { status: 500 }\n );\n }\n}\n\n/**\n * GET /api/push/subscribe\n * Get all subscriptions (for testing)\n */\nexport async function handleGetSubscriptions() {\n return NextResponse.json({\n totalSubscriptions: subscriptions.size,\n subscriptions: Array.from(subscriptions).map((sub) => {\n const parsed = JSON.parse(sub);\n return {\n endpoint: parsed.endpoint.substring(0, 50) + '...',\n };\n }),\n });\n}\n\n/**\n * POST /api/push/send\n * Send push notification\n *\n * @example\n * ```typescript\n * // app/api/push/send/route.ts\n * export { POST as handleSend as POST } from '@djangocfg/nextjs/pwa/server/routes';\n * ```\n */\nexport async function handleSend(request: NextRequest) {\n try {\n const { subscription, notification } = await request.json();\n\n if (!subscription) {\n return NextResponse.json(\n { error: 'Subscription is required' },\n { status: 400 }\n );\n }\n\n if (!validateSubscription(subscription)) {\n return NextResponse.json(\n { error: 'Invalid subscription format' },\n { status: 400 }\n );\n }\n\n // Configure VAPID if not already configured\n configureVapid();\n\n await sendPushNotification(subscription, {\n title: notification?.title || 'Test Notification',\n body: notification?.body || 'This is a test push notification',\n icon: notification?.icon,\n badge: notification?.badge,\n data: notification?.data,\n });\n\n return NextResponse.json({\n success: true,\n message: 'Push notification sent',\n });\n } catch (error: any) {\n console.error('Push notification error:', error);\n\n return NextResponse.json(\n {\n error: 'Failed to send push notification',\n details: error.message,\n },\n { status: 500 }\n );\n }\n}\n\n/**\n * Combined route handlers\n * Use like: export { POST, GET } from '@djangocfg/nextjs/pwa/server/routes'\n */\nexport const POST = handleSubscribe;\nexport const GET = handleGetSubscriptions;\n","/**\n * Server-side Push Notification Utilities\n *\n * VAPID-based Web Push notifications using web-push library\n */\n\nimport webpush, { PushSubscription } from 'web-push';\nimport { consola } from 'consola';\n\nlet vapidConfigured = false;\n\n/**\n * Check if VAPID keys are configured\n */\nexport function isVapidConfigured(): boolean {\n return vapidConfigured;\n}\n\n/**\n * Get VAPID keys from environment\n */\nexport function getVapidKeys() {\n const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;\n const privateKey = process.env.VAPID_PRIVATE_KEY;\n const mailto = process.env.VAPID_MAILTO || 'mailto:noreply@example.com';\n\n return { publicKey, privateKey, mailto };\n}\n\n/**\n * Configure VAPID keys for web-push\n * Call this once at app startup\n *\n * @example\n * ```typescript\n * // In your API route or middleware\n * import { configureVapid } from '@djangocfg/nextjs/pwa/server';\n *\n * configureVapid(); // Uses env vars automatically\n * ```\n */\nexport function configureVapid(options?: {\n publicKey?: string;\n privateKey?: string;\n mailto?: string;\n}): boolean {\n const { publicKey, privateKey, mailto } = options || getVapidKeys();\n\n if (!publicKey || !privateKey) {\n consola.warn(\n '⚠️ VAPID keys not configured!\\n' +\n ' Generate keys: npx web-push generate-vapid-keys\\n' +\n ' Add to .env.local:\\n' +\n ' NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key\\n' +\n ' VAPID_PRIVATE_KEY=your_private_key\\n' +\n ' Push notifications will not work without VAPID keys.'\n );\n return false;\n }\n\n try {\n webpush.setVapidDetails(mailto, publicKey, privateKey);\n vapidConfigured = true;\n consola.success('✅ VAPID keys configured for push notifications');\n return true;\n } catch (error: any) {\n consola.error('Failed to configure VAPID keys:', error.message);\n return false;\n }\n}\n\n/**\n * Send push notification to a subscription\n *\n * @example\n * ```typescript\n * await sendPushNotification(subscription, {\n * title: 'Hello!',\n * body: 'Test notification',\n * data: { url: '/page' },\n * });\n * ```\n */\nexport async function sendPushNotification(\n subscription: PushSubscription,\n notification: {\n title: string;\n body?: string;\n icon?: string;\n badge?: string;\n data?: any;\n tag?: string;\n requireInteraction?: boolean;\n }\n): Promise<void> {\n if (!vapidConfigured) {\n const configured = configureVapid();\n if (!configured) {\n throw new Error('VAPID keys not configured. Cannot send push notification.');\n }\n }\n\n const payload = JSON.stringify({\n title: notification.title,\n body: notification.body || '',\n icon: notification.icon,\n badge: notification.badge,\n data: notification.data,\n tag: notification.tag,\n requireInteraction: notification.requireInteraction,\n });\n\n const result = await webpush.sendNotification(subscription, payload);\n console.log('✅ Push Sent to FCM:', {\n statusCode: result.statusCode,\n headers: result.headers,\n bodyLength: result.body?.length\n });\n}\n\n/**\n * Send push notification to multiple subscriptions\n *\n * @example\n * ```typescript\n * const results = await sendPushToMultiple(subscriptions, {\n * title: 'Broadcast message',\n * body: 'Sent to all users',\n * });\n * console.log(`Sent: ${results.successful}, Failed: ${results.failed}`);\n * ```\n */\nexport async function sendPushToMultiple(\n subscriptions: PushSubscription[],\n notification: Parameters<typeof sendPushNotification>[1]\n): Promise<{\n successful: number;\n failed: number;\n errors: Array<{ subscription: PushSubscription; error: Error }>;\n}> {\n const results = await Promise.allSettled(\n subscriptions.map((sub) => sendPushNotification(sub, notification))\n );\n\n const successful = results.filter((r) => r.status === 'fulfilled').length;\n const failed = results.filter((r) => r.status === 'rejected').length;\n const errors = results\n .map((r, i) => (r.status === 'rejected' ? { subscription: subscriptions[i], error: r.reason } : null))\n .filter((e): e is NonNullable<typeof e> => e !== null);\n\n return { successful, failed, errors };\n}\n\n/**\n * Validate push subscription format\n */\nexport function validateSubscription(subscription: any): subscription is PushSubscription {\n return (\n subscription &&\n typeof subscription === 'object' &&\n typeof subscription.endpoint === 'string' &&\n subscription.keys &&\n typeof subscription.keys.p256dh === 'string' &&\n typeof subscription.keys.auth === 'string'\n );\n}\n"],"mappings":";AAMA,SAAsB,oBAAoB;;;ACA1C,OAAO,aAAmC;AAC1C,SAAS,eAAe;AAExB,IAAI,kBAAkB;AAYf,SAAS,eAAe;AAC7B,QAAM,YAAY,QAAQ,IAAI;AAC9B,QAAM,aAAa,QAAQ,IAAI;AAC/B,QAAM,SAAS,QAAQ,IAAI,gBAAgB;AAE3C,SAAO,EAAE,WAAW,YAAY,OAAO;AACzC;AAcO,SAAS,eAAe,SAInB;AACV,QAAM,EAAE,WAAW,YAAY,OAAO,IAAI,WAAW,aAAa;AAElE,MAAI,CAAC,aAAa,CAAC,YAAY;AAC7B,YAAQ;AAAA,MACN;AAAA,IAMF;AACA,WAAO;AAAA,EACT;AAEA,MAAI;AACF,YAAQ,gBAAgB,QAAQ,WAAW,UAAU;AACrD,sBAAkB;AAClB,YAAQ,QAAQ,qDAAgD;AAChE,WAAO;AAAA,EACT,SAAS,OAAY;AACnB,YAAQ,MAAM,mCAAmC,MAAM,OAAO;AAC9D,WAAO;AAAA,EACT;AACF;AAcA,eAAsB,qBACpB,cACA,cASe;AACf,MAAI,CAAC,iBAAiB;AACpB,UAAM,aAAa,eAAe;AAClC,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,2DAA2D;AAAA,IAC7E;AAAA,EACF;AAEA,QAAM,UAAU,KAAK,UAAU;AAAA,IAC7B,OAAO,aAAa;AAAA,IACpB,MAAM,aAAa,QAAQ;AAAA,IAC3B,MAAM,aAAa;AAAA,IACnB,OAAO,aAAa;AAAA,IACpB,MAAM,aAAa;AAAA,IACnB,KAAK,aAAa;AAAA,IAClB,oBAAoB,aAAa;AAAA,EACnC,CAAC;AAED,QAAM,SAAS,MAAM,QAAQ,iBAAiB,cAAc,OAAO;AACnE,UAAQ,IAAI,4BAAuB;AAAA,IACjC,YAAY,OAAO;AAAA,IACnB,SAAS,OAAO;AAAA,IAChB,YAAY,OAAO,MAAM;AAAA,EAC3B,CAAC;AACH;AAsCO,SAAS,qBAAqB,cAAqD;AACxF,SACE,gBACA,OAAO,iBAAiB,YACxB,OAAO,aAAa,aAAa,YACjC,aAAa,QACb,OAAO,aAAa,KAAK,WAAW,YACpC,OAAO,aAAa,KAAK,SAAS;AAEtC;;;AD3JA,IAAM,gBAAgB,oBAAI,IAAY;AAYtC,eAAsB,gBAAgB,SAAsB;AAC1D,MAAI;AACF,UAAM,eAAe,MAAM,QAAQ,KAAK;AAExC,QAAI,CAAC,qBAAqB,YAAY,GAAG;AACvC,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,8BAA8B;AAAA,QACvC,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAGA,kBAAc,IAAI,KAAK,UAAU,YAAY,CAAC;AAE9C,YAAQ,IAAI,mCAA8B;AAAA,MACxC,UAAU,aAAa,SAAS,UAAU,GAAG,EAAE,IAAI;AAAA,MACnD,OAAO,cAAc;AAAA,IACvB,CAAC;AAED,WAAO,aAAa,KAAK;AAAA,MACvB,SAAS;AAAA,MACT,SAAS;AAAA,MACT,oBAAoB,cAAc;AAAA,IACpC,CAAC;AAAA,EACH,SAAS,OAAY;AACnB,YAAQ,MAAM,uBAAuB,KAAK;AAE1C,WAAO,aAAa;AAAA,MAClB;AAAA,QACE,OAAO;AAAA,QACP,SAAS,MAAM;AAAA,MACjB;AAAA,MACA,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;AAMA,eAAsB,yBAAyB;AAC7C,SAAO,aAAa,KAAK;AAAA,IACvB,oBAAoB,cAAc;AAAA,IAClC,eAAe,MAAM,KAAK,aAAa,EAAE,IAAI,CAAC,QAAQ;AACpD,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,aAAO;AAAA,QACL,UAAU,OAAO,SAAS,UAAU,GAAG,EAAE,IAAI;AAAA,MAC/C;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAYA,eAAsB,WAAW,SAAsB;AACrD,MAAI;AACF,UAAM,EAAE,cAAc,aAAa,IAAI,MAAM,QAAQ,KAAK;AAE1D,QAAI,CAAC,cAAc;AACjB,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,2BAA2B;AAAA,QACpC,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,QAAI,CAAC,qBAAqB,YAAY,GAAG;AACvC,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,8BAA8B;AAAA,QACvC,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAGA,mBAAe;AAEf,UAAM,qBAAqB,cAAc;AAAA,MACvC,OAAO,cAAc,SAAS;AAAA,MAC9B,MAAM,cAAc,QAAQ;AAAA,MAC5B,MAAM,cAAc;AAAA,MACpB,OAAO,cAAc;AAAA,MACrB,MAAM,cAAc;AAAA,IACtB,CAAC;AAED,WAAO,aAAa,KAAK;AAAA,MACvB,SAAS;AAAA,MACT,SAAS;AAAA,IACX,CAAC;AAAA,EACH,SAAS,OAAY;AACnB,YAAQ,MAAM,4BAA4B,KAAK;AAE/C,WAAO,aAAa;AAAA,MAClB;AAAA,QACE,OAAO;AAAA,QACP,SAAS,MAAM;AAAA,MACjB;AAAA,MACA,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;AAMO,IAAM,OAAO;AACb,IAAM,MAAM;","names":[]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Worker Utilities
|
|
3
|
+
*
|
|
4
|
+
* Ready-to-use Serwist configuration for Next.js PWA
|
|
5
|
+
*/
|
|
6
|
+
interface ServiceWorkerOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Offline fallback URL
|
|
9
|
+
* @default '/_offline'
|
|
10
|
+
*/
|
|
11
|
+
offlineFallback?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Skip waiting - activate new SW immediately
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
skipWaiting?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Take control of all clients immediately
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
clientsClaim?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Enable navigation preload for faster loads
|
|
24
|
+
* @default true
|
|
25
|
+
*/
|
|
26
|
+
navigationPreload?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Enable push notifications
|
|
29
|
+
* @default false
|
|
30
|
+
*/
|
|
31
|
+
enablePushNotifications?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Default notification icon
|
|
34
|
+
*/
|
|
35
|
+
notificationIcon?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Default notification badge
|
|
38
|
+
*/
|
|
39
|
+
notificationBadge?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create and initialize Serwist service worker
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* // app/sw.ts
|
|
47
|
+
* import { createServiceWorker } from '@djangocfg/nextjs/worker';
|
|
48
|
+
*
|
|
49
|
+
* createServiceWorker({
|
|
50
|
+
* offlineFallback: '/_offline',
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare function createServiceWorker(options?: ServiceWorkerOptions): void;
|
|
55
|
+
|
|
56
|
+
export { type ServiceWorkerOptions, createServiceWorker };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/pwa/worker/index.ts
|
|
2
|
+
import { defaultCache } from "@serwist/next/worker";
|
|
3
|
+
import { Serwist } from "serwist";
|
|
4
|
+
function createServiceWorker(options = {}) {
|
|
5
|
+
const {
|
|
6
|
+
offlineFallback = "/_offline",
|
|
7
|
+
skipWaiting = true,
|
|
8
|
+
clientsClaim = true,
|
|
9
|
+
navigationPreload = true,
|
|
10
|
+
enablePushNotifications = false,
|
|
11
|
+
notificationIcon,
|
|
12
|
+
notificationBadge
|
|
13
|
+
} = options;
|
|
14
|
+
const self = globalThis;
|
|
15
|
+
console.log("SW: Worker Initialized \u{1F680}");
|
|
16
|
+
const serwist = new Serwist({
|
|
17
|
+
// Precache entries injected by Serwist build plugin
|
|
18
|
+
precacheEntries: self.__SW_MANIFEST,
|
|
19
|
+
// Skip waiting - activate new SW immediately
|
|
20
|
+
skipWaiting,
|
|
21
|
+
// Take control of all clients immediately
|
|
22
|
+
clientsClaim,
|
|
23
|
+
// Enable navigation preload for faster loads
|
|
24
|
+
navigationPreload,
|
|
25
|
+
// Use default Next.js runtime caching strategies
|
|
26
|
+
runtimeCaching: defaultCache,
|
|
27
|
+
// Fallback pages for offline
|
|
28
|
+
fallbacks: {
|
|
29
|
+
entries: [
|
|
30
|
+
{
|
|
31
|
+
url: offlineFallback,
|
|
32
|
+
matcher({ request }) {
|
|
33
|
+
return request.destination === "document";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
serwist.addEventListeners();
|
|
40
|
+
if (enablePushNotifications) {
|
|
41
|
+
self.addEventListener("push", (event) => {
|
|
42
|
+
let data;
|
|
43
|
+
try {
|
|
44
|
+
data = event.data?.json() || {};
|
|
45
|
+
console.log("SW: Push Received (JSON)", data);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.log("SW: Push Received (Raw)", event.data?.text());
|
|
48
|
+
data = {};
|
|
49
|
+
}
|
|
50
|
+
const {
|
|
51
|
+
title = "Notification",
|
|
52
|
+
body = "",
|
|
53
|
+
icon = notificationIcon,
|
|
54
|
+
badge = notificationBadge,
|
|
55
|
+
data: notificationData = {},
|
|
56
|
+
tag,
|
|
57
|
+
requireInteraction = false,
|
|
58
|
+
vibrate = [200, 100, 200],
|
|
59
|
+
// Default vibration pattern
|
|
60
|
+
silent = false
|
|
61
|
+
// Default to playing sound
|
|
62
|
+
} = data;
|
|
63
|
+
event.waitUntil(
|
|
64
|
+
self.registration.showNotification(title, {
|
|
65
|
+
body,
|
|
66
|
+
icon,
|
|
67
|
+
badge,
|
|
68
|
+
data: notificationData,
|
|
69
|
+
tag,
|
|
70
|
+
requireInteraction,
|
|
71
|
+
vibrate,
|
|
72
|
+
silent
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
self.addEventListener("notificationclick", (event) => {
|
|
77
|
+
event.notification.close();
|
|
78
|
+
const urlToOpen = event.notification.data?.url || "/";
|
|
79
|
+
event.waitUntil(
|
|
80
|
+
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
|
|
81
|
+
for (const client of clientList) {
|
|
82
|
+
if (client.url === urlToOpen && "focus" in client) {
|
|
83
|
+
return client.focus();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (self.clients.openWindow) {
|
|
87
|
+
return self.clients.openWindow(urlToOpen);
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
createServiceWorker
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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 { defaultCache } from '@serwist/next/worker';\nimport { Serwist } from 'serwist';\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 * Enable push notifications\n * @default false\n */\n enablePushNotifications?: boolean;\n\n /**\n * Default notification icon\n */\n notificationIcon?: string;\n\n /**\n * Default notification badge\n */\n notificationBadge?: string;\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 enablePushNotifications = false,\n notificationIcon,\n notificationBadge,\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 // Push notification support\n if (enablePushNotifications) {\n // Handle push events\n self.addEventListener('push', (event: PushEvent) => {\n let data;\n try {\n data = event.data?.json() || {};\n console.log('SW: Push Received (JSON)', data);\n } catch (err) {\n console.log('SW: Push Received (Raw)', event.data?.text());\n data = {};\n }\n\n const {\n title = 'Notification',\n body = '',\n icon = notificationIcon,\n badge = notificationBadge,\n data: notificationData = {},\n tag,\n requireInteraction = false,\n vibrate = [200, 100, 200], // Default vibration pattern\n silent = false, // Default to playing sound\n } = data;\n\n event.waitUntil(\n self.registration.showNotification(title, {\n body,\n icon,\n badge,\n data: notificationData,\n tag,\n requireInteraction,\n vibrate,\n silent,\n })\n );\n });\n\n // Handle notification clicks\n self.addEventListener('notificationclick', (event: NotificationEvent) => {\n event.notification.close();\n\n const urlToOpen = event.notification.data?.url || '/';\n\n event.waitUntil(\n self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList: readonly WindowClient[]) => {\n // Check if there's already a window open with the URL\n for (const client of clientList) {\n if (client.url === urlToOpen && 'focus' in client) {\n return client.focus();\n }\n }\n // If not, open a new window\n if (self.clients.openWindow) {\n return self.clients.openWindow(urlToOpen);\n }\n })\n );\n });\n }\n}\n"],"mappings":";AAQA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AAyDjB,SAAS,oBAAoB,UAAgC,CAAC,GAAG;AACtE,QAAM;AAAA,IACJ,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,oBAAoB;AAAA,IACpB,0BAA0B;AAAA,IAC1B;AAAA,IACA;AAAA,EACF,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;AAG1B,MAAI,yBAAyB;AAE3B,SAAK,iBAAiB,QAAQ,CAAC,UAAqB;AAClD,UAAI;AACJ,UAAI;AACF,eAAO,MAAM,MAAM,KAAK,KAAK,CAAC;AAC9B,gBAAQ,IAAI,4BAA4B,IAAI;AAAA,MAC9C,SAAS,KAAK;AACZ,gBAAQ,IAAI,2BAA2B,MAAM,MAAM,KAAK,CAAC;AACzD,eAAO,CAAC;AAAA,MACV;AAEA,YAAM;AAAA,QACJ,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,MAAM,mBAAmB,CAAC;AAAA,QAC1B;AAAA,QACA,qBAAqB;AAAA,QACrB,UAAU,CAAC,KAAK,KAAK,GAAG;AAAA;AAAA,QACxB,SAAS;AAAA;AAAA,MACX,IAAI;AAEJ,YAAM;AAAA,QACJ,KAAK,aAAa,iBAAiB,OAAO;AAAA,UACxC;AAAA,UACA;AAAA,UACA;AAAA,UACA,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAGD,SAAK,iBAAiB,qBAAqB,CAAC,UAA6B;AACvE,YAAM,aAAa,MAAM;AAEzB,YAAM,YAAY,MAAM,aAAa,MAAM,OAAO;AAElD,YAAM;AAAA,QACJ,KAAK,QAAQ,SAAS,EAAE,MAAM,UAAU,qBAAqB,KAAK,CAAC,EAAE,KAAK,CAAC,eAAwC;AAEjH,qBAAW,UAAU,YAAY;AAC/B,gBAAI,OAAO,QAAQ,aAAa,WAAW,QAAQ;AACjD,qBAAO,OAAO,MAAM;AAAA,YACtB;AAAA,UACF;AAEA,cAAI,KAAK,QAAQ,YAAY;AAC3B,mBAAO,KAAK,QAAQ,WAAW,SAAS;AAAA,UAC1C;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ready-to-use Push Notification Route Handlers
|
|
5
|
+
*
|
|
6
|
+
* Import these in your app/api/push/ routes
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* POST /api/push/subscribe
|
|
11
|
+
* Save push subscription
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // app/api/push/subscribe/route.ts
|
|
16
|
+
* export { POST } from '@djangocfg/nextjs/pwa/server/routes';
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
declare function handleSubscribe(request: NextRequest): Promise<NextResponse<{
|
|
20
|
+
error: string;
|
|
21
|
+
}> | NextResponse<{
|
|
22
|
+
success: boolean;
|
|
23
|
+
message: string;
|
|
24
|
+
totalSubscriptions: number;
|
|
25
|
+
}>>;
|
|
26
|
+
/**
|
|
27
|
+
* GET /api/push/subscribe
|
|
28
|
+
* Get all subscriptions (for testing)
|
|
29
|
+
*/
|
|
30
|
+
declare function handleGetSubscriptions(): Promise<NextResponse<{
|
|
31
|
+
totalSubscriptions: number;
|
|
32
|
+
subscriptions: {
|
|
33
|
+
endpoint: string;
|
|
34
|
+
}[];
|
|
35
|
+
}>>;
|
|
36
|
+
/**
|
|
37
|
+
* POST /api/push/send
|
|
38
|
+
* Send push notification
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* // app/api/push/send/route.ts
|
|
43
|
+
* export { POST as handleSend as POST } from '@djangocfg/nextjs/pwa/server/routes';
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare function handleSend(request: NextRequest): Promise<NextResponse<{
|
|
47
|
+
error: string;
|
|
48
|
+
}> | NextResponse<{
|
|
49
|
+
success: boolean;
|
|
50
|
+
message: string;
|
|
51
|
+
}>>;
|
|
52
|
+
/**
|
|
53
|
+
* Combined route handlers
|
|
54
|
+
* Use like: export { POST, GET } from '@djangocfg/nextjs/pwa/server/routes'
|
|
55
|
+
*/
|
|
56
|
+
declare const POST: typeof handleSubscribe;
|
|
57
|
+
declare const GET: typeof handleGetSubscriptions;
|
|
58
|
+
|
|
59
|
+
declare const routes_GET: typeof GET;
|
|
60
|
+
declare const routes_POST: typeof POST;
|
|
61
|
+
declare const routes_handleGetSubscriptions: typeof handleGetSubscriptions;
|
|
62
|
+
declare const routes_handleSend: typeof handleSend;
|
|
63
|
+
declare const routes_handleSubscribe: typeof handleSubscribe;
|
|
64
|
+
declare namespace routes {
|
|
65
|
+
export { routes_GET as GET, routes_POST as POST, routes_handleGetSubscriptions as handleGetSubscriptions, routes_handleSend as handleSend, routes_handleSubscribe as handleSubscribe };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { GET as G, POST as P, handleGetSubscriptions as a, handleSend as b, handleSubscribe as h, routes as r };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/nextjs",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.37",
|
|
4
4
|
"description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
@@ -78,6 +78,31 @@
|
|
|
78
78
|
"types": "./dist/scripts/index.d.mts",
|
|
79
79
|
"import": "./dist/scripts/index.mjs",
|
|
80
80
|
"default": "./dist/scripts/index.mjs"
|
|
81
|
+
},
|
|
82
|
+
"./pwa": {
|
|
83
|
+
"types": "./dist/pwa/index.d.mts",
|
|
84
|
+
"import": "./dist/pwa/index.mjs",
|
|
85
|
+
"default": "./dist/pwa/index.mjs"
|
|
86
|
+
},
|
|
87
|
+
"./pwa/worker": {
|
|
88
|
+
"types": "./dist/pwa/worker/index.d.mts",
|
|
89
|
+
"import": "./dist/pwa/worker/index.mjs",
|
|
90
|
+
"default": "./dist/pwa/worker/index.mjs"
|
|
91
|
+
},
|
|
92
|
+
"./worker": {
|
|
93
|
+
"types": "./dist/pwa/worker/index.d.mts",
|
|
94
|
+
"import": "./dist/pwa/worker/index.mjs",
|
|
95
|
+
"default": "./dist/pwa/worker/index.mjs"
|
|
96
|
+
},
|
|
97
|
+
"./pwa/server": {
|
|
98
|
+
"types": "./dist/pwa/server/index.d.mts",
|
|
99
|
+
"import": "./dist/pwa/server/index.mjs",
|
|
100
|
+
"default": "./dist/pwa/server/index.mjs"
|
|
101
|
+
},
|
|
102
|
+
"./pwa/server/routes": {
|
|
103
|
+
"types": "./dist/pwa/server/routes.d.mts",
|
|
104
|
+
"import": "./dist/pwa/server/routes.mjs",
|
|
105
|
+
"default": "./dist/pwa/server/routes.mjs"
|
|
81
106
|
}
|
|
82
107
|
},
|
|
83
108
|
"files": [
|
|
@@ -87,7 +112,8 @@
|
|
|
87
112
|
"LICENSE"
|
|
88
113
|
],
|
|
89
114
|
"bin": {
|
|
90
|
-
"djangocfg-docs": "./dist/ai/cli.mjs"
|
|
115
|
+
"djangocfg-docs": "./dist/ai/cli.mjs",
|
|
116
|
+
"djangocfg-pwa": "./dist/pwa/cli.mjs"
|
|
91
117
|
},
|
|
92
118
|
"scripts": {
|
|
93
119
|
"build": "tsup",
|
|
@@ -96,26 +122,31 @@
|
|
|
96
122
|
"lint": "eslint .",
|
|
97
123
|
"check": "tsc --noEmit",
|
|
98
124
|
"check-links": "tsx src/scripts/check-links.ts",
|
|
99
|
-
"ai-docs": "tsx src/ai/cli.ts"
|
|
125
|
+
"ai-docs": "tsx src/ai/cli.ts",
|
|
126
|
+
"pwa": "tsx src/pwa/cli.ts"
|
|
100
127
|
},
|
|
101
128
|
"peerDependencies": {
|
|
102
129
|
"next": "^16.0.10"
|
|
103
130
|
},
|
|
104
131
|
"dependencies": {
|
|
105
|
-
"@
|
|
132
|
+
"@serwist/next": "^9.2.3",
|
|
133
|
+
"@serwist/sw": "^9.2.3",
|
|
106
134
|
"chalk": "^5.3.0",
|
|
107
135
|
"conf": "^15.0.2",
|
|
108
136
|
"consola": "^3.4.2",
|
|
109
|
-
"semver": "^7.7.3"
|
|
137
|
+
"semver": "^7.7.3",
|
|
138
|
+
"serwist": "^9.2.3",
|
|
139
|
+
"web-push": "^3.6.7"
|
|
110
140
|
},
|
|
111
141
|
"devDependencies": {
|
|
112
|
-
"@djangocfg/imgai": "^2.1.
|
|
113
|
-
"@djangocfg/layouts": "^2.1.
|
|
114
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
142
|
+
"@djangocfg/imgai": "^2.1.37",
|
|
143
|
+
"@djangocfg/layouts": "^2.1.37",
|
|
144
|
+
"@djangocfg/typescript-config": "^2.1.37",
|
|
115
145
|
"@types/node": "^24.7.2",
|
|
116
146
|
"@types/react": "19.2.2",
|
|
117
147
|
"@types/react-dom": "19.2.1",
|
|
118
148
|
"@types/semver": "^7.7.1",
|
|
149
|
+
"@types/web-push": "^3.6.4",
|
|
119
150
|
"@types/webpack": "^5.28.5",
|
|
120
151
|
"@vercel/og": "^0.8.5",
|
|
121
152
|
"eslint": "^9.37.0",
|
|
@@ -29,7 +29,7 @@ import { deepMerge } from './utils/deepMerge';
|
|
|
29
29
|
import { isStaticBuild, isDev, getBasePath, getApiUrl, getSiteUrl } from './utils/env';
|
|
30
30
|
import { DevStartupPlugin } from './plugins/devStartup';
|
|
31
31
|
import { addCompressionPlugins } from './plugins/compression';
|
|
32
|
-
import { withPWA, type PWAPluginOptions } from '
|
|
32
|
+
import { withPWA, type PWAPluginOptions } from '../pwa/plugin';
|
|
33
33
|
|
|
34
34
|
// ─────────────────────────────────────────────────────────────────────────
|
|
35
35
|
// Configuration Options
|
|
@@ -42,12 +42,6 @@ export interface BaseNextConfigOptions {
|
|
|
42
42
|
transpilePackages?: string[];
|
|
43
43
|
/** Additional optimize package imports (merged with defaults) */
|
|
44
44
|
optimizePackageImports?: string[];
|
|
45
|
-
/**
|
|
46
|
-
* Automatically open browser in dev mode (default: false)
|
|
47
|
-
* NOTE: Only works with webpack mode in Next.js 16+ (Turbopack doesn't support webpack plugins)
|
|
48
|
-
* For Turbopack compatibility, use a custom dev script instead of this option
|
|
49
|
-
*/
|
|
50
|
-
openBrowser?: boolean;
|
|
51
45
|
/** Check for @djangocfg/* package updates on startup (default: true) */
|
|
52
46
|
checkUpdates?: boolean;
|
|
53
47
|
/** Auto-update outdated packages without prompting (default: false) */
|
|
@@ -69,6 +63,8 @@ export interface BaseNextConfigOptions {
|
|
|
69
63
|
* @default { enabled: true (in production), disable: true (in development) }
|
|
70
64
|
*/
|
|
71
65
|
pwa?: PWAPluginOptions | false;
|
|
66
|
+
/** Turbopack configuration (Next.js 16+ default bundler) */
|
|
67
|
+
turbopack?: NextConfig['turbopack'];
|
|
72
68
|
/** Custom webpack configuration function (called after base webpack logic) */
|
|
73
69
|
webpack?: (
|
|
74
70
|
config: WebpackConfig,
|
|
@@ -179,6 +175,10 @@ export function createBaseNextConfig(
|
|
|
179
175
|
...(options.transpilePackages || []),
|
|
180
176
|
],
|
|
181
177
|
|
|
178
|
+
// Turbopack configuration (Next.js 16+ default bundler)
|
|
179
|
+
// Always set turbopack config to silence Next.js 16 warning about webpack config
|
|
180
|
+
turbopack: options.turbopack || {},
|
|
181
|
+
|
|
182
182
|
// Experimental features
|
|
183
183
|
experimental: {
|
|
184
184
|
// Optimize package imports (only in production)
|
|
@@ -197,20 +197,16 @@ export function createBaseNextConfig(
|
|
|
197
197
|
},
|
|
198
198
|
|
|
199
199
|
// Webpack configuration
|
|
200
|
-
// NOTE: Next.js 16 uses Turbopack by default in dev mode, which doesn't support webpack plugins.
|
|
201
|
-
// DevStartupPlugin only runs in webpack mode (next dev --webpack).
|
|
202
|
-
// For Turbopack compatibility, consider using a custom dev script for browser auto-open.
|
|
203
200
|
webpack: (config: WebpackConfig, webpackOptions: { isServer: boolean; dev: boolean; [key: string]: any }) => {
|
|
204
201
|
const { isServer, dev } = webpackOptions;
|
|
205
202
|
|
|
206
|
-
// Add dev startup plugin (client-side only in dev
|
|
203
|
+
// Add dev startup plugin (client-side only in dev)
|
|
207
204
|
if (dev && !isServer) {
|
|
208
205
|
if (!config.plugins) {
|
|
209
206
|
config.plugins = [];
|
|
210
207
|
}
|
|
211
208
|
config.plugins.push(
|
|
212
209
|
new DevStartupPlugin({
|
|
213
|
-
openBrowser: options.openBrowser,
|
|
214
210
|
checkUpdates: options.checkUpdates,
|
|
215
211
|
autoUpdate: options.autoUpdate,
|
|
216
212
|
forceCheckWorkspace: options.forceCheckWorkspace,
|
|
@@ -256,13 +252,13 @@ export function createBaseNextConfig(
|
|
|
256
252
|
// Cleanup: Remove custom options that are not part of NextConfig
|
|
257
253
|
delete (finalConfig as any).optimizePackageImports;
|
|
258
254
|
delete (finalConfig as any).isDefaultCfgAdmin;
|
|
259
|
-
delete (finalConfig as any).openBrowser;
|
|
260
255
|
delete (finalConfig as any).checkUpdates;
|
|
261
256
|
delete (finalConfig as any).autoUpdate;
|
|
262
257
|
delete (finalConfig as any).forceCheckWorkspace;
|
|
263
258
|
delete (finalConfig as any).checkPackages;
|
|
264
259
|
delete (finalConfig as any).autoInstall;
|
|
265
260
|
delete (finalConfig as any).allowIframeFrom;
|
|
261
|
+
// Note: turbopack is a valid NextConfig option, don't delete it
|
|
266
262
|
|
|
267
263
|
// Apply PWA wrapper if enabled
|
|
268
264
|
if (options.pwa !== false) {
|
package/src/config/index.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* import { createBaseNextConfig } from '@djangocfg/nextjs/config';
|
|
7
7
|
*
|
|
8
8
|
* export default createBaseNextConfig({
|
|
9
|
-
* openBrowser: true,
|
|
10
9
|
* checkPackages: true,
|
|
10
|
+
* checkUpdates: true,
|
|
11
11
|
* });
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
@@ -97,22 +97,4 @@ export {
|
|
|
97
97
|
isCompressionAvailable,
|
|
98
98
|
type CompressionPluginOptions,
|
|
99
99
|
} from './plugins/compression';
|
|
100
|
-
|
|
101
|
-
withPWA,
|
|
102
|
-
isPWAAvailable,
|
|
103
|
-
defaultRuntimeCaching,
|
|
104
|
-
createApiCacheRule,
|
|
105
|
-
createStaticAssetRule,
|
|
106
|
-
createCdnCacheRule,
|
|
107
|
-
type PWAPluginOptions,
|
|
108
|
-
type CacheStrategy,
|
|
109
|
-
type RuntimeCacheEntry,
|
|
110
|
-
} from './plugins/pwa';
|
|
111
|
-
export {
|
|
112
|
-
createManifestMetadata,
|
|
113
|
-
createViewport,
|
|
114
|
-
generateManifest,
|
|
115
|
-
createManifest,
|
|
116
|
-
type ManifestConfig,
|
|
117
|
-
type IconPaths,
|
|
118
|
-
} from './utils/manifest';
|
|
100
|
+
|