@djangocfg/nextjs 1.0.6 → 2.1.1
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/package.json +12 -8
- package/src/config/constants.ts +55 -0
- package/src/config/createNextConfig.ts +209 -0
- package/src/config/index.ts +86 -1
- package/src/config/packages/checker.ts +94 -0
- package/src/config/packages/definitions.ts +67 -0
- package/src/config/packages/index.ts +27 -0
- package/src/config/packages/installer.ts +457 -0
- package/src/config/packages/updater.ts +469 -0
- package/src/config/plugins/compression.ts +97 -0
- package/src/config/plugins/devStartup.ts +123 -0
- package/src/config/plugins/index.ts +6 -0
- package/src/config/utils/deepMerge.ts +33 -0
- package/src/config/utils/env.ts +30 -0
- package/src/config/utils/index.ts +7 -0
- package/src/config/utils/version.ts +121 -0
- package/src/og-image/utils/metadata.ts +1 -2
- package/src/og-image/utils/url.ts +57 -44
- package/src/config/base-next-config.ts +0 -303
- package/src/config/deepMerge.ts +0 -33
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base Next.js Configuration Factory
|
|
3
|
-
*
|
|
4
|
-
* Universal, reusable Next.js config for all DjangoCFG projects
|
|
5
|
-
* Provides standard settings that can be extended per project
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* // In your project's next.config.ts
|
|
10
|
-
* import { createBaseNextConfig } from '@djangocfg/nextjs/config';
|
|
11
|
-
* import bundleAnalyzer from '@next/bundle-analyzer';
|
|
12
|
-
*
|
|
13
|
-
* const withBundleAnalyzer = bundleAnalyzer({
|
|
14
|
-
* enabled: process.env.ANALYZE === 'true',
|
|
15
|
-
* });
|
|
16
|
-
*
|
|
17
|
-
* export default withBundleAnalyzer(createBaseNextConfig({
|
|
18
|
-
* // Your project-specific overrides
|
|
19
|
-
* transpilePackages: ['my-custom-package'],
|
|
20
|
-
* }));
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import type { NextConfig } from 'next';
|
|
25
|
-
import type { Configuration as WebpackConfig } from 'webpack';
|
|
26
|
-
import chalk from 'chalk';
|
|
27
|
-
import { deepMerge } from './deepMerge';
|
|
28
|
-
|
|
29
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
30
|
-
// Standard Environment Variables
|
|
31
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
32
|
-
|
|
33
|
-
const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';
|
|
34
|
-
const isDev = process.env.NODE_ENV === 'development';
|
|
35
|
-
|
|
36
|
-
// Global flag to track if browser was already opened (persists across hot reloads)
|
|
37
|
-
let browserOpened = false;
|
|
38
|
-
let bannerPrinted = false;
|
|
39
|
-
|
|
40
|
-
// ASCII Art Banner for Django CFG
|
|
41
|
-
const DJANGO_CFG_BANNER = `
|
|
42
|
-
888 d8b .d888
|
|
43
|
-
888 Y8P d88P"
|
|
44
|
-
888 888
|
|
45
|
-
.d88888 8888 8888b. 88888b. .d88b. .d88b. .d8888b 888888 .d88b.
|
|
46
|
-
d88" 888 "888 "88b 888 "88b d88P"88b d88""88b d88P" 888 d88P"88b
|
|
47
|
-
888 888 888 .d888888 888 888 888 888 888 888 888 888 888 888
|
|
48
|
-
Y88b 888 888 888 888 888 888 Y88b 888 Y88..88P Y88b. 888 Y88b 888
|
|
49
|
-
"Y88888 888 "Y888888 888 888 "Y88888 "Y88P" "Y8888P 888 "Y88888
|
|
50
|
-
888 888 888
|
|
51
|
-
d88P Y8b d88P Y8b d88P
|
|
52
|
-
888P" "Y88P" "Y88P"
|
|
53
|
-
`;
|
|
54
|
-
|
|
55
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
56
|
-
// Configuration Options
|
|
57
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
58
|
-
|
|
59
|
-
export interface BaseNextConfigOptions {
|
|
60
|
-
/** Static build path (used when NEXT_PUBLIC_STATIC_BUILD=true) */
|
|
61
|
-
isDefaultCfgAdmin?: boolean;
|
|
62
|
-
/** Additional transpile packages (merged with defaults) */
|
|
63
|
-
transpilePackages?: string[];
|
|
64
|
-
/** Additional optimize package imports (merged with defaults) */
|
|
65
|
-
optimizePackageImports?: string[];
|
|
66
|
-
/** Automatically open browser in dev mode (default: false) */
|
|
67
|
-
openBrowser?: boolean;
|
|
68
|
-
/** Custom webpack configuration function (called after base webpack logic) */
|
|
69
|
-
webpack?: (
|
|
70
|
-
config: WebpackConfig,
|
|
71
|
-
options: { isServer: boolean; dev: boolean;[key: string]: any }
|
|
72
|
-
) => WebpackConfig | void;
|
|
73
|
-
/** Custom experimental options (merged with defaults) */
|
|
74
|
-
experimental?: NextConfig['experimental'];
|
|
75
|
-
/** Custom env variables (merged with defaults) */
|
|
76
|
-
env?: Record<string, string | undefined>;
|
|
77
|
-
/** Override any Next.js config option */
|
|
78
|
-
[key: string]: any;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
82
|
-
// Base Configuration Factory
|
|
83
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Create base Next.js configuration with standard DjangoCFG settings
|
|
87
|
-
*
|
|
88
|
-
* @param options - Custom configuration options to merge with base config
|
|
89
|
-
* @returns Next.js configuration function
|
|
90
|
-
*/
|
|
91
|
-
export function createBaseNextConfig(
|
|
92
|
-
options: BaseNextConfigOptions = {}
|
|
93
|
-
): NextConfig {
|
|
94
|
-
|
|
95
|
-
// If static build not provided, use default for nextjs-admin development
|
|
96
|
-
const staticBuildPath = options.isDefaultCfgAdmin ? '/cfg/admin' : '/cfg/nextjs-admin';
|
|
97
|
-
const basePath = isStaticBuild ? staticBuildPath : '';
|
|
98
|
-
const apiUrl = isStaticBuild ? '' : process.env.NEXT_PUBLIC_API_URL || '';
|
|
99
|
-
const siteUrl = isStaticBuild ? '' : process.env.NEXT_PUBLIC_SITE_URL || '';
|
|
100
|
-
|
|
101
|
-
// Base configuration
|
|
102
|
-
const baseConfig: NextConfig = {
|
|
103
|
-
reactStrictMode: true,
|
|
104
|
-
trailingSlash: true,
|
|
105
|
-
|
|
106
|
-
// Static export configuration
|
|
107
|
-
...(isStaticBuild && {
|
|
108
|
-
output: 'export' as const,
|
|
109
|
-
distDir: 'out',
|
|
110
|
-
basePath: basePath,
|
|
111
|
-
// assetPrefix: basePath,
|
|
112
|
-
// Fix for Next.js 15.5.4: prevent 500.html generation issue
|
|
113
|
-
//
|
|
114
|
-
// PROBLEM: In App Router, error.tsx is a client component ('use client')
|
|
115
|
-
// and cannot be statically exported as 500.html. Next.js tries to create
|
|
116
|
-
// and move 500.html during static export, causing ENOENT error.
|
|
117
|
-
//
|
|
118
|
-
// SOLUTION: Use generateBuildId to work around the issue.
|
|
119
|
-
// The error.tsx component will still work in development/runtime mode.
|
|
120
|
-
generateBuildId: async () => {
|
|
121
|
-
return 'static-build';
|
|
122
|
-
},
|
|
123
|
-
}),
|
|
124
|
-
|
|
125
|
-
// Standalone output for Docker (only in production, not dev)
|
|
126
|
-
...(!isStaticBuild && !isDev && {
|
|
127
|
-
output: 'standalone' as const,
|
|
128
|
-
}),
|
|
129
|
-
|
|
130
|
-
// Environment variables
|
|
131
|
-
env: {
|
|
132
|
-
NEXT_PUBLIC_BASE_PATH: basePath,
|
|
133
|
-
NEXT_PUBLIC_API_URL: apiUrl,
|
|
134
|
-
NEXT_PUBLIC_SITE_URL: siteUrl,
|
|
135
|
-
...options.env,
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
// Images configuration
|
|
139
|
-
images: {
|
|
140
|
-
unoptimized: true,
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
// Transpile packages (merge with user-provided)
|
|
144
|
-
transpilePackages: [
|
|
145
|
-
'@djangocfg/ui',
|
|
146
|
-
'@djangocfg/layouts',
|
|
147
|
-
'@djangocfg/nextjs',
|
|
148
|
-
'@djangocfg/api',
|
|
149
|
-
'@djangocfg/centrifugo',
|
|
150
|
-
...(options.transpilePackages || []),
|
|
151
|
-
],
|
|
152
|
-
|
|
153
|
-
// Experimental features
|
|
154
|
-
// Base optimizations first, then user options (user can override base settings)
|
|
155
|
-
experimental: {
|
|
156
|
-
// Optimize package imports (only in production)
|
|
157
|
-
...(!isDev && {
|
|
158
|
-
optimizePackageImports: [
|
|
159
|
-
'@djangocfg/ui',
|
|
160
|
-
'@djangocfg/layouts',
|
|
161
|
-
'lucide-react',
|
|
162
|
-
'recharts',
|
|
163
|
-
...(options.optimizePackageImports || []),
|
|
164
|
-
],
|
|
165
|
-
}),
|
|
166
|
-
// Dev mode optimizations
|
|
167
|
-
...(isDev && {
|
|
168
|
-
optimizeCss: false,
|
|
169
|
-
}),
|
|
170
|
-
// User experimental options applied last (can override base settings)
|
|
171
|
-
...options.experimental,
|
|
172
|
-
},
|
|
173
|
-
|
|
174
|
-
// Webpack configuration
|
|
175
|
-
webpack: (config: WebpackConfig, webpackOptions: { isServer: boolean; dev: boolean;[key: string]: any }) => {
|
|
176
|
-
const { isServer, dev } = webpackOptions;
|
|
177
|
-
|
|
178
|
-
// Print banner and auto-open browser in dev mode (client-side only)
|
|
179
|
-
if (dev && !isServer) {
|
|
180
|
-
// Create a simple plugin to print banner and open browser after first compilation
|
|
181
|
-
const DevStartupPlugin = class {
|
|
182
|
-
apply(compiler: any) {
|
|
183
|
-
compiler.hooks.done.tap('DevStartupPlugin', () => {
|
|
184
|
-
// Print banner only once
|
|
185
|
-
if (!bannerPrinted) {
|
|
186
|
-
bannerPrinted = true;
|
|
187
|
-
console.log('\n' + chalk.yellowBright.bold(DJANGO_CFG_BANNER) + '\n');
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Auto-open browser if enabled
|
|
191
|
-
if (options.openBrowser && !browserOpened) {
|
|
192
|
-
browserOpened = true;
|
|
193
|
-
// Delay to ensure server is ready
|
|
194
|
-
setTimeout(() => {
|
|
195
|
-
const { exec } = require('child_process');
|
|
196
|
-
const port = process.env.PORT || '3000';
|
|
197
|
-
const url = `http://localhost:${port}`;
|
|
198
|
-
|
|
199
|
-
const command = process.platform === 'darwin'
|
|
200
|
-
? 'open'
|
|
201
|
-
: process.platform === 'win32'
|
|
202
|
-
? 'start'
|
|
203
|
-
: 'xdg-open';
|
|
204
|
-
|
|
205
|
-
exec(`${command} ${url}`, (error: Error | null) => {
|
|
206
|
-
if (error) {
|
|
207
|
-
console.warn(`Failed to open browser: ${error.message}`);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
}, 2000); // Wait 2 seconds for server to be ready
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
if (!config.plugins) {
|
|
217
|
-
config.plugins = [];
|
|
218
|
-
}
|
|
219
|
-
config.plugins.push(new DevStartupPlugin());
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Dev mode optimizations
|
|
223
|
-
if (dev) {
|
|
224
|
-
config.optimization = {
|
|
225
|
-
...config.optimization,
|
|
226
|
-
removeAvailableModules: false,
|
|
227
|
-
removeEmptyChunks: false,
|
|
228
|
-
splitChunks: false, // Disable code splitting in dev for faster compilation
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Filesystem cache (dev and production)
|
|
233
|
-
config.cache = {
|
|
234
|
-
type: 'filesystem',
|
|
235
|
-
buildDependencies: {},
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
// Note: Dynamic API routes should use `export const dynamic = 'force-dynamic'`
|
|
239
|
-
// in the route file itself to exclude them from static export.
|
|
240
|
-
// This is simpler than using webpack plugins.
|
|
241
|
-
|
|
242
|
-
// Compression plugins (only for static build in production)
|
|
243
|
-
// Note: compression-webpack-plugin should be installed in the consuming project
|
|
244
|
-
if (!isServer && isStaticBuild && !dev) {
|
|
245
|
-
try {
|
|
246
|
-
// Dynamic import to avoid bundling compression-webpack-plugin
|
|
247
|
-
const CompressionPlugin = require('compression-webpack-plugin');
|
|
248
|
-
|
|
249
|
-
// Gzip compression
|
|
250
|
-
config.plugins.push(
|
|
251
|
-
new CompressionPlugin({
|
|
252
|
-
filename: '[path][base].gz',
|
|
253
|
-
algorithm: 'gzip',
|
|
254
|
-
test: /\.(js|css|html|svg|json)$/,
|
|
255
|
-
threshold: 8192,
|
|
256
|
-
minRatio: 0.8,
|
|
257
|
-
})
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
// Brotli compression (balanced level for speed/size)
|
|
261
|
-
config.plugins.push(
|
|
262
|
-
new CompressionPlugin({
|
|
263
|
-
filename: '[path][base].br',
|
|
264
|
-
algorithm: 'brotliCompress',
|
|
265
|
-
test: /\.(js|css|html|svg|json)$/,
|
|
266
|
-
threshold: 8192,
|
|
267
|
-
minRatio: 0.8,
|
|
268
|
-
compressionOptions: {
|
|
269
|
-
level: 8, // Balanced: good compression without excessive build time
|
|
270
|
-
},
|
|
271
|
-
})
|
|
272
|
-
);
|
|
273
|
-
} catch (error) {
|
|
274
|
-
// compression-webpack-plugin not installed, skip compression
|
|
275
|
-
console.warn('compression-webpack-plugin not found, skipping compression');
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Call user's webpack function if provided
|
|
280
|
-
if (options.webpack) {
|
|
281
|
-
return options.webpack(config, webpackOptions);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return config;
|
|
285
|
-
},
|
|
286
|
-
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
// Deep merge user options with base config
|
|
290
|
-
const finalConfig = deepMerge(baseConfig, options);
|
|
291
|
-
|
|
292
|
-
// Cleanup: Remove our custom options that are not part of NextConfig
|
|
293
|
-
// These are internal to BaseNextConfigOptions and should not be in the final config
|
|
294
|
-
delete (finalConfig as any).optimizePackageImports;
|
|
295
|
-
delete (finalConfig as any).isDefaultCfgAdmin;
|
|
296
|
-
delete (finalConfig as any).openBrowser;
|
|
297
|
-
// Note: We don't delete transpilePackages, experimental, env, webpack
|
|
298
|
-
// as they are valid NextConfig keys and may have been overridden by user
|
|
299
|
-
|
|
300
|
-
// Return clean NextConfig object (user should wrap with withBundleAnalyzer in their next.config.ts)
|
|
301
|
-
return finalConfig;
|
|
302
|
-
}
|
|
303
|
-
|
package/src/config/deepMerge.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
3
|
-
// Deep Merge Helper (simple implementation)
|
|
4
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
5
|
-
|
|
6
|
-
export function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
|
|
7
|
-
const output = { ...target };
|
|
8
|
-
|
|
9
|
-
for (const key in source) {
|
|
10
|
-
if (source[key] === undefined) continue;
|
|
11
|
-
|
|
12
|
-
// Arrays: replace (don't merge arrays)
|
|
13
|
-
if (Array.isArray(source[key])) {
|
|
14
|
-
output[key] = source[key] as any;
|
|
15
|
-
}
|
|
16
|
-
// Objects: deep merge
|
|
17
|
-
else if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
18
|
-
const targetValue = output[key];
|
|
19
|
-
if (targetValue && typeof targetValue === 'object' && !Array.isArray(targetValue)) {
|
|
20
|
-
output[key] = deepMerge(targetValue, source[key] as any);
|
|
21
|
-
} else {
|
|
22
|
-
output[key] = source[key] as any;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
// Primitives: replace
|
|
26
|
-
else {
|
|
27
|
-
output[key] = source[key] as any;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return output;
|
|
32
|
-
}
|
|
33
|
-
|