rasengan 1.2.1-beta.4 → 1.2.2

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/esm/core/plugins/index.js +10 -13
  3. package/lib/esm/entries/client/render.js +24 -7
  4. package/lib/esm/entries/server/entry.server.js +1 -1
  5. package/lib/esm/entries/server/error-template.js +14 -0
  6. package/lib/esm/entries/server/index.js +2 -1
  7. package/lib/esm/routing/components/index.js +12 -69
  8. package/lib/esm/routing/error-overlay/ErrorBoundaryFallback.js +42 -0
  9. package/lib/esm/routing/error-overlay/ErrorOverlay.js +60 -0
  10. package/lib/esm/routing/error-overlay/ErrorOverlayProvider.js +39 -0
  11. package/lib/esm/routing/error-overlay/error-overlay.css +472 -0
  12. package/lib/esm/routing/error-overlay/error-store.js +44 -0
  13. package/lib/esm/routing/error-overlay/index.js +2 -0
  14. package/lib/esm/routing/error-overlay/stack-utils.js +72 -0
  15. package/lib/esm/routing/utils/define-router.js +10 -4
  16. package/lib/esm/routing/utils/flat-routes.js +10 -5
  17. package/lib/esm/routing/utils/generate-routes.js +3 -2
  18. package/lib/esm/server/dev/handlers.js +9 -5
  19. package/lib/esm/server/dev/server.js +8 -1
  20. package/lib/esm/server/node/index.js +2 -4
  21. package/lib/esm/server/node/rendering.js +6 -4
  22. package/lib/tsconfig.esm.tsbuildinfo +1 -1
  23. package/lib/tsconfig.types.tsbuildinfo +1 -1
  24. package/lib/types/entries/server/entry.server.d.ts +2 -0
  25. package/lib/types/entries/server/error-template.d.ts +1 -0
  26. package/lib/types/routing/error-overlay/ErrorBoundaryFallback.d.ts +14 -0
  27. package/lib/types/routing/error-overlay/ErrorOverlay.d.ts +2 -0
  28. package/lib/types/routing/error-overlay/ErrorOverlayProvider.d.ts +7 -0
  29. package/lib/types/routing/error-overlay/error-store.d.ts +24 -0
  30. package/lib/types/routing/error-overlay/index.d.ts +3 -0
  31. package/lib/types/routing/error-overlay/stack-utils.d.ts +31 -0
  32. package/lib/types/routing/utils/define-router.d.ts +6 -0
  33. package/lib/types/server/node/rendering.d.ts +7 -1
  34. package/package.json +13 -12
  35. package/types/client.d.ts +1 -0
