@flexireact/core 3.0.0 → 3.0.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 (56) hide show
  1. package/README.md +204 -52
  2. package/dist/cli/index.js +1514 -0
  3. package/dist/cli/index.js.map +1 -0
  4. package/dist/core/client/index.js +373 -0
  5. package/dist/core/client/index.js.map +1 -0
  6. package/dist/core/index.js +6415 -0
  7. package/dist/core/index.js.map +1 -0
  8. package/dist/core/server/index.js +3094 -0
  9. package/dist/core/server/index.js.map +1 -0
  10. package/package.json +80 -80
  11. package/bin/flexireact.js +0 -23
  12. package/cli/generators.ts +0 -616
  13. package/cli/index.ts +0 -1182
  14. package/core/actions/index.ts +0 -364
  15. package/core/api.ts +0 -143
  16. package/core/build/index.ts +0 -425
  17. package/core/cli/logger.ts +0 -353
  18. package/core/client/Link.tsx +0 -345
  19. package/core/client/hydration.ts +0 -147
  20. package/core/client/index.ts +0 -12
  21. package/core/client/islands.ts +0 -143
  22. package/core/client/navigation.ts +0 -212
  23. package/core/client/runtime.ts +0 -52
  24. package/core/config.ts +0 -116
  25. package/core/context.ts +0 -83
  26. package/core/dev.ts +0 -47
  27. package/core/devtools/index.ts +0 -644
  28. package/core/edge/cache.ts +0 -344
  29. package/core/edge/fetch-polyfill.ts +0 -247
  30. package/core/edge/handler.ts +0 -248
  31. package/core/edge/index.ts +0 -81
  32. package/core/edge/ppr.ts +0 -264
  33. package/core/edge/runtime.ts +0 -161
  34. package/core/font/index.ts +0 -306
  35. package/core/helpers.ts +0 -494
  36. package/core/image/index.ts +0 -413
  37. package/core/index.ts +0 -218
  38. package/core/islands/index.ts +0 -293
  39. package/core/loader.ts +0 -111
  40. package/core/logger.ts +0 -242
  41. package/core/metadata/index.ts +0 -622
  42. package/core/middleware/index.ts +0 -416
  43. package/core/plugins/index.ts +0 -373
  44. package/core/render/index.ts +0 -1243
  45. package/core/render.ts +0 -136
  46. package/core/router/index.ts +0 -551
  47. package/core/router.ts +0 -141
  48. package/core/rsc/index.ts +0 -199
  49. package/core/server/index.ts +0 -779
  50. package/core/server.ts +0 -203
  51. package/core/ssg/index.ts +0 -346
  52. package/core/start-dev.ts +0 -6
  53. package/core/start-prod.ts +0 -6
  54. package/core/tsconfig.json +0 -30
  55. package/core/types.ts +0 -239
  56. package/core/utils.ts +0 -176
