@flexireact/core 3.0.1 → 3.0.3

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 (55) hide show
  1. package/dist/cli/index.js +1514 -0
  2. package/dist/cli/index.js.map +1 -0
  3. package/dist/core/client/index.js +373 -0
  4. package/dist/core/client/index.js.map +1 -0
  5. package/dist/core/index.js +6415 -0
  6. package/dist/core/index.js.map +1 -0
  7. package/dist/core/server/index.js +3094 -0
  8. package/dist/core/server/index.js.map +1 -0
  9. package/package.json +80 -80
  10. package/bin/flexireact.js +0 -23
  11. package/cli/generators.ts +0 -616
  12. package/cli/index.ts +0 -1182
  13. package/core/actions/index.ts +0 -364
  14. package/core/api.ts +0 -143
  15. package/core/build/index.ts +0 -425
  16. package/core/cli/logger.ts +0 -353
  17. package/core/client/Link.tsx +0 -345
  18. package/core/client/hydration.ts +0 -147
  19. package/core/client/index.ts +0 -12
  20. package/core/client/islands.ts +0 -143
  21. package/core/client/navigation.ts +0 -212
  22. package/core/client/runtime.ts +0 -52
  23. package/core/config.ts +0 -116
  24. package/core/context.ts +0 -83
  25. package/core/dev.ts +0 -47
  26. package/core/devtools/index.ts +0 -644
  27. package/core/edge/cache.ts +0 -344
  28. package/core/edge/fetch-polyfill.ts +0 -247
  29. package/core/edge/handler.ts +0 -248
  30. package/core/edge/index.ts +0 -81
  31. package/core/edge/ppr.ts +0 -264
  32. package/core/edge/runtime.ts +0 -161
  33. package/core/font/index.ts +0 -306
  34. package/core/helpers.ts +0 -494
  35. package/core/image/index.ts +0 -413
  36. package/core/index.ts +0 -218
  37. package/core/islands/index.ts +0 -293
  38. package/core/loader.ts +0 -111
  39. package/core/logger.ts +0 -242
  40. package/core/metadata/index.ts +0 -622
  41. package/core/middleware/index.ts +0 -416
  42. package/core/plugins/index.ts +0 -373
  43. package/core/render/index.ts +0 -1243
  44. package/core/render.ts +0 -136
  45. package/core/router/index.ts +0 -551
  46. package/core/router.ts +0 -141
  47. package/core/rsc/index.ts +0 -199
  48. package/core/server/index.ts +0 -779
  49. package/core/server.ts +0 -203
  50. package/core/ssg/index.ts +0 -346
  51. package/core/start-dev.ts +0 -6
  52. package/core/start-prod.ts +0 -6
  53. package/core/tsconfig.json +0 -30
  54. package/core/types.ts +0 -239
  55. 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
- };