@unsetsoft/ryunix-presets 1.0.24 → 1.0.25
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 +1 -1
- package/webpack/bin/dev.server.mjs +2 -1
- package/webpack/bin/index.mjs +36 -19
- package/webpack/bin/prerender.mjs +3 -3
- package/webpack/bin/prod.server.mjs +401 -393
- package/webpack/utils/config.cjs +0 -2
- package/webpack/utils/ssg.mjs +9 -19
- package/webpack/utils/ssgPlugin.mjs +11 -40
- package/webpack/webpack.config.mjs +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unsetsoft/ryunix-presets",
|
|
3
3
|
"description": "Package with presets for different development environments.",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.25",
|
|
5
5
|
"author": "Neyunse",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": "https://github.com/UnSetSoft/Ryunixjs",
|
|
@@ -49,7 +49,8 @@ const StartServer = async (cliSettings) => {
|
|
|
49
49
|
`${defaultSettings.webpack.output.buildDirectory}/cache`,
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
-
const mode =
|
|
52
|
+
const mode =
|
|
53
|
+
cliSettings.production || defaultSettings.webpack.production ? true : false
|
|
53
54
|
|
|
54
55
|
if (!mode) {
|
|
55
56
|
cleanCacheDir(cacheDir)
|
package/webpack/bin/index.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import fs from 'fs'
|
|
|
18
18
|
import { fileURLToPath } from 'url'
|
|
19
19
|
import { dirname, join } from 'path'
|
|
20
20
|
import server from './prod.server.mjs'
|
|
21
|
-
import config from '../utils/config.cjs'
|
|
21
|
+
import config from '../utils/config.cjs'
|
|
22
22
|
const __filename = fileURLToPath(import.meta.url)
|
|
23
23
|
|
|
24
24
|
const __dirname = dirname(__filename)
|
|
@@ -60,7 +60,9 @@ const dev = {
|
|
|
60
60
|
describe: 'Run server for developer mode.',
|
|
61
61
|
handler: async (arg) => {
|
|
62
62
|
if (defaultSettings.webpack.production) {
|
|
63
|
-
logger.error(
|
|
63
|
+
logger.error(
|
|
64
|
+
'You need use development mode! change webpack.production to false in ryunix.config.js.',
|
|
65
|
+
)
|
|
64
66
|
return
|
|
65
67
|
}
|
|
66
68
|
const open = Boolean(arg.browser) || false
|
|
@@ -77,19 +79,25 @@ const prod = {
|
|
|
77
79
|
describe: 'Run server for production mode. Requiere .ryunix/static',
|
|
78
80
|
handler: async (arg) => {
|
|
79
81
|
if (!defaultSettings.webpack.production) {
|
|
80
|
-
logger.error(
|
|
82
|
+
logger.error('You need use production mode!')
|
|
81
83
|
return
|
|
82
84
|
}
|
|
83
85
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
+
if (
|
|
87
|
+
!fs.existsSync(
|
|
88
|
+
join(process.cwd(), config.webpack.output.buildDirectory, 'static'),
|
|
89
|
+
)
|
|
90
|
+
) {
|
|
91
|
+
logger.error('You need build first!')
|
|
86
92
|
return
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
server.listen(config.webpack.devServer.port, () => {
|
|
90
|
-
console.log(
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
console.log(
|
|
97
|
+
`Server running at http://localhost:${config.webpack.devServer.port}/`,
|
|
98
|
+
)
|
|
99
|
+
})
|
|
100
|
+
},
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
const build = {
|
|
@@ -130,8 +138,6 @@ const build = {
|
|
|
130
138
|
minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`
|
|
131
139
|
|
|
132
140
|
if (defaultSettings.webpack.production) {
|
|
133
|
-
|
|
134
|
-
|
|
135
141
|
await Prerender(defaultSettings.webpack.output.buildDirectory)
|
|
136
142
|
}
|
|
137
143
|
|
|
@@ -153,15 +159,26 @@ const extractHTML = {
|
|
|
153
159
|
handler: async (arg) => {
|
|
154
160
|
const runPath = process.cwd()
|
|
155
161
|
|
|
156
|
-
fs.copyFile(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
fs.copyFile(
|
|
163
|
+
join(__dirname, '..', 'template/index.html'),
|
|
164
|
+
join(runPath, 'public/index.html'),
|
|
165
|
+
(err) => {
|
|
166
|
+
if (err) {
|
|
167
|
+
console.error('Error extracting HTML: ', err.message)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
console.log(
|
|
171
|
+
'File extracted successfully. Now you can enable the template with static.customTemplate inside ryunix.config.js',
|
|
172
|
+
)
|
|
173
|
+
},
|
|
174
|
+
)
|
|
163
175
|
},
|
|
164
176
|
}
|
|
165
177
|
|
|
166
|
-
|
|
167
|
-
|
|
178
|
+
yargs(hideBin(process.argv))
|
|
179
|
+
.command(dev)
|
|
180
|
+
.command(build)
|
|
181
|
+
.command(prod)
|
|
182
|
+
.command(lint)
|
|
183
|
+
.command(extractHTML)
|
|
184
|
+
.parse()
|
|
@@ -29,10 +29,10 @@ const Prerender = async (directory) => {
|
|
|
29
29
|
|
|
30
30
|
const metaExist = routes.some((route) => route.meta)
|
|
31
31
|
if (metaExist && defaultSettings.static.seo.meta.length > 0) {
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
console.error(
|
|
33
|
+
'[Ryunix Error] You are mixing static and dynamic meta tags; you can only use one of the two. Remove static.seo.meta from ryunix.config.js.',
|
|
34
|
+
)
|
|
34
35
|
process.exit(1)
|
|
35
|
-
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (routes.length === 0) {
|
|
@@ -1,393 +1,401 @@
|
|
|
1
|
-
import http from 'http'
|
|
2
|
-
import { promises as fs } from 'fs'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { createHash } from 'crypto'
|
|
5
|
-
import zlib from 'zlib'
|
|
6
|
-
import { promisify } from 'util'
|
|
7
|
-
import { createReadStream } from 'fs'
|
|
8
|
-
import { pipeline } from 'stream/promises'
|
|
9
|
-
import config from '../utils/config.cjs'
|
|
10
|
-
|
|
11
|
-
const gzip = promisify(zlib.gzip)
|
|
12
|
-
const brotliCompress = promisify(zlib.brotliCompress)
|
|
13
|
-
|
|
14
|
-
// MIME types dictionary
|
|
15
|
-
const MIME_TYPES = {
|
|
16
|
-
'.js': 'application/javascript',
|
|
17
|
-
'.mjs': 'application/javascript',
|
|
18
|
-
'.css': 'text/css',
|
|
19
|
-
'.html': 'text/html',
|
|
20
|
-
'.json': 'application/json',
|
|
21
|
-
'.png': 'image/png',
|
|
22
|
-
'.jpg': 'image/jpeg',
|
|
23
|
-
'.jpeg': 'image/jpeg',
|
|
24
|
-
'.gif': 'image/gif',
|
|
25
|
-
'.svg': 'image/svg+xml',
|
|
26
|
-
'.woff': 'font/woff',
|
|
27
|
-
'.woff2': 'font/woff2',
|
|
28
|
-
'.ttf': 'font/ttf',
|
|
29
|
-
'.eot': 'application/vnd.ms-fontobject',
|
|
30
|
-
'.otf': 'font/otf',
|
|
31
|
-
'.wasm': 'application/wasm',
|
|
32
|
-
'.ico': 'image/x-icon',
|
|
33
|
-
'.mp3': 'audio/mpeg',
|
|
34
|
-
'.mp4': 'video/mp4',
|
|
35
|
-
'.pdf': 'application/pdf',
|
|
36
|
-
'.zip': 'application/zip',
|
|
37
|
-
'.gz': 'application/gzip',
|
|
38
|
-
'.tar': 'application/x-tar',
|
|
39
|
-
'.7z': 'application/x-7z-compressed',
|
|
40
|
-
'.rar': 'application/x-rar-compressed',
|
|
41
|
-
'.avi': 'video/x-msvideo',
|
|
42
|
-
'.mov': 'video/quicktime',
|
|
43
|
-
'.wmv': 'video/x-ms-wmv',
|
|
44
|
-
'.flv': 'video/x-flv',
|
|
45
|
-
'.webm': 'video/webm',
|
|
46
|
-
'.ogg': 'audio/ogg',
|
|
47
|
-
'.ogv': 'video/ogg',
|
|
48
|
-
'.m4v': 'video/mp4',
|
|
49
|
-
'.3gp': 'video/3gpp',
|
|
50
|
-
'.3g2': 'video/3gpp2',
|
|
51
|
-
'.mkv': 'video/x-matroska',
|
|
52
|
-
'.ts': 'video/mp2t',
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// File cache for production server
|
|
56
|
-
const fileCache = new Map()
|
|
57
|
-
const MAX_CACHE_SIZE = 50 * 1024 * 1024 // 50MB
|
|
58
|
-
let currentCacheSize = 0
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get MIME type from file extension
|
|
62
|
-
*/
|
|
63
|
-
const getMimeType = (filePath) => {
|
|
64
|
-
const ext = path.extname(filePath).toLowerCase()
|
|
65
|
-
return MIME_TYPES[ext] || 'application/octet-stream'
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Validate path to prevent directory traversal attacks
|
|
70
|
-
*/
|
|
71
|
-
const validatePath = (requestPath, rootDir) => {
|
|
72
|
-
try {
|
|
73
|
-
const normalizedPath = path.normalize(requestPath)
|
|
74
|
-
const resolvedPath = path.resolve(rootDir, normalizedPath.slice(1))
|
|
75
|
-
|
|
76
|
-
if (!resolvedPath.startsWith(rootDir)) {
|
|
77
|
-
return null
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return resolvedPath
|
|
81
|
-
} catch {
|
|
82
|
-
return null
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Generate ETag from file content
|
|
88
|
-
*/
|
|
89
|
-
const generateETag = (content) => {
|
|
90
|
-
return createHash('md5').update(content).digest('hex')
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Check compression support (Brotli preferred over Gzip)
|
|
95
|
-
*/
|
|
96
|
-
const getAcceptedEncoding = (headers) => {
|
|
97
|
-
const encoding = headers['accept-encoding'] || ''
|
|
98
|
-
if (encoding.includes('br')) return 'br'
|
|
99
|
-
if (encoding.includes('gzip')) return 'gzip'
|
|
100
|
-
return null
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Check if MIME type is compressible
|
|
105
|
-
*/
|
|
106
|
-
const isCompressible = (mimeType) => {
|
|
107
|
-
return
|
|
108
|
-
mimeType.
|
|
109
|
-
mimeType.includes('
|
|
110
|
-
mimeType.includes('
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
await pipeline(stream, res)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
]
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
'
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
candidates.push(path.join(staticDir,
|
|
293
|
-
|
|
294
|
-
//
|
|
295
|
-
candidates.push(path.join(staticDir, 'index.html'))
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
res
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
1
|
+
import http from 'http'
|
|
2
|
+
import { promises as fs } from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { createHash } from 'crypto'
|
|
5
|
+
import zlib from 'zlib'
|
|
6
|
+
import { promisify } from 'util'
|
|
7
|
+
import { createReadStream } from 'fs'
|
|
8
|
+
import { pipeline } from 'stream/promises'
|
|
9
|
+
import config from '../utils/config.cjs'
|
|
10
|
+
|
|
11
|
+
const gzip = promisify(zlib.gzip)
|
|
12
|
+
const brotliCompress = promisify(zlib.brotliCompress)
|
|
13
|
+
|
|
14
|
+
// MIME types dictionary
|
|
15
|
+
const MIME_TYPES = {
|
|
16
|
+
'.js': 'application/javascript',
|
|
17
|
+
'.mjs': 'application/javascript',
|
|
18
|
+
'.css': 'text/css',
|
|
19
|
+
'.html': 'text/html',
|
|
20
|
+
'.json': 'application/json',
|
|
21
|
+
'.png': 'image/png',
|
|
22
|
+
'.jpg': 'image/jpeg',
|
|
23
|
+
'.jpeg': 'image/jpeg',
|
|
24
|
+
'.gif': 'image/gif',
|
|
25
|
+
'.svg': 'image/svg+xml',
|
|
26
|
+
'.woff': 'font/woff',
|
|
27
|
+
'.woff2': 'font/woff2',
|
|
28
|
+
'.ttf': 'font/ttf',
|
|
29
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
30
|
+
'.otf': 'font/otf',
|
|
31
|
+
'.wasm': 'application/wasm',
|
|
32
|
+
'.ico': 'image/x-icon',
|
|
33
|
+
'.mp3': 'audio/mpeg',
|
|
34
|
+
'.mp4': 'video/mp4',
|
|
35
|
+
'.pdf': 'application/pdf',
|
|
36
|
+
'.zip': 'application/zip',
|
|
37
|
+
'.gz': 'application/gzip',
|
|
38
|
+
'.tar': 'application/x-tar',
|
|
39
|
+
'.7z': 'application/x-7z-compressed',
|
|
40
|
+
'.rar': 'application/x-rar-compressed',
|
|
41
|
+
'.avi': 'video/x-msvideo',
|
|
42
|
+
'.mov': 'video/quicktime',
|
|
43
|
+
'.wmv': 'video/x-ms-wmv',
|
|
44
|
+
'.flv': 'video/x-flv',
|
|
45
|
+
'.webm': 'video/webm',
|
|
46
|
+
'.ogg': 'audio/ogg',
|
|
47
|
+
'.ogv': 'video/ogg',
|
|
48
|
+
'.m4v': 'video/mp4',
|
|
49
|
+
'.3gp': 'video/3gpp',
|
|
50
|
+
'.3g2': 'video/3gpp2',
|
|
51
|
+
'.mkv': 'video/x-matroska',
|
|
52
|
+
'.ts': 'video/mp2t',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// File cache for production server
|
|
56
|
+
const fileCache = new Map()
|
|
57
|
+
const MAX_CACHE_SIZE = 50 * 1024 * 1024 // 50MB
|
|
58
|
+
let currentCacheSize = 0
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get MIME type from file extension
|
|
62
|
+
*/
|
|
63
|
+
const getMimeType = (filePath) => {
|
|
64
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
65
|
+
return MIME_TYPES[ext] || 'application/octet-stream'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validate path to prevent directory traversal attacks
|
|
70
|
+
*/
|
|
71
|
+
const validatePath = (requestPath, rootDir) => {
|
|
72
|
+
try {
|
|
73
|
+
const normalizedPath = path.normalize(requestPath)
|
|
74
|
+
const resolvedPath = path.resolve(rootDir, normalizedPath.slice(1))
|
|
75
|
+
|
|
76
|
+
if (!resolvedPath.startsWith(rootDir)) {
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return resolvedPath
|
|
81
|
+
} catch {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Generate ETag from file content
|
|
88
|
+
*/
|
|
89
|
+
const generateETag = (content) => {
|
|
90
|
+
return createHash('md5').update(content).digest('hex')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check compression support (Brotli preferred over Gzip)
|
|
95
|
+
*/
|
|
96
|
+
const getAcceptedEncoding = (headers) => {
|
|
97
|
+
const encoding = headers['accept-encoding'] || ''
|
|
98
|
+
if (encoding.includes('br')) return 'br'
|
|
99
|
+
if (encoding.includes('gzip')) return 'gzip'
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check if MIME type is compressible
|
|
105
|
+
*/
|
|
106
|
+
const isCompressible = (mimeType) => {
|
|
107
|
+
return (
|
|
108
|
+
mimeType.startsWith('text/') ||
|
|
109
|
+
mimeType.includes('javascript') ||
|
|
110
|
+
mimeType.includes('json') ||
|
|
111
|
+
mimeType.includes('css')
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Parse Range header
|
|
117
|
+
*/
|
|
118
|
+
const parseRange = (rangeHeader, fileSize) => {
|
|
119
|
+
if (!rangeHeader) return null
|
|
120
|
+
|
|
121
|
+
const parts = rangeHeader.replace(/bytes=/, '').split('-')
|
|
122
|
+
const start = parseInt(parts[0], 10)
|
|
123
|
+
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1
|
|
124
|
+
|
|
125
|
+
if (isNaN(start) || isNaN(end) || start > end || end >= fileSize) {
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { start, end, length: end - start + 1 }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if file should support range requests (media files)
|
|
134
|
+
*/
|
|
135
|
+
const supportsRangeRequests = (mimeType) => {
|
|
136
|
+
return (
|
|
137
|
+
mimeType.startsWith('video/') ||
|
|
138
|
+
mimeType.startsWith('audio/') ||
|
|
139
|
+
mimeType === 'application/pdf'
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Serve file with range support (for video/audio)
|
|
145
|
+
*/
|
|
146
|
+
const serveWithRange = async (filePath, req, res, stats) => {
|
|
147
|
+
const mimeType = getMimeType(filePath)
|
|
148
|
+
const range = parseRange(req.headers.range, stats.size)
|
|
149
|
+
|
|
150
|
+
const headers = {
|
|
151
|
+
'Content-Type': mimeType,
|
|
152
|
+
'Accept-Ranges': 'bytes',
|
|
153
|
+
'Cache-Control': 'public, max-age=31536000',
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (range) {
|
|
157
|
+
// Partial content
|
|
158
|
+
headers['Content-Range'] = `bytes ${range.start}-${range.end}/${stats.size}`
|
|
159
|
+
headers['Content-Length'] = range.length
|
|
160
|
+
|
|
161
|
+
res.writeHead(206, headers)
|
|
162
|
+
|
|
163
|
+
const stream = createReadStream(filePath, {
|
|
164
|
+
start: range.start,
|
|
165
|
+
end: range.end,
|
|
166
|
+
})
|
|
167
|
+
await pipeline(stream, res)
|
|
168
|
+
} else {
|
|
169
|
+
// Full content
|
|
170
|
+
headers['Content-Length'] = stats.size
|
|
171
|
+
res.writeHead(200, headers)
|
|
172
|
+
|
|
173
|
+
const stream = createReadStream(filePath)
|
|
174
|
+
await pipeline(stream, res)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return true
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Serve static file with caching and compression
|
|
182
|
+
*/
|
|
183
|
+
const serveStaticFile = async (filePath, req, res) => {
|
|
184
|
+
try {
|
|
185
|
+
let stats = await fs.stat(filePath)
|
|
186
|
+
|
|
187
|
+
// 👉 If is a directory
|
|
188
|
+
if (stats.isDirectory()) {
|
|
189
|
+
const indexPath = path.join(filePath, 'index.html')
|
|
190
|
+
await fs.access(indexPath)
|
|
191
|
+
stats = await fs.stat(indexPath)
|
|
192
|
+
filePath = indexPath
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const mimeType = getMimeType(filePath)
|
|
196
|
+
|
|
197
|
+
// Use range requests for media files or large files
|
|
198
|
+
if (supportsRangeRequests(mimeType) || stats.size > 5 * 1024 * 1024) {
|
|
199
|
+
return await serveWithRange(filePath, req, res, stats)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let cached = fileCache.get(filePath)
|
|
203
|
+
|
|
204
|
+
if (!cached) {
|
|
205
|
+
// Read and cache file
|
|
206
|
+
const content = await fs.readFile(filePath)
|
|
207
|
+
const etag = generateETag(content)
|
|
208
|
+
|
|
209
|
+
// Compress if text-based content
|
|
210
|
+
let brotli = null
|
|
211
|
+
let gzipped = null
|
|
212
|
+
|
|
213
|
+
if (isCompressible(mimeType)) {
|
|
214
|
+
try {
|
|
215
|
+
;[brotli, gzipped] = await Promise.all([
|
|
216
|
+
brotliCompress(content, {
|
|
217
|
+
params: {
|
|
218
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: 6,
|
|
219
|
+
},
|
|
220
|
+
}),
|
|
221
|
+
gzip(content),
|
|
222
|
+
])
|
|
223
|
+
} catch {
|
|
224
|
+
// Compression failed, serve uncompressed
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
cached = {
|
|
229
|
+
content,
|
|
230
|
+
brotli,
|
|
231
|
+
gzipped,
|
|
232
|
+
etag,
|
|
233
|
+
mimeType,
|
|
234
|
+
size: stats.size,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Update cache
|
|
238
|
+
if (currentCacheSize + stats.size < MAX_CACHE_SIZE) {
|
|
239
|
+
fileCache.set(filePath, cached)
|
|
240
|
+
currentCacheSize += stats.size
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Check ETag for 304 Not Modified
|
|
245
|
+
if (req.headers['if-none-match'] === cached.etag) {
|
|
246
|
+
res.writeHead(304)
|
|
247
|
+
res.end()
|
|
248
|
+
return true
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Select best encoding
|
|
252
|
+
const encoding = getAcceptedEncoding(req.headers)
|
|
253
|
+
let responseContent = cached.content
|
|
254
|
+
let contentEncoding = null
|
|
255
|
+
|
|
256
|
+
if (encoding === 'br' && cached.brotli) {
|
|
257
|
+
responseContent = cached.brotli
|
|
258
|
+
contentEncoding = 'br'
|
|
259
|
+
} else if (encoding === 'gzip' && cached.gzipped) {
|
|
260
|
+
responseContent = cached.gzipped
|
|
261
|
+
contentEncoding = 'gzip'
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const headers = {
|
|
265
|
+
'Content-Type': cached.mimeType,
|
|
266
|
+
'Content-Length': responseContent.length,
|
|
267
|
+
ETag: cached.etag,
|
|
268
|
+
'Cache-Control': 'public, max-age=31536000',
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (contentEncoding) {
|
|
272
|
+
headers['Content-Encoding'] = contentEncoding
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
res.writeHead(200, headers)
|
|
276
|
+
res.end(responseContent)
|
|
277
|
+
return true
|
|
278
|
+
} catch (error) {
|
|
279
|
+
return false
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Serve HTML page with SPA fallback support
|
|
285
|
+
*/
|
|
286
|
+
const serveHTMLPage = async (pathname, staticDir, req, res) => {
|
|
287
|
+
try {
|
|
288
|
+
const candidates = []
|
|
289
|
+
|
|
290
|
+
// / → /index.html
|
|
291
|
+
if (pathname === '/') {
|
|
292
|
+
candidates.push(path.join(staticDir, 'index.html'))
|
|
293
|
+
} else {
|
|
294
|
+
// /test → /test/index.html
|
|
295
|
+
candidates.push(path.join(staticDir, pathname, 'index.html'))
|
|
296
|
+
|
|
297
|
+
// /test → /test.html
|
|
298
|
+
candidates.push(path.join(staticDir, `${pathname}.html`))
|
|
299
|
+
|
|
300
|
+
// SPA fallback
|
|
301
|
+
candidates.push(path.join(staticDir, 'index.html'))
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let pageFile = null
|
|
305
|
+
|
|
306
|
+
for (const file of candidates) {
|
|
307
|
+
try {
|
|
308
|
+
await fs.access(file)
|
|
309
|
+
pageFile = file
|
|
310
|
+
break
|
|
311
|
+
} catch {}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!pageFile) {
|
|
315
|
+
res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
316
|
+
res.end('404')
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const content = await fs.readFile(pageFile, 'utf-8')
|
|
321
|
+
const etag = generateETag(Buffer.from(content))
|
|
322
|
+
|
|
323
|
+
if (req.headers['if-none-match'] === etag) {
|
|
324
|
+
res.writeHead(304)
|
|
325
|
+
res.end()
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Compress HTML
|
|
330
|
+
let responseContent = content
|
|
331
|
+
const headers = {
|
|
332
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
333
|
+
ETag: etag,
|
|
334
|
+
'Cache-Control': 'no-cache',
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const encoding = getAcceptedEncoding(req.headers)
|
|
338
|
+
|
|
339
|
+
if (encoding === 'br') {
|
|
340
|
+
try {
|
|
341
|
+
responseContent = await brotliCompress(Buffer.from(content))
|
|
342
|
+
headers['Content-Encoding'] = 'br'
|
|
343
|
+
} catch {
|
|
344
|
+
// Fallback to uncompressed
|
|
345
|
+
}
|
|
346
|
+
} else if (encoding === 'gzip') {
|
|
347
|
+
try {
|
|
348
|
+
responseContent = await gzip(Buffer.from(content))
|
|
349
|
+
headers['Content-Encoding'] = 'gzip'
|
|
350
|
+
} catch {
|
|
351
|
+
// Fallback to uncompressed
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
headers['Content-Length'] = Buffer.byteLength(responseContent)
|
|
356
|
+
|
|
357
|
+
res.writeHead(200, headers)
|
|
358
|
+
res.end(responseContent)
|
|
359
|
+
} catch (error) {
|
|
360
|
+
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
361
|
+
res.end('500')
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Request handler
|
|
367
|
+
*/
|
|
368
|
+
const requestHandler = async (req, res) => {
|
|
369
|
+
const rootDir = process.cwd()
|
|
370
|
+
const staticDir = path.join(
|
|
371
|
+
rootDir,
|
|
372
|
+
config.webpack.output.buildDirectory,
|
|
373
|
+
'static',
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const parsedUrl = new URL(req.url, `http://${req.headers.host}`)
|
|
378
|
+
const pathname = decodeURIComponent(parsedUrl.pathname)
|
|
379
|
+
|
|
380
|
+
const safePath = validatePath(pathname, staticDir)
|
|
381
|
+
if (!safePath) {
|
|
382
|
+
res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' })
|
|
383
|
+
res.end('403')
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const fileServed = await serveStaticFile(safePath, req, res)
|
|
388
|
+
|
|
389
|
+
if (!fileServed) {
|
|
390
|
+
await serveHTMLPage(pathname, staticDir, req, res)
|
|
391
|
+
}
|
|
392
|
+
} catch (error) {
|
|
393
|
+
console.error('[Ryunix Server Error]:', error.message)
|
|
394
|
+
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' })
|
|
395
|
+
res.end('500')
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const httpServ = http.createServer(requestHandler)
|
|
400
|
+
|
|
401
|
+
export default httpServ
|
package/webpack/utils/config.cjs
CHANGED
|
@@ -67,7 +67,6 @@ const DEFAULT_SSG_SITEMAP_SETTINGS = {
|
|
|
67
67
|
priority: '0.7',
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
71
70
|
// ============================================================================
|
|
72
71
|
// Configuration Builder
|
|
73
72
|
// ============================================================================
|
|
@@ -171,5 +170,4 @@ const defaultSettings = {
|
|
|
171
170
|
},
|
|
172
171
|
}
|
|
173
172
|
|
|
174
|
-
|
|
175
173
|
module.exports = defaultSettings
|
package/webpack/utils/ssg.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import path from 'path'
|
|
|
9
9
|
/**
|
|
10
10
|
* Extract valid routes for SSG from routes configuration
|
|
11
11
|
* Filters out dynamic routes and special routes
|
|
12
|
-
*
|
|
12
|
+
*
|
|
13
13
|
* @param {Array} routes - Array of route objects
|
|
14
14
|
* @returns {Array} - Array of valid SSG routes
|
|
15
15
|
*/
|
|
@@ -46,7 +46,7 @@ const extractSSGRoutes = (routes) => {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Generate robots.txt content
|
|
49
|
-
*
|
|
49
|
+
*
|
|
50
50
|
* @param {string} baseURL - Base URL of the site
|
|
51
51
|
* @param {Object} options - Configuration options
|
|
52
52
|
* @param {Array<string>} options.disallow - Paths to disallow
|
|
@@ -73,7 +73,7 @@ const generateRobotsTxt = (baseURL, options = {}) => {
|
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* Generate XML sitemap with all routes
|
|
76
|
-
*
|
|
76
|
+
*
|
|
77
77
|
* @param {Array} routes - Array of route objects
|
|
78
78
|
* @param {string} baseURL - Base URL of the site
|
|
79
79
|
* @param {Object} defaultSettings - Default sitemap settings
|
|
@@ -115,7 +115,7 @@ ${urls}
|
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
117
|
* Generate HTML meta tags from metadata object
|
|
118
|
-
*
|
|
118
|
+
*
|
|
119
119
|
* @param {Object} meta - Metadata object
|
|
120
120
|
* @param {Object} defaultMeta - Default metadata
|
|
121
121
|
* @returns {string} - HTML meta tags
|
|
@@ -181,7 +181,7 @@ const generateMetaTags = (meta, defaultMeta = {}) => {
|
|
|
181
181
|
|
|
182
182
|
/**
|
|
183
183
|
* Prerender a route to static HTML
|
|
184
|
-
*
|
|
184
|
+
*
|
|
185
185
|
* @param {Object} route - Route object
|
|
186
186
|
* @param {string} template - HTML template
|
|
187
187
|
* @param {Object} config - Configuration object
|
|
@@ -194,24 +194,15 @@ const prerenderRoute = async (route, template, config) => {
|
|
|
194
194
|
|
|
195
195
|
// Replace title - use route meta or default
|
|
196
196
|
const pageTitle = meta.title || defaultMeta.title || 'Ryunix App'
|
|
197
|
-
html = html.replace(
|
|
198
|
-
/<title>.*?<\/title>/,
|
|
199
|
-
`<title>${pageTitle}</title>`,
|
|
200
|
-
)
|
|
197
|
+
html = html.replace(/<title>.*?<\/title>/, `<title>${pageTitle}</title>`)
|
|
201
198
|
|
|
202
199
|
// Generate and add meta tags
|
|
203
200
|
const metaTags = generateMetaTags(meta, defaultMeta)
|
|
204
201
|
|
|
205
202
|
// Remove existing meta tags (except framework/mode) and duplicate favicon
|
|
206
203
|
// Remove all meta tags except framework and mode
|
|
207
|
-
html = html.replace(
|
|
208
|
-
|
|
209
|
-
''
|
|
210
|
-
)
|
|
211
|
-
html = html.replace(
|
|
212
|
-
/<meta\s+property="[^"]*"[^>]*>/gi,
|
|
213
|
-
''
|
|
214
|
-
)
|
|
204
|
+
html = html.replace(/<meta\s+name="(?!framework|mode)[^"]*"[^>]*>/gi, '')
|
|
205
|
+
html = html.replace(/<meta\s+property="[^"]*"[^>]*>/gi, '')
|
|
215
206
|
|
|
216
207
|
// Remove duplicate favicon links (keep only first one)
|
|
217
208
|
const faviconMatches = html.match(/<link\s+rel="icon"[^>]*>/gi)
|
|
@@ -285,7 +276,7 @@ const prerenderRoute = async (route, template, config) => {
|
|
|
285
276
|
/**
|
|
286
277
|
* Full SSG build process
|
|
287
278
|
* Generates prerendered HTML, sitemap, and robots.txt
|
|
288
|
-
*
|
|
279
|
+
*
|
|
289
280
|
* @param {Array} routesConfig - Routes configuration
|
|
290
281
|
* @param {Object} config - Site configuration
|
|
291
282
|
* @param {string} buildDir - Build output directory
|
|
@@ -295,7 +286,6 @@ const buildSSG = async (routesConfig, config, buildDir) => {
|
|
|
295
286
|
const routes = extractSSGRoutes(routesConfig)
|
|
296
287
|
|
|
297
288
|
if (routes.length === 0) {
|
|
298
|
-
|
|
299
289
|
return
|
|
300
290
|
}
|
|
301
291
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ryunix Routes Plugin - SSG Manifest Generator
|
|
3
3
|
* Extracts routes from routes.ryx file and generates manifest for static site generation
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* Features:
|
|
6
6
|
* - Template literal support (`/docs/${var.field}`)
|
|
7
7
|
* - Frontmatter extraction from MDX files
|
|
@@ -24,39 +24,31 @@ class RyunixRoutesPlugin {
|
|
|
24
24
|
compiler.hooks.emit.tapAsync(
|
|
25
25
|
'RyunixRoutesPlugin',
|
|
26
26
|
(compilation, callback) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
27
|
// Skip in development mode
|
|
30
28
|
if (compiler.options.mode !== 'production') {
|
|
31
29
|
callback()
|
|
32
30
|
return
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
33
|
const routesFile = path.resolve(process.cwd(), this.routesPath)
|
|
38
34
|
|
|
39
35
|
if (!fs.existsSync(routesFile)) {
|
|
40
|
-
console.log(
|
|
41
|
-
'[SSG] ❌ The route file was not found:', this.routesPath,
|
|
42
|
-
)
|
|
36
|
+
console.log('[SSG] ❌ The route file was not found:', this.routesPath)
|
|
43
37
|
callback()
|
|
44
38
|
return
|
|
45
39
|
}
|
|
46
40
|
|
|
47
|
-
|
|
48
41
|
try {
|
|
49
42
|
const content = fs.readFileSync(routesFile, 'utf-8')
|
|
50
43
|
|
|
51
44
|
this.extractFrontmatter(content, path.dirname(routesFile))
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
|
|
55
46
|
const routes = this.parseRoutes(content)
|
|
56
47
|
|
|
57
|
-
|
|
58
48
|
// Count routes with meta
|
|
59
|
-
const routesWithMeta = routes.filter(
|
|
49
|
+
const routesWithMeta = routes.filter(
|
|
50
|
+
(r) => r.meta && Object.keys(r.meta).length > 0,
|
|
51
|
+
)
|
|
60
52
|
|
|
61
53
|
const manifest = JSON.stringify(routes, null, 2)
|
|
62
54
|
const outputPath = path.resolve(process.cwd(), this.outputPath)
|
|
@@ -64,10 +56,7 @@ class RyunixRoutesPlugin {
|
|
|
64
56
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
|
|
65
57
|
fs.writeFileSync(outputPath, manifest)
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
|
|
69
59
|
console.log('✅ [SSG Plugin] Process successfully completed')
|
|
70
|
-
|
|
71
60
|
} catch (error) {
|
|
72
61
|
console.error('\n' + '='.repeat(70))
|
|
73
62
|
console.error('[SSG] ❌ ERROR generating route manifest:')
|
|
@@ -98,7 +87,6 @@ class RyunixRoutesPlugin {
|
|
|
98
87
|
// Resolve absolute path
|
|
99
88
|
const absolutePath = path.resolve(baseDir, mdxPath)
|
|
100
89
|
|
|
101
|
-
|
|
102
90
|
if (!fs.existsSync(absolutePath)) {
|
|
103
91
|
if (this.debug) {
|
|
104
92
|
console.warn(`⚠️ File not found: ${absolutePath}`)
|
|
@@ -112,7 +100,6 @@ class RyunixRoutesPlugin {
|
|
|
112
100
|
|
|
113
101
|
// Remove BOM if present
|
|
114
102
|
if (mdxContent.charCodeAt(0) === 0xfeff) {
|
|
115
|
-
|
|
116
103
|
mdxContent = mdxContent.slice(1)
|
|
117
104
|
}
|
|
118
105
|
|
|
@@ -120,17 +107,11 @@ class RyunixRoutesPlugin {
|
|
|
120
107
|
|
|
121
108
|
if (Object.keys(frontmatter).length > 0) {
|
|
122
109
|
this.frontmatterCache.set(alias, frontmatter)
|
|
123
|
-
|
|
124
|
-
}
|
|
110
|
+
}
|
|
125
111
|
} catch (error) {
|
|
126
|
-
console.error(
|
|
127
|
-
`❌ Error reading ${absolutePath}:`,
|
|
128
|
-
error.message,
|
|
129
|
-
)
|
|
112
|
+
console.error(`❌ Error reading ${absolutePath}:`, error.message)
|
|
130
113
|
}
|
|
131
114
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
115
|
}
|
|
135
116
|
|
|
136
117
|
/**
|
|
@@ -230,7 +211,8 @@ class RyunixRoutesPlugin {
|
|
|
230
211
|
const routes = []
|
|
231
212
|
|
|
232
213
|
// Match route objects - capture the entire object
|
|
233
|
-
const routeRegex =
|
|
214
|
+
const routeRegex =
|
|
215
|
+
/\{\s*path:\s*[`"'][^`"']*[`"'](?:\s*\|\|\s*[`"'][^`"']*[`"'])?\s*,[\s\S]*?\}/g
|
|
234
216
|
let match
|
|
235
217
|
|
|
236
218
|
while ((match = routeRegex.exec(content)) !== null) {
|
|
@@ -258,14 +240,11 @@ class RyunixRoutesPlugin {
|
|
|
258
240
|
|
|
259
241
|
// Skip dynamic routes and wildcards
|
|
260
242
|
if (evaluatedPath.includes(':') || evaluatedPath === '*') {
|
|
261
|
-
|
|
262
243
|
continue
|
|
263
244
|
}
|
|
264
245
|
|
|
265
246
|
// Check for noRenderLink early
|
|
266
|
-
const noRenderLinkMatch = routeBlock.match(
|
|
267
|
-
/noRenderLink:\s*([^,}\s]+)/,
|
|
268
|
-
)
|
|
247
|
+
const noRenderLinkMatch = routeBlock.match(/noRenderLink:\s*([^,}\s]+)/)
|
|
269
248
|
if (noRenderLinkMatch) {
|
|
270
249
|
const noRenderValue = this.evaluateExpression(
|
|
271
250
|
noRenderLinkMatch[1].trim(),
|
|
@@ -280,7 +259,6 @@ class RyunixRoutesPlugin {
|
|
|
280
259
|
|
|
281
260
|
// Check for NotFound property
|
|
282
261
|
if (routeBlock.includes('NotFound:')) {
|
|
283
|
-
|
|
284
262
|
continue
|
|
285
263
|
}
|
|
286
264
|
|
|
@@ -293,8 +271,6 @@ class RyunixRoutesPlugin {
|
|
|
293
271
|
const metaContent = this.extractMetaObject(routeBlock)
|
|
294
272
|
|
|
295
273
|
if (metaContent) {
|
|
296
|
-
|
|
297
|
-
|
|
298
274
|
route.meta = {}
|
|
299
275
|
|
|
300
276
|
// Temporalmente activar debug para esta ruta
|
|
@@ -307,16 +283,11 @@ class RyunixRoutesPlugin {
|
|
|
307
283
|
this.extractField(metaContent, 'image', route.meta)
|
|
308
284
|
this.extractField(metaContent, 'author', route.meta)
|
|
309
285
|
this.extractField(metaContent, 'canonical', route.meta)
|
|
310
|
-
|
|
311
|
-
|
|
312
286
|
} else {
|
|
313
287
|
console.log(` ❌ No object meta found in route: ${evaluatedPath}`)
|
|
314
|
-
|
|
315
288
|
}
|
|
316
289
|
|
|
317
290
|
routes.push(route)
|
|
318
|
-
|
|
319
|
-
|
|
320
291
|
}
|
|
321
292
|
|
|
322
293
|
return routes
|
|
@@ -526,7 +497,7 @@ class RyunixRoutesPlugin {
|
|
|
526
497
|
// Only add field if value is meaningful
|
|
527
498
|
if (value !== undefined && value !== null && value !== '') {
|
|
528
499
|
target[fieldName] = value
|
|
529
|
-
}
|
|
500
|
+
}
|
|
530
501
|
}
|
|
531
502
|
|
|
532
503
|
/**
|
|
@@ -70,7 +70,7 @@ export default {
|
|
|
70
70
|
},
|
|
71
71
|
context: resolveApp(dir, config.webpack.root),
|
|
72
72
|
entry: './main.ryx',
|
|
73
|
-
devtool: config.webpack.production ? 'source-map'
|
|
73
|
+
devtool: config.webpack.production ? false : 'source-map',
|
|
74
74
|
output: {
|
|
75
75
|
path: resolveApp(dir, `${config.webpack.output.buildDirectory}/static`),
|
|
76
76
|
publicPath: '/',
|