package/core/render.ts DELETED
@@ -1,136 +0,0 @@
1
- import { renderToString } from 'react-dom/server';
2
- import React from 'react';
3
-
4
- /**
5
- * Renders a React component to a full HTML page
6
- */
7
- interface RenderOptions {
8
- title?: string;
9
- scripts?: string[];
10
- styles?: string[];
11
- }
12
-
13
- export function render(Component: React.ComponentType<any>, props: Record<string, any> = {}, options: RenderOptions = {}) {
14
- const {
15
- title = 'FlexiReact App',
16
- scripts = [],
17
- styles = []
18
- } = options;
19
-
20
- // Render the component to string
21
- const content = renderToString(React.createElement(Component, props));
22
-
23
- // Build the HTML document
24
- const html = `<!DOCTYPE html>
25
- <html lang="en">
26
- <head>
27
- <meta charset="UTF-8">
28
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
29
- <title>${escapeHtml(title)}</title>
30
- ${styles.map(href => `<link rel="stylesheet" href="${escapeHtml(href)}">`).join('\n ')}
31
- <style>
32
- * {
33
- margin: 0;
34
- padding: 0;
35
- box-sizing: border-box;
36
- }
37
- body {
38
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
39
- line-height: 1.6;
40
- color: #333;
41
- }
42
- </style>
43
- </head>
44
- <body>
45
- <div id="root">${content}</div>
46
- <script>
47
- // FlexiReact client-side runtime
48
- window.__FLEXIREACT_PROPS__ = ${JSON.stringify(props)};
49
- </script>
50
- ${scripts.map(src => `<script src="${escapeHtml(src)}"></script>`).join('\n ')}
51
- </body>
52
- </html>`;
53
-
54
- return html;
55
- }
56
-
57
- /**
58
- * Renders an error page
59
- * @param {number} statusCode - HTTP status code
60
- * @param {string} message - Error message
61
- * @returns {string} HTML error page
62
- */
63
- export function renderError(statusCode, message) {
64
- return `<!DOCTYPE html>
65
- <html lang="en">
66
- <head>
67
- <meta charset="UTF-8">
68
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
69
- <title>Error ${statusCode} - FlexiReact</title>
70
- <style>
71
- * {
72
- margin: 0;
73
- padding: 0;
74
- box-sizing: border-box;
75
- }
76
- body {
77
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
78
- display: flex;
79
- align-items: center;
80
- justify-content: center;
81
- min-height: 100vh;
82
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
83
- color: white;
84
- }
85
- .error-container {
86
- text-align: center;
87
- padding: 2rem;
88
- }
89
- .error-code {
90
- font-size: 6rem;
91
- font-weight: bold;
92
- opacity: 0.8;
93
- }
94
- .error-message {
95
- font-size: 1.5rem;
96
- margin-top: 1rem;
97
- opacity: 0.9;
98
- }
99
- .back-link {
100
- display: inline-block;
101
- margin-top: 2rem;
102
- padding: 0.75rem 1.5rem;
103
- background: rgba(255,255,255,0.2);
104
- color: white;
105
- text-decoration: none;
106
- border-radius: 5px;
107
- transition: background 0.3s;
108
- }
109
- .back-link:hover {
110
- background: rgba(255,255,255,0.3);
111
- }
112
- </style>
113
- </head>
114
- <body>
115
- <div class="error-container">
116
- <div class="error-code">${statusCode}</div>
117
- <div class="error-message">${escapeHtml(message)}</div>
118
- <a href="/" class="back-link">← Back to Home</a>
119
- </div>
120
- </body>
121
- </html>`;
122
- }
123
-
124
- /**
125
- * Escapes HTML special characters
126
- */
127
- function escapeHtml(str) {
128
- const htmlEntities = {
129
- '&': '&amp;',
130
- '<': '&lt;',
131
- '>': '&gt;',
132
- '"': '&quot;',
133
- "'": '&#39;'
134
- };
135
- return String(str).replace(/[&<>"']/g, char => htmlEntities[char]);
136
- }
@@ -1,551 +0,0 @@
1
- /**
2
- * FlexiReact Router v2
3
- * Advanced file-based routing with nested routes, loading, and error boundaries
4
- *
5
- * Supports multiple routing conventions:
6
- * - pages/ : Traditional file-based routing (index.tsx, about.tsx)
7
- * - app/ : Next.js style App Router (page.tsx, layout.tsx)
8
- * - routes/ : FlexiReact v2 routes directory (home.tsx → /, [slug].tsx → /:slug)
9
- */
10
-
11
- import fs from 'fs';
12
- import path from 'path';
13
- import { isServerComponent, isClientComponent, isIsland } from '../utils.js';
14
-
15
- /**
16
- * Route types
17
- */
18
- export const RouteType = {
19
- PAGE: 'page',
20
- API: 'api',
21
- LAYOUT: 'layout',
22
- LOADING: 'loading',
23
- ERROR: 'error',
24
- NOT_FOUND: 'not-found'
25
- };
26
-
27
- /**
28
- * Builds the complete route tree from all routing directories
29
- */
30
- export function buildRouteTree(pagesDir, layoutsDir, appDir = null, routesDir = null) {
31
- const projectRoot = path.dirname(pagesDir);
32
-
33
- const routes: {
34
- pages: any[];
35
- api: any[];
36
- layouts: Map<any, any>;
37
- tree: Record<string, any>;
38
- appRoutes: any[];
39
- flexiRoutes: any[];
40
- rootLayout?: string;
41
- } = {
42
- pages: [],
43
- api: [],
44
- layouts: new Map(),
45
- tree: {},
46
- appRoutes: [], // Next.js style app router routes
47
- flexiRoutes: [] // FlexiReact v2 routes/ directory
48
- };
49
-
50
- // 1. Scan routes/ directory (FlexiReact v2 - priority)
51
- const routesDirPath = routesDir || path.join(projectRoot, 'routes');
52
- if (fs.existsSync(routesDirPath)) {
53
- scanRoutesDirectory(routesDirPath, routesDirPath, routes);
54
- }
55
-
56
- // 2. Scan app/ directory (Next.js style App Router)
57
- const appDirPath = appDir || path.join(projectRoot, 'app');
58
- if (fs.existsSync(appDirPath)) {
59
- scanAppDirectory(appDirPath, appDirPath, routes);
60
- }
61
-
62
- // 3. Scan pages/ directory (traditional routing - fallback)
63
- if (fs.existsSync(pagesDir)) {
64
- scanDirectory(pagesDir, pagesDir, routes);
65
- }
66
-
67
- // 4. Scan layouts/ directory
68
- if (fs.existsSync(layoutsDir)) {
69
- scanLayouts(layoutsDir, routes.layouts);
70
- }
71
-
72
- // 5. Check for root layout in app/ directory
73
- const rootLayoutPath = path.join(appDirPath, 'layout.tsx');
74
- const rootLayoutPathJs = path.join(appDirPath, 'layout.jsx');
75
- if (fs.existsSync(rootLayoutPath)) {
76
- routes.rootLayout = rootLayoutPath;
77
- } else if (fs.existsSync(rootLayoutPathJs)) {
78
- routes.rootLayout = rootLayoutPathJs;
79
- }
80
-
81
- // Build route tree for nested routes
82
- routes.tree = buildTree([...routes.flexiRoutes, ...routes.appRoutes, ...routes.pages]);
83
-
84
- return routes;
85
- }
86
-
87
- /**
88
- * Scans routes/ directory for FlexiReact v2 style routing
89
- *
90
- * Convention:
91
- * - home.tsx → /
92
- * - about.tsx → /about
93
- * - blog/index.tsx → /blog
94
- * - blog/[slug].tsx → /blog/:slug
95
- * - (public)/home.tsx → / (route group, not in URL)
96
- * - api/hello.ts → /api/hello (API route)
97
- * - dashboard/layout.tsx → layout for /dashboard/*
98
- */
99
- function scanRoutesDirectory(baseDir, currentDir, routes, parentSegments = [], parentLayout = null, parentMiddleware = null) {
100
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
101
-
102
- // Find special files in current directory
103
- let layoutFile = null;
104
- let loadingFile = null;
105
- let errorFile = null;
106
- let middlewareFile = null;
107
-
108
- for (const entry of entries) {
109
- if (entry.isFile()) {
110
- const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, '');
111
- const fullPath = path.join(currentDir, entry.name);
112
- const ext = path.extname(entry.name);
113
-
114
- // Special files
115
- if (name === 'layout') layoutFile = fullPath;
116
- if (name === 'loading') loadingFile = fullPath;
117
- if (name === 'error') errorFile = fullPath;
118
- if (name === '_middleware' || name === 'middleware') middlewareFile = fullPath;
119
-
120
- // Skip special files and non-route files
121
- if (['layout', 'loading', 'error', 'not-found', '_middleware', 'middleware'].includes(name)) continue;
122
- if (!['.tsx', '.jsx', '.ts', '.js'].includes(ext)) continue;
123
-
124
- // API routes (in api/ folder or .ts/.js files in api/)
125
- const relativePath = path.relative(baseDir, currentDir);
126
- const isApiRoute = relativePath.startsWith('api') || relativePath.startsWith('api/');
127
-
128
- if (isApiRoute && ['.ts', '.js'].includes(ext)) {
129
- const apiPath = '/' + [...parentSegments, name === 'index' ? '' : name].filter(Boolean).join('/');
130
- routes.api.push({
131
- type: RouteType.API,
132
- path: apiPath.replace(/\/+/g, '/') || '/',
133
- filePath: fullPath,
134
- pattern: createRoutePattern(apiPath),
135
- segments: [...parentSegments, name === 'index' ? '' : name].filter(Boolean)
136
- });
137
- continue;
138
- }
139
-
140
- // Page routes
141
- if (['.tsx', '.jsx'].includes(ext)) {
142
- let routePath;
143
-
144
- // home.tsx → /
145
- if (name === 'home' && parentSegments.length === 0) {
146
- routePath = '/';
147
- }
148
- // index.tsx → parent path
149
- else if (name === 'index') {
150
- routePath = '/' + parentSegments.join('/') || '/';
151
- }
152
- // [param].tsx → /:param
153
- else if (name.startsWith('[') && name.endsWith(']')) {
154
- const paramName = name.slice(1, -1);
155
- // Handle catch-all [...slug]
156
- if (paramName.startsWith('...')) {
157
- routePath = '/' + [...parentSegments, '*' + paramName.slice(3)].join('/');
158
- } else {
159
- routePath = '/' + [...parentSegments, ':' + paramName].join('/');
160
- }
161
- }
162
- // regular.tsx → /regular
163
- else {
164
- routePath = '/' + [...parentSegments, name].join('/');
165
- }
166
-
167
- routes.flexiRoutes.push({
168
- type: RouteType.PAGE,
169
- path: routePath.replace(/\/+/g, '/'),
170
- filePath: fullPath,
171
- pattern: createRoutePattern(routePath),
172
- segments: routePath.split('/').filter(Boolean),
173
- layout: layoutFile || parentLayout,
174
- loading: loadingFile,
175
- error: errorFile,
176
- middleware: middlewareFile || parentMiddleware,
177
- isFlexiRouter: true,
178
- isServerComponent: isServerComponent(fullPath),
179
- isClientComponent: isClientComponent(fullPath),
180
- isIsland: isIsland(fullPath)
181
- });
182
- }
183
- }
184
- }
185
-
186
- // Recursively scan subdirectories
187
- for (const entry of entries) {
188
- if (entry.isDirectory()) {
189
- const fullPath = path.join(currentDir, entry.name);
190
- const dirName = entry.name;
191
-
192
- // Skip special directories
193
- if (dirName.startsWith('_') || dirName.startsWith('.')) continue;
194
-
195
- // Handle route groups (parentheses) - don't add to URL
196
- const isGroup = dirName.startsWith('(') && dirName.endsWith(')');
197
-
198
- // Handle dynamic segments [param]
199
- let segmentName = dirName;
200
- if (dirName.startsWith('[') && dirName.endsWith(']')) {
201
- const paramName = dirName.slice(1, -1);
202
- if (paramName.startsWith('...')) {
203
- segmentName = '*' + paramName.slice(3);
204
- } else {
205
- segmentName = ':' + paramName;
206
- }
207
- }
208
-
209
- const newSegments = isGroup ? parentSegments : [...parentSegments, segmentName];
210
- const newLayout = layoutFile || parentLayout;
211
- const newMiddleware = middlewareFile || parentMiddleware;
212
-
213
- scanRoutesDirectory(baseDir, fullPath, routes, newSegments, newLayout, newMiddleware);
214
- }
215
- }
216
- }
217
-
218
- /**
219
- * Scans app directory for Next.js style routing
220
- * Supports: page.tsx, layout.tsx, loading.tsx, error.tsx, not-found.tsx
221
- */
222
- function scanAppDirectory(baseDir, currentDir, routes, parentSegments = [], parentLayout = null, parentMiddleware = null) {
223
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
224
-
225
- // Find special files in current directory
226
- const specialFiles: Record<string, string | null> = {
227
- page: null,
228
- layout: null,
229
- loading: null,
230
- error: null,
231
- notFound: null,
232
- template: null,
233
- middleware: null
234
- };
235
-
236
- for (const entry of entries) {
237
- if (entry.isFile()) {
238
- const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, '');
239
- const fullPath = path.join(currentDir, entry.name);
240
-
241
- if (name === 'page') specialFiles.page = fullPath;
242
- if (name === 'layout') specialFiles.layout = fullPath;
243
- if (name === 'loading') specialFiles.loading = fullPath;
244
- if (name === 'error') specialFiles.error = fullPath;
245
- if (name === 'not-found') specialFiles.notFound = fullPath;
246
- if (name === 'template') specialFiles.template = fullPath;
247
- if (name === 'middleware' || name === '_middleware') specialFiles.middleware = fullPath;
248
- }
249
- }
250
-
251
- // If there's a page.tsx, create a route
252
- if (specialFiles.page) {
253
- const routePath = '/' + parentSegments.join('/') || '/';
254
-
255
- routes.appRoutes.push({
256
- type: RouteType.PAGE,
257
- path: routePath.replace(/\/+/g, '/'),
258
- filePath: specialFiles.page,
259
- pattern: createRoutePattern(routePath),
260
- segments: parentSegments,
261
- layout: specialFiles.layout || parentLayout,
262
- loading: specialFiles.loading,
263
- error: specialFiles.error,
264
- notFound: specialFiles.notFound,
265
- template: specialFiles.template,
266
- middleware: specialFiles.middleware || parentMiddleware,
267
- isAppRouter: true,
268
- isServerComponent: isServerComponent(specialFiles.page),
269
- isClientComponent: isClientComponent(specialFiles.page),
270
- isIsland: isIsland(specialFiles.page)
271
- });
272
- }
273
-
274
- // Recursively scan subdirectories
275
- for (const entry of entries) {
276
- if (entry.isDirectory()) {
277
- const fullPath = path.join(currentDir, entry.name);
278
-
279
- // Handle route groups (parentheses) - don't add to URL
280
- const isGroup = entry.name.startsWith('(') && entry.name.endsWith(')');
281
-
282
- // Handle dynamic segments [param]
283
- let segmentName = entry.name;
284
- if (entry.name.startsWith('[') && entry.name.endsWith(']')) {
285
- // Convert [param] to :param
286
- segmentName = ':' + entry.name.slice(1, -1);
287
- // Handle catch-all [...param]
288
- if (entry.name.startsWith('[...')) {
289
- segmentName = '*' + entry.name.slice(4, -1);
290
- }
291
- // Handle optional catch-all [[...param]]
292
- if (entry.name.startsWith('[[...')) {
293
- segmentName = '*' + entry.name.slice(5, -2);
294
- }
295
- }
296
-
297
- const newSegments = isGroup ? parentSegments : [...parentSegments, segmentName];
298
- const newLayout = specialFiles.layout || parentLayout;
299
- const newMiddleware = specialFiles.middleware || parentMiddleware;
300
-
301
- scanAppDirectory(baseDir, fullPath, routes, newSegments, newLayout, newMiddleware);
302
- }
303
- }
304
- }
305
-
306
- /**
307
- * Scans directory recursively for route files
308
- */
309
- function scanDirectory(baseDir, currentDir, routes, parentSegments = []) {
310
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
311
-
312
- // First, find special files in current directory
313
- const specialFiles = {
314
- layout: null,
315
- loading: null,
316
- error: null,
317
- notFound: null
318
- };
319
-
320
- for (const entry of entries) {
321
- if (entry.isFile()) {
322
- const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, '');
323
- const fullPath = path.join(currentDir, entry.name);
324
-
325
- if (name === 'layout') specialFiles.layout = fullPath;
326
- if (name === 'loading') specialFiles.loading = fullPath;
327
- if (name === 'error') specialFiles.error = fullPath;
328
- if (name === 'not-found' || name === '404') specialFiles.notFound = fullPath;
329
- }
330
- }
331
-
332
- for (const entry of entries) {
333
- const fullPath = path.join(currentDir, entry.name);
334
- const relativePath = path.relative(baseDir, fullPath);
335
-
336
- if (entry.isDirectory()) {
337
- // Handle route groups (parentheses)
338
- const isGroup = entry.name.startsWith('(') && entry.name.endsWith(')');
339
- const newSegments = isGroup ? parentSegments : [...parentSegments, entry.name];
340
-
341
- scanDirectory(baseDir, fullPath, routes, newSegments);
342
- } else if (entry.isFile()) {
343
- const ext = path.extname(entry.name);
344
- const baseName = path.basename(entry.name, ext);
345
-
346
- // Skip special files (already processed)
347
- if (['layout', 'loading', 'error', 'not-found', '404'].includes(baseName)) {
348
- continue;
349
- }
350
-
351
- if (['.jsx', '.js', '.tsx', '.ts'].includes(ext)) {
352
- const isApi = relativePath.startsWith('api' + path.sep) || relativePath.startsWith('api/');
353
-
354
- if (isApi && ['.js', '.ts'].includes(ext)) {
355
- routes.api.push(createRoute(fullPath, baseDir, specialFiles, RouteType.API));
356
- } else if (!isApi && ['.jsx', '.tsx'].includes(ext)) {
357
- routes.pages.push(createRoute(fullPath, baseDir, specialFiles, RouteType.PAGE));
358
- }
359
- }
360
- }
361
- }
362
- }
363
-
364
- /**
365
- * Creates a route object from file path
366
- */
367
- function createRoute(filePath, baseDir, specialFiles, type) {
368
- const relativePath = path.relative(baseDir, filePath);
369
- const routePath = filePathToRoute(relativePath);
370
-
371
- return {
372
- type,
373
- path: routePath,
374
- filePath,
375
- pattern: createRoutePattern(routePath),
376
- segments: routePath.split('/').filter(Boolean),
377
- layout: specialFiles.layout,
378
- loading: specialFiles.loading,
379
- error: specialFiles.error,
380
- notFound: specialFiles.notFound,
381
- isServerComponent: isServerComponent(filePath),
382
- isClientComponent: isClientComponent(filePath),
383
- isIsland: isIsland(filePath)
384
- };
385
- }
386
-
387
- /**
388
- * Scans layouts directory
389
- */
390
- function scanLayouts(layoutsDir, layoutsMap) {
391
- const entries = fs.readdirSync(layoutsDir, { withFileTypes: true });
392
-
393
- for (const entry of entries) {
394
- if (entry.isFile() && /\.(jsx|tsx)$/.test(entry.name)) {
395
- const name = entry.name.replace(/\.(jsx|tsx)$/, '');
396
- layoutsMap.set(name, path.join(layoutsDir, entry.name));
397
- }
398
- }
399
- }
400
-
401
- /**
402
- * Converts file path to route path
403
- */
404
- function filePathToRoute(filePath) {
405
- let route = filePath.replace(/\\/g, '/');
406
-
407
- // Remove extension
408
- route = route.replace(/\.(jsx|js|tsx|ts)$/, '');
409
-
410
- // Convert [param] to :param
411
- route = route.replace(/\[\.\.\.([^\]]+)\]/g, '*$1'); // Catch-all [...slug]
412
- route = route.replace(/\[([^\]]+)\]/g, ':$1');
413
-
414
- // Handle index files
415
- if (route.endsWith('/index')) {
416
- route = route.slice(0, -6) || '/';
417
- } else if (route === 'index') {
418
- route = '/';
419
- }
420
-
421
- // Handle route groups - remove (groupName) from path
422
- route = route.replace(/\/?\([^)]+\)\/?/g, '/');
423
-
424
- // Ensure leading slash and clean up
425
- if (!route.startsWith('/')) {
426
- route = '/' + route;
427
- }
428
- route = route.replace(/\/+/g, '/');
429
-
430
- return route;
431
- }
432
-
433
- /**
434
- * Creates regex pattern for route matching
435
- */
436
- function createRoutePattern(routePath) {
437
- let pattern = routePath
438
- .replace(/\*[^/]*/g, '(.*)') // Catch-all
439
- .replace(/:[^/]+/g, '([^/]+)') // Dynamic segments
440
- .replace(/\//g, '\\/');
441
-
442
- return new RegExp(`^${pattern}$`);
443
- }
444
-
445
- /**
446
- * Builds a tree structure for nested routes
447
- */
448
- function buildTree(routes) {
449
- const tree = { children: {}, routes: [] };
450
-
451
- for (const route of routes) {
452
- let current = tree;
453
-
454
- for (const segment of route.segments) {
455
- if (!current.children[segment]) {
456
- current.children[segment] = { children: {}, routes: [] };
457
- }
458
- current = current.children[segment];
459
- }
460
-
461
- current.routes.push(route);
462
- }
463
-
464
- return tree;
465
- }
466
-
467
- /**
468
- * Matches URL path against routes
469
- */
470
- export function matchRoute(urlPath, routes) {
471
- const normalizedPath = urlPath === '' ? '/' : urlPath.split('?')[0];
472
-
473
- for (const route of routes) {
474
- const match = normalizedPath.match(route.pattern);
475
-
476
- if (match) {
477
- const params = extractParams(route.path, match);
478
- return { ...route, params };
479
- }
480
- }
481
-
482
- return null;
483
- }
484
-
485
- /**
486
- * Extracts parameters from route match
487
- */
488
- function extractParams(routePath, match) {
489
- const params = {};
490
- const paramNames = [];
491
-
492
- // Extract param names from route path
493
- const paramRegex = /:([^/]+)|\*([^/]*)/g;
494
- let paramMatch;
495
-
496
- while ((paramMatch = paramRegex.exec(routePath)) !== null) {
497
- paramNames.push(paramMatch[1] || paramMatch[2] || 'splat');
498
- }
499
-
500
- paramNames.forEach((name, index) => {
501
- params[name] = match[index + 1];
502
- });
503
-
504
- return params;
505
- }
506
-
507
- /**
508
- * Finds all layouts that apply to a route
509
- */
510
- export function findRouteLayouts(route, layoutsMap) {
511
- const layouts = [];
512
-
513
- // Check for segment-based layouts
514
- let currentPath = '';
515
- for (const segment of route.segments) {
516
- currentPath += '/' + segment;
517
- const layoutName = segment;
518
-
519
- if (layoutsMap.has(layoutName)) {
520
- layouts.push({
521
- name: layoutName,
522
- filePath: layoutsMap.get(layoutName)
523
- });
524
- }
525
- }
526
-
527
- // Check for route-specific layout
528
- if (route.layout) {
529
- layouts.push({
530
- name: 'route',
531
- filePath: route.layout
532
- });
533
- }
534
-
535
- // Check for root layout
536
- if (layoutsMap.has('root')) {
537
- layouts.unshift({
538
- name: 'root',
539
- filePath: layoutsMap.get('root')
540
- });
541
- }
542
-
543
- return layouts;
544
- }
545
-
546
- export default {
547
- buildRouteTree,
548
- matchRoute,
549
- findRouteLayouts,
550
- RouteType
551
- };