@@ -0,0 +1,472 @@
1
+ :root {
2
+ --re-bg: #0d1117;
3
+ --re-surface: #161b22;
4
+ --re-surface-subtle: #21262d;
5
+ --re-border: #30363d;
6
+ --re-text: #e6edf3;
7
+ --re-text-secondary: #8b949e;
8
+ --re-error: #f85149;
9
+ --re-error-hover: #ff7b72;
10
+ --re-accent: #2f81f7;
11
+ }
12
+
13
+ .rasengan-error-backdrop {
14
+ position: fixed;
15
+ inset: 0;
16
+ background: rgba(1, 4, 9, 0.8);
17
+ backdrop-filter: blur(4px);
18
+ z-index: 99998;
19
+ animation: re-fade-in 0.2s ease;
20
+ }
21
+
22
+ .rasengan-error-overlay {
23
+ position: fixed;
24
+ top: 50%;
25
+ left: 50%;
26
+ transform: translate(-50%, -50%);
27
+ z-index: 99999;
28
+ width: 92%;
29
+ max-width: 840px;
30
+ max-height: 82vh;
31
+ display: flex;
32
+ flex-direction: column;
33
+ background: var(--re-bg);
34
+ border: 1px solid var(--re-border);
35
+ border-radius: 12px;
36
+ color: var(--re-text);
37
+ font-family:
38
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica,
39
+ Arial, sans-serif;
40
+ font-size: 14px;
41
+ line-height: 1.5;
42
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
43
+ overflow: hidden;
44
+ animation: re-overlay-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);
45
+ }
46
+
47
+ @keyframes re-fade-in {
48
+ from {
49
+ opacity: 0;
50
+ }
51
+ to {
52
+ opacity: 1;
53
+ }
54
+ }
55
+
56
+ @keyframes re-overlay-in {
57
+ from {
58
+ opacity: 0;
59
+ transform: translate(-50%, -50%) scale(0.96);
60
+ }
61
+ to {
62
+ opacity: 1;
63
+ transform: translate(-50%, -50%) scale(1);
64
+ }
65
+ }
66
+
67
+ @keyframes re-glow-pulse {
68
+ 0%,
69
+ 100% {
70
+ box-shadow: 0 0 0 0 rgba(248, 81, 73, 0.3);
71
+ }
72
+ 50% {
73
+ box-shadow: 0 0 12px 2px rgba(248, 81, 73, 0.15);
74
+ }
75
+ }
76
+
77
+ @keyframes re-dot-pulse {
78
+ 0%,
79
+ 100% {
80
+ opacity: 1;
81
+ box-shadow: 0 0 0 0 rgba(63, 185, 80, 0.5);
82
+ }
83
+ 50% {
84
+ opacity: 0.8;
85
+ box-shadow: 0 0 6px 2px rgba(63, 185, 80, 0.3);
86
+ }
87
+ }
88
+
89
+ .rasengan-error-overlay-header {
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: space-between;
93
+ padding: 12px 16px;
94
+ background: var(--re-surface);
95
+ border-bottom: 1px solid var(--re-border);
96
+ border-left: 3px solid var(--re-error);
97
+ flex-shrink: 0;
98
+ }
99
+
100
+ .rasengan-error-pagination {
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 6px;
104
+ }
105
+
106
+ .rasengan-error-version-label {
107
+ display: inline-flex;
108
+ align-items: center;
109
+ gap: 6px;
110
+ font-size: 11px;
111
+ color: var(--re-text-secondary);
112
+ font-weight: 500;
113
+ user-select: none;
114
+ letter-spacing: 0.02em;
115
+ flex-shrink: 0;
116
+ padding: 0 8px;
117
+ }
118
+
119
+ .rasengan-error-version-dot {
120
+ width: 7px;
121
+ height: 7px;
122
+ border-radius: 50%;
123
+ background: #3fb950;
124
+ animation: re-dot-pulse 2s ease-in-out infinite;
125
+ flex-shrink: 0;
126
+ }
127
+
128
+ .rasengan-error-pagination-btn {
129
+ background: transparent;
130
+ border: 1px solid var(--re-border);
131
+ color: var(--re-text-secondary);
132
+ width: 28px;
133
+ height: 28px;
134
+ border-radius: 6px;
135
+ cursor: pointer;
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ font-size: 16px;
140
+ font-family: inherit;
141
+ transition:
142
+ background 0.15s,
143
+ color 0.15s,
144
+ border-color 0.15s;
145
+ line-height: 1;
146
+ }
147
+
148
+ .rasengan-error-pagination-btn:hover:not(:disabled) {
149
+ background: var(--re-surface-subtle);
150
+ color: var(--re-text);
151
+ border-color: var(--re-text-secondary);
152
+ }
153
+
154
+ .rasengan-error-pagination-btn:disabled {
155
+ opacity: 0.25;
156
+ cursor: not-allowed;
157
+ }
158
+
159
+ .rasengan-error-pagination-text {
160
+ font-size: 12px;
161
+ color: var(--re-text-secondary);
162
+ min-width: 64px;
163
+ text-align: center;
164
+ user-select: none;
165
+ font-variant-numeric: tabular-nums;
166
+ }
167
+
168
+ .rasengan-error-overlay-actions {
169
+ display: flex;
170
+ gap: 4px;
171
+ flex-shrink: 0;
172
+ }
173
+
174
+ .rasengan-error-overlay-close {
175
+ background: transparent;
176
+ border: 1px solid var(--re-border);
177
+ color: var(--re-text-secondary);
178
+ width: 28px;
179
+ height: 28px;
180
+ border-radius: 6px;
181
+ cursor: pointer;
182
+ font-size: 15px;
183
+ display: flex;
184
+ align-items: center;
185
+ justify-content: center;
186
+ transition:
187
+ background 0.15s,
188
+ color 0.15s,
189
+ border-color 0.15s;
190
+ line-height: 1;
191
+ font-family: inherit;
192
+ }
193
+
194
+ .rasengan-error-overlay-close:hover {
195
+ background: var(--re-surface-subtle);
196
+ color: var(--re-text);
197
+ border-color: var(--re-text-secondary);
198
+ }
199
+
200
+ .rasengan-error-overlay-body {
201
+ flex: 1;
202
+ overflow: auto;
203
+ padding: 24px 24px 20px;
204
+ }
205
+
206
+ .rasengan-error-overlay-source {
207
+ display: flex;
208
+ flex-direction: row;
209
+ align-items: center;
210
+ gap: 8px;
211
+ font-size: 11px;
212
+ font-weight: 600;
213
+ text-transform: uppercase;
214
+ letter-spacing: 0.08em;
215
+ color: var(--re-text-secondary);
216
+ margin-bottom: 4px;
217
+ }
218
+
219
+ .rasengan-error-overlay-message {
220
+ font-size: 15px;
221
+ font-weight: 600;
222
+ color: var(--re-error);
223
+ margin-bottom: 20px;
224
+ line-height: 1.6;
225
+ word-break: break-word;
226
+ /* animation: re-glow-pulse 2.5s ease-in-out infinite; */
227
+ }
228
+
229
+ .rasengan-error-code-preview {
230
+ margin-bottom: 20px;
231
+ border: 1px solid var(--re-border);
232
+ border-radius: 8px;
233
+ overflow: hidden;
234
+ background: var(--re-surface);
235
+ }
236
+
237
+ .rasengan-error-code-preview-header {
238
+ display: flex;
239
+ align-items: center;
240
+ gap: 6px;
241
+ padding: 7px 12px;
242
+ background: var(--re-surface-subtle);
243
+ border-bottom: 1px solid var(--re-border);
244
+ font-size: 12px;
245
+ color: var(--re-text-secondary);
246
+ overflow: hidden;
247
+ text-overflow: ellipsis;
248
+ white-space: nowrap;
249
+ }
250
+
251
+ .rasengan-error-code-preview-file {
252
+ font-weight: 500;
253
+ color: var(--re-text);
254
+ }
255
+
256
+ .rasengan-error-code-preview-line {
257
+ color: var(--re-text-secondary);
258
+ margin-left: auto;
259
+ font-variant-numeric: tabular-nums;
260
+ }
261
+
262
+ .rasengan-error-code-preview-content {
263
+ padding: 8px 0;
264
+ overflow-x: auto;
265
+ font-family:
266
+ 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Courier New', monospace;
267
+ font-size: 12px;
268
+ }
269
+
270
+ .rasengan-error-code-line {
271
+ display: flex;
272
+ align-items: center;
273
+ min-height: 24px;
274
+ padding: 0 12px;
275
+ line-height: 1.6;
276
+ color: var(--re-text);
277
+ }
278
+
279
+ .rasengan-error-code-line.error {
280
+ background: rgba(248, 81, 73, 0.08);
281
+ border-left: 2px solid var(--re-error);
282
+ }
283
+
284
+ .rasengan-error-code-line-number {
285
+ min-width: 30px;
286
+ text-align: right;
287
+ padding-right: 12px;
288
+ color: var(--re-text-secondary);
289
+ user-select: none;
290
+ font-size: 11px;
291
+ opacity: 0.6;
292
+ }
293
+
294
+ .rasengan-error-code-line-arrow {
295
+ color: var(--re-error);
296
+ margin-right: 6px;
297
+ font-weight: 700;
298
+ font-size: 14px;
299
+ line-height: 1;
300
+ width: 12px;
301
+ flex-shrink: 0;
302
+ }
303
+
304
+ .rasengan-error-code-line-content {
305
+ white-space: pre;
306
+ tab-size: 2;
307
+ }
308
+
309
+ .rasengan-error-overlay-section {
310
+ margin-top: 16px;
311
+ border: 1px solid var(--re-border);
312
+ border-radius: 8px;
313
+ overflow: hidden;
314
+ background: var(--re-surface);
315
+ }
316
+
317
+ .rasengan-error-overlay-section-title {
318
+ font-size: 11px;
319
+ font-weight: 600;
320
+ text-transform: uppercase;
321
+ letter-spacing: 0.08em;
322
+ color: var(--re-text-secondary);
323
+ padding: 8px 14px;
324
+ background: var(--re-surface-subtle);
325
+ border-bottom: 1px solid var(--re-border);
326
+ cursor: pointer;
327
+ user-select: none;
328
+ transition: color 0.15s;
329
+ }
330
+
331
+ /* .rasengan-error-overlay-section-title::-webkit-details-marker {
332
+ display: none;
333
+ } */
334
+
335
+ /* .rasengan-error-overlay-section-title::before {
336
+ content: '\203A';
337
+ display: inline-block;
338
+ margin-right: 8px;
339
+ font-size: 14px;
340
+ font-weight: 700;
341
+ color: var(--re-text-secondary);
342
+ transition: transform 0.2s;
343
+ }
344
+
345
+ .rasengan-error-overlay-section[open] > .rasengan-error-overlay-section-title::before {
346
+ transform: rotate(90deg);
347
+ } */
348
+
349
+ .rasengan-error-overlay-section-title:hover {
350
+ color: var(--re-text);
351
+ }
352
+
353
+ .rasengan-error-overlay-stack {
354
+ margin: 0;
355
+ padding: 14px 16px;
356
+ font-family:
357
+ 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Courier New', monospace;
358
+ font-size: 12px;
359
+ line-height: 1.7;
360
+ color: var(--re-text-secondary);
361
+ white-space: pre-wrap;
362
+ word-break: break-word;
363
+ overflow-x: auto;
364
+ tab-size: 2;
365
+ background: var(--re-bg);
366
+ }
367
+
368
+ .rasengan-error-overlay-stack::-webkit-scrollbar {
369
+ width: 6px;
370
+ height: 6px;
371
+ }
372
+
373
+ .rasengan-error-overlay-stack::-webkit-scrollbar-track {
374
+ background: transparent;
375
+ }
376
+
377
+ .rasengan-error-overlay-stack::-webkit-scrollbar-thumb {
378
+ background: var(--re-border);
379
+ border-radius: 3px;
380
+ }
381
+
382
+ .rasengan-error-fab {
383
+ position: fixed;
384
+ bottom: 24px;
385
+ left: 24px;
386
+ z-index: 99999;
387
+ width: 48px;
388
+ height: 48px;
389
+ border-radius: 50%;
390
+ background: var(--re-error);
391
+ border: none;
392
+ color: #fff;
393
+ cursor: pointer;
394
+ display: flex;
395
+ align-items: center;
396
+ justify-content: center;
397
+ box-shadow: 0 4px 16px rgba(248, 81, 73, 0.35);
398
+ transition:
399
+ transform 0.2s,
400
+ box-shadow 0.2s,
401
+ background 0.2s;
402
+ animation: re-fab-in 0.35s cubic-bezier(0.16, 1, 0.3, 1);
403
+ }
404
+
405
+ .rasengan-error-fab:hover {
406
+ transform: scale(1.08);
407
+ background: var(--re-error-hover);
408
+ box-shadow: 0 6px 24px rgba(248, 81, 73, 0.45);
409
+ }
410
+
411
+ .rasengan-error-fab:active {
412
+ transform: scale(0.95);
413
+ }
414
+
415
+ .rasengan-error-fab-icon {
416
+ font-size: 20px;
417
+ font-weight: 700;
418
+ line-height: 1;
419
+ }
420
+
421
+ .rasengan-error-fab-count {
422
+ position: absolute;
423
+ top: -4px;
424
+ right: -4px;
425
+ background: var(--re-text);
426
+ color: var(--re-bg);
427
+ font-size: 10px;
428
+ font-weight: 700;
429
+ min-width: 18px;
430
+ height: 18px;
431
+ border-radius: 9px;
432
+ display: flex;
433
+ align-items: center;
434
+ justify-content: center;
435
+ padding: 0 4px;
436
+ font-family:
437
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica,
438
+ Arial, sans-serif;
439
+ }
440
+
441
+ .rasengan-route-error {
442
+ display: flex;
443
+ align-items: center;
444
+ gap: 8px;
445
+ padding: 10px 16px;
446
+ background: var(--re-surface);
447
+ border-bottom: 1px solid var(--re-border);
448
+ border-left: 3px solid var(--re-error);
449
+ color: var(--re-error);
450
+ font-family:
451
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica,
452
+ Arial, sans-serif;
453
+ font-size: 13px;
454
+ font-weight: 500;
455
+ }
456
+
457
+ .rasengan-route-error-detail {
458
+ color: var(--re-text-secondary);
459
+ font-size: 12px;
460
+ font-weight: 400;
461
+ }
462
+
463
+ @keyframes re-fab-in {
464
+ from {
465
+ transform: scale(0);
466
+ opacity: 0;
467
+ }
468
+ to {
469
+ transform: scale(1);
470
+ opacity: 1;
471
+ }
472
+ }
@@ -0,0 +1,44 @@
1
+ class ErrorStore {
2
+ errors = [];
3
+ listeners = new Set();
4
+ nextId = 1;
5
+ minimized = false;
6
+ addError(error, source = 'global', componentStack) {
7
+ this.errors = [
8
+ ...this.errors,
9
+ {
10
+ id: this.nextId++,
11
+ error,
12
+ source,
13
+ timestamp: Date.now(),
14
+ componentStack,
15
+ },
16
+ ];
17
+ this.notify();
18
+ }
19
+ clearAll() {
20
+ this.errors = [];
21
+ this.minimized = false;
22
+ this.notify();
23
+ }
24
+ toggleMinimize() {
25
+ this.minimized = !this.minimized;
26
+ this.notify();
27
+ }
28
+ getErrors() {
29
+ return this.errors;
30
+ }
31
+ isMinimized() {
32
+ return this.minimized;
33
+ }
34
+ subscribe(listener) {
35
+ this.listeners.add(listener);
36
+ return () => {
37
+ this.listeners.delete(listener);
38
+ };
39
+ }
40
+ notify() {
41
+ this.listeners.forEach((fn) => fn());
42
+ }
43
+ }
44
+ export const errorStore = new ErrorStore();
@@ -0,0 +1,2 @@
1
+ export { ErrorOverlayProvider } from './ErrorOverlayProvider.js';
2
+ export { errorStore } from './error-store.js';
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Parse an error stack trace and extract the first meaningful user frame.
3
+ * Handles Chrome/V8 and Firefox stack trace formats.
4
+ * Skips the "Error:" header line.
5
+ *
6
+ * @param stack - The full error.stack string
7
+ * @returns The first parsed StackFrame, or null if none found
8
+ */
9
+ export function parseStackFrame(stack) {
10
+ const lines = stack.split('\n');
11
+ for (const rawLine of lines) {
12
+ const line = rawLine.trim();
13
+ if (!line || line.startsWith('Error:'))
14
+ continue;
15
+ // Match: /path/file.ext:line:col (absolute path, any extension)
16
+ const match = line.match(/(\/[^\s:]+\.(?:tsx?|jsx?|m?js|mdx?|css|json|html)):(\d+):(\d+)/);
17
+ if (match) {
18
+ const file = match[1].split('?')[0].split('#')[0];
19
+ return { file, line: parseInt(match[2]), column: parseInt(match[3]) };
20
+ }
21
+ }
22
+ return null;
23
+ }
24
+ /**
25
+ * Vite plugin transforms may wrap a file's original source in
26
+ * `export default "..."` with escape sequences. Detect and unwrap.
27
+ */
28
+ function unwrapExportDefaultString(source) {
29
+ const match = source.match(/^export default\s+"((?:[^"\\]|\\.)*)"/s);
30
+ if (match) {
31
+ try {
32
+ return JSON.parse('"' + match[1] + '"');
33
+ }
34
+ catch {
35
+ return source;
36
+ }
37
+ }
38
+ return source;
39
+ }
40
+ /**
41
+ * Fetch the source file from the Vite dev server (using ?raw to get original
42
+ * source) and return a snippet of lines surrounding the error line.
43
+ *
44
+ * @param file - File path (absolute, e.g. /src/app/page.tsx)
45
+ * @param errorLine - 1-indexed line number where the error occurred
46
+ * @param contextLines - Number of lines to show above and below the error
47
+ * @returns An object with the snippet string, the 0-indexed error line
48
+ * position within the snippet, and the total file line count,
49
+ * or null on failure
50
+ */
51
+ export async function fetchSourceSnippet(file, errorLine, contextLines = 2) {
52
+ try {
53
+ const url = file + '?raw';
54
+ const response = await fetch(url);
55
+ if (!response.ok)
56
+ return null;
57
+ let source = await response.text();
58
+ // Some Vite transforms wrap the source in `export default "..."`,
59
+ // escaping newlines and quotes. Detect and unescape it.
60
+ source = unwrapExportDefaultString(source);
61
+ const lines = source.split('\n');
62
+ const totalLines = lines.length;
63
+ const start = Math.max(0, errorLine - contextLines - 1);
64
+ const end = Math.min(lines.length, errorLine + contextLines);
65
+ const snippet = lines.slice(start, end).join('\n');
66
+ const errorLineIndex = errorLine - 1 - start;
67
+ return { snippet, errorLineIndex, totalLines };
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
@@ -16,12 +16,12 @@ export const defineRouter = (option) => {
16
16
  // Check if p is an array
17
17
  if (Array.isArray(p)) {
18
18
  for (let page of p) {
19
- if ('source' in page) {
19
+ if (page.source) {
20
20
  pageComponentList.push(page);
21
21
  continue;
22
22
  }
23
23
  // When p is a MDXPageComponent
24
- // type property holds the "MDXPageComponent" value, coming from @rasenganjs/mdx plugin
24
+ // the "type" property holds the "MDXPageComponent" value, coming from @rasenganjs/mdx plugin
25
25
  if (isMDXPage(page)) {
26
26
  const Page = await convertMDXPageToPageComponent(page);
27
27
  pageComponentList.push(Page);
@@ -32,7 +32,7 @@ export const defineRouter = (option) => {
32
32
  }
33
33
  continue;
34
34
  }
35
- if ('source' in p) {
35
+ if (p.source) {
36
36
  pageComponentList.push(p);
37
37
  continue;
38
38
  }
@@ -57,9 +57,15 @@ export const defineRouter = (option) => {
57
57
  return router;
58
58
  };
59
59
  };
60
+ /**
61
+ * This function helps to convert the data provided by @rasenganjs/mdx into a PageComponent component
62
+ * The MDXPage arg has to follow exactly the type returned by the @rasenganjs/mdx plugin
63
+ * @param MDXPage
64
+ * @returns
65
+ */
60
66
  export const convertMDXPageToPageComponent = async (MDXPage) => {
61
67
  const Page = () => {
62
- return (_jsx(MDXPage.Renderer, { config: MDXPage.config, toc: MDXPage.toc, children: MDXPage.Content }));
68
+ return (_jsx(MDXPage.Renderer, { config: MDXPage.config, toc: MDXPage.toc, raw: MDXPage.raw, children: MDXPage.Content }));
63
69
  };
64
70
  Page.path = MDXPage.metadata.path;
65
71
  Page.metadata = MDXPage.metadata.metadata;
@@ -179,7 +179,7 @@ async function generateRouter(tree) {
179
179
  let layout;
180
180
  // Get layout if defined
181
181
  if (root.isLayout) {
182
- if ('source' in root) {
182
+ if (root.source) {
183
183
  layout = root;
184
184
  }
185
185
  else {
@@ -216,7 +216,7 @@ async function generateRoutes(tree) {
216
216
  // Handle layout
217
217
  if (node.isLayout) {
218
218
  let layout;
219
- if ('source' in node) {
219
+ if (node.source) {
220
220
  layout = node;
221
221
  }
222
222
  else {
@@ -257,7 +257,7 @@ async function generateRoutes(tree) {
257
257
  }
258
258
  catch (error) {
259
259
  console.error(error);
260
- // TODO: Handle error
260
+ throw error;
261
261
  }
262
262
  }
263
263
  /**
@@ -284,8 +284,14 @@ export async function flatRoutes(fn) {
284
284
  const layoutModulesMap = new Map([...modulesMap.entries()].filter(([filePath]) => filePath.includes('layout.')));
285
285
  // Filter out pages
286
286
  const pageModulesMap = new Map([...modulesMap.entries()].filter(([filePath]) => filePath.includes('.page.')));
287
+ const isRootLayoutExists = Object.keys(layoutModulesMap).find((path) => [
288
+ `${basePath}layout.ts`,
289
+ `${basePath}layout.tsx`,
290
+ `${basePath}layout.jsx`,
291
+ `${basePath}layout.js`,
292
+ ].includes(path));
287
293
  // Handle the case where modules are empty
288
- if (layoutModulesMap.size === 0) {
294
+ if (!isRootLayoutExists) {
289
295
  insertNodeToTree(tree, ['_'], {
290
296
  component: DefaultLayout,
291
297
  metadata: {},
@@ -315,6 +321,5 @@ export async function flatRoutes(fn) {
315
321
  catch (error) {
316
322
  console.error(error);
317
323
  throw error;
318
- // TODO: Handle error
319
324
  }
320
325
  }
@@ -183,7 +183,7 @@ export const generateRoutes = (router, isRoot = true, parentLayoutPath = undefin
183
183
  let layoutPath;
184
184
  try {
185
185
  // Check if the layout is coming from the file-based routing system
186
- if ('source' in router.layout) {
186
+ if (router.layout.source) {
187
187
  const layoutNode = router.layout;
188
188
  layoutPath = layoutNode.path;
189
189
  route = {
@@ -322,7 +322,7 @@ export const generateRoutes = (router, isRoot = true, parentLayoutPath = undefin
322
322
  // Get informations about pages
323
323
  const pages = router.pages.map((p) => {
324
324
  // Check if the page is coming from file-based routing system
325
- if ('source' in p) {
325
+ if (p.source) {
326
326
  const pageNode = p;
327
327
  // /home => home
328
328
  // / => /
@@ -474,6 +474,7 @@ export const generateRoutes = (router, isRoot = true, parentLayoutPath = undefin
474
474
  }
475
475
  catch (error) {
476
476
  console.error(error);
477
+ throw error;
477
478
  }
478
479
  finally {
479
480
  // Return the formated router