@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
@@ -1,779 +0,0 @@
1
- /**
2
- * FlexiReact Server v2
3
- * Production-ready server with SSR, RSC, Islands, and more
4
- */
5
-
6
- import http from 'http';
7
- import fs from 'fs';
8
- import path from 'path';
9
- import { fileURLToPath, pathToFileURL } from 'url';
10
- import { loadConfig, resolvePaths } from '../config.js';
11
- import { buildRouteTree, matchRoute, findRouteLayouts } from '../router/index.js';
12
- import { renderPage, renderError, renderLoading } from '../render/index.js';
13
- import { loadMiddleware, runMiddleware } from '../middleware/index.js';
14
- import { loadPlugins, pluginManager, PluginHooks } from '../plugins/index.js';
15
- import { getRegisteredIslands, generateAdvancedHydrationScript } from '../islands/index.js';
16
- import { createRequestContext, RequestContext, RouteContext } from '../context.js';
17
- import { logger } from '../logger.js';
18
- import { RedirectError, NotFoundError } from '../helpers.js';
19
- import { executeAction, deserializeArgs } from '../actions/index.js';
20
- import { handleImageOptimization } from '../image/index.js';
21
- import { handleFontRequest } from '../font/index.js';
22
- import React from 'react';
23
-
24
- const __filename = fileURLToPath(import.meta.url);
25
- const __dirname = path.dirname(__filename);
26
-
27
- // MIME types
28
- const MIME_TYPES = {
29
- '.html': 'text/html',
30
- '.css': 'text/css',
31
- '.js': 'application/javascript',
32
- '.mjs': 'application/javascript',
33
- '.json': 'application/json',
34
- '.png': 'image/png',
35
- '.jpg': 'image/jpeg',
36
- '.jpeg': 'image/jpeg',
37
- '.gif': 'image/gif',
38
- '.svg': 'image/svg+xml',
39
- '.ico': 'image/x-icon',
40
- '.woff': 'font/woff',
41
- '.woff2': 'font/woff2',
42
- '.ttf': 'font/ttf',
43
- '.webp': 'image/webp',
44
- '.mp4': 'video/mp4',
45
- '.webm': 'video/webm'
46
- };
47
-
48
- /**
49
- * Creates the FlexiReact server
50
- */
51
- interface CreateServerOptions {
52
- projectRoot?: string;
53
- mode?: 'development' | 'production';
54
- port?: number;
55
- host?: string;
56
- }
57
-
58
- export async function createServer(options: CreateServerOptions = {}) {
59
- const serverStartTime = Date.now();
60
- const projectRoot = options.projectRoot || process.cwd();
61
- const isDev = options.mode === 'development';
62
-
63
- // Show logo
64
- logger.logo();
65
-
66
- // Load configuration
67
- const rawConfig = await loadConfig(projectRoot);
68
- const config = resolvePaths(rawConfig, projectRoot);
69
-
70
- // Load plugins
71
- await loadPlugins(projectRoot, config);
72
-
73
- // Run config hook
74
- await pluginManager.runHook(PluginHooks.CONFIG, config);
75
-
76
- // Load middleware
77
- const middleware = await loadMiddleware(projectRoot);
78
-
79
- // Build routes
80
- let routes = buildRouteTree(config.pagesDir, config.layoutsDir);
81
-
82
- // Run routes loaded hook
83
- await pluginManager.runHook(PluginHooks.ROUTES_LOADED, routes);
84
-
85
- // Create module loader with cache busting for dev
86
- const loadModule = createModuleLoader(isDev);
87
-
88
- // Create HTTP server
89
- const server = http.createServer(async (req, res) => {
90
- const startTime = Date.now();
91
-
92
- // Parse URL early so it's available in finally block
93
- const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
94
- const pathname = url.pathname;
95
-
96
- try {
97
-
98
- // Run request hook
99
- await pluginManager.runHook(PluginHooks.REQUEST, req, res);
100
-
101
- // Run middleware
102
- const middlewareResult = await runMiddleware(req, res, middleware);
103
- if (!middlewareResult.continue) {
104
- return;
105
- }
106
-
107
- // Handle rewritten URL
108
- const effectivePath = middlewareResult.rewritten
109
- ? new URL(req.url, `http://${req.headers.host}`).pathname
110
- : pathname;
111
-
112
- // Serve static files from public directory
113
- if (await serveStaticFile(res, config.publicDir, effectivePath)) {
114
- return;
115
- }
116
-
117
- // Serve built assets in production
118
- if (!isDev && effectivePath.startsWith('/_flexi/')) {
119
- const assetPath = path.join(config.outDir, 'client', effectivePath.slice(8));
120
- if (await serveStaticFile(res, path.dirname(assetPath), path.basename(assetPath))) {
121
- return;
122
- }
123
- }
124
-
125
- // Serve client components (for hydration)
126
- if (effectivePath.startsWith('/_flexi/component/')) {
127
- const componentName = effectivePath.slice(18).replace('.js', '');
128
- return await serveClientComponent(res, config.pagesDir, componentName);
129
- }
130
-
131
- // Handle server actions
132
- if (effectivePath === '/_flexi/action' && req.method === 'POST') {
133
- return await handleServerAction(req, res);
134
- }
135
-
136
- // Handle image optimization
137
- if (effectivePath.startsWith('/_flexi/image')) {
138
- return await handleImageOptimization(req, res, config.images || {});
139
- }
140
-
141
- // Handle font requests
142
- if (effectivePath.startsWith('/_flexi/font')) {
143
- return await handleFontRequest(req, res);
144
- }
145
-
146
- // Rebuild routes in dev mode for hot reload
147
- if (isDev) {
148
- routes = buildRouteTree(config.pagesDir, config.layoutsDir);
149
- }
150
-
151
- // Match API routes
152
- const apiRoute = matchRoute(effectivePath, routes.api);
153
- if (apiRoute) {
154
- return await handleApiRoute(req, res, apiRoute, loadModule);
155
- }
156
-
157
- // Match FlexiReact v2 routes (routes/ directory - priority)
158
- const flexiRoute = matchRoute(effectivePath, routes.flexiRoutes || []);
159
- if (flexiRoute) {
160
- return await handlePageRoute(req, res, flexiRoute, routes, config, loadModule, url);
161
- }
162
-
163
- // Match app routes (app/ directory - Next.js style)
164
- const appRoute = matchRoute(effectivePath, routes.appRoutes || []);
165
- if (appRoute) {
166
- return await handlePageRoute(req, res, appRoute, routes, config, loadModule, url);
167
- }
168
-
169
- // Match page routes (pages/ directory - legacy fallback)
170
- const pageRoute = matchRoute(effectivePath, routes.pages);
171
- if (pageRoute) {
172
- return await handlePageRoute(req, res, pageRoute, routes, config, loadModule, url);
173
- }
174
-
175
- // 404 Not Found
176
- res.writeHead(404, { 'Content-Type': 'text/html' });
177
- res.end(renderError(404, 'Page not found'));
178
-
179
- } catch (error: any) {
180
- // Handle redirect() calls
181
- if (error instanceof RedirectError) {
182
- res.writeHead(error.statusCode, { 'Location': error.url });
183
- res.end();
184
- return;
185
- }
186
-
187
- // Handle notFound() calls
188
- if (error instanceof NotFoundError) {
189
- res.writeHead(404, { 'Content-Type': 'text/html' });
190
- res.end(renderError(404, error.message));
191
- return;
192
- }
193
-
194
- console.error('Server Error:', error);
195
-
196
- if (!res.headersSent) {
197
- res.writeHead(500, { 'Content-Type': 'text/html' });
198
- res.end(renderError(500, error.message, isDev ? error.stack : null));
199
- }
200
- } finally {
201
- const duration = Date.now() - startTime;
202
- if (isDev) {
203
- // Determine route type for logging
204
- const routeType = pathname.startsWith('/api/') ? 'api' :
205
- pathname.startsWith('/_flexi/') ? 'asset' :
206
- pathname.match(/\.(js|css|png|jpg|svg|ico)$/) ? 'asset' : 'dynamic';
207
- logger.request(req.method, pathname, res.statusCode, duration, { type: routeType });
208
- }
209
-
210
- // Run response hook
211
- await pluginManager.runHook(PluginHooks.RESPONSE, req, res, duration);
212
- }
213
- });
214
-
215
- // Start server
216
- const port = process.env.PORT || options.port || config.server.port;
217
- const host = options.host || config.server.host;
218
-
219
- return new Promise((resolve, reject) => {
220
- // Handle port in use error
221
- server.on('error', (err: NodeJS.ErrnoException) => {
222
- if (err.code === 'EADDRINUSE') {
223
- logger.portInUse(port);
224
- process.exit(1);
225
- } else {
226
- logger.error('Server error', err);
227
- reject(err);
228
- }
229
- });
230
-
231
- server.listen(port, host, async () => {
232
- // Show startup info with styled logger
233
- logger.serverStart({
234
- port,
235
- host,
236
- mode: isDev ? 'development' : 'production',
237
- pagesDir: config.pagesDir,
238
- islands: config.islands?.enabled,
239
- rsc: config.rsc?.enabled
240
- }, serverStartTime);
241
-
242
- // Run server start hook
243
- await pluginManager.runHook(PluginHooks.SERVER_START, server);
244
-
245
- resolve(server);
246
- });
247
- });
248
- }
249
-
250
- /**
251
- * Creates a module loader with optional cache busting
252
- */
253
- function createModuleLoader(isDev) {
254
- return async (filePath) => {
255
- const url = pathToFileURL(filePath).href;
256
- const cacheBuster = isDev ? `?t=${Date.now()}` : '';
257
- return import(`${url}${cacheBuster}`);
258
- };
259
- }
260
-
261
- /**
262
- * Serves static files
263
- */
264
- async function serveStaticFile(res, baseDir, pathname) {
265
- // Prevent directory traversal
266
- const safePath = path.normalize(pathname).replace(/^(\.\.[\/\\])+/, '');
267
- const filePath = path.join(baseDir, safePath);
268
-
269
- // Check if file exists and is within base directory
270
- if (!filePath.startsWith(baseDir) || !fs.existsSync(filePath)) {
271
- return false;
272
- }
273
-
274
- const stat = fs.statSync(filePath);
275
- if (!stat.isFile()) {
276
- return false;
277
- }
278
-
279
- const ext = path.extname(filePath).toLowerCase();
280
- const contentType = MIME_TYPES[ext] || 'application/octet-stream';
281
-
282
- res.writeHead(200, {
283
- 'Content-Type': contentType,
284
- 'Content-Length': stat.size,
285
- 'Cache-Control': 'public, max-age=31536000'
286
- });
287
-
288
- fs.createReadStream(filePath).pipe(res);
289
- return true;
290
- }
291
-
292
- /**
293
- * Handles API route requests
294
- */
295
- async function handleApiRoute(req, res, route, loadModule) {
296
- try {
297
- const module = await loadModule(route.filePath);
298
- const method = req.method.toLowerCase();
299
-
300
- // Parse request body
301
- const body = await parseBody(req);
302
-
303
- // Parse query
304
- const url = new URL(req.url, `http://${req.headers.host}`);
305
- const query = Object.fromEntries(url.searchParams);
306
-
307
- // Enhanced request
308
- const enhancedReq = {
309
- ...req,
310
- body,
311
- query,
312
- params: route.params,
313
- method: req.method
314
- };
315
-
316
- // Enhanced response
317
- const enhancedRes = createApiResponse(res);
318
-
319
- // Find handler (check both lowercase and uppercase method names)
320
- const handler = module[method] || module[method.toUpperCase()] || module.default;
321
-
322
- if (!handler) {
323
- enhancedRes.status(405).json({ error: 'Method not allowed' });
324
- return;
325
- }
326
-
327
- await handler(enhancedReq, enhancedRes);
328
-
329
- } catch (error) {
330
- console.error('API Error:', error);
331
- if (!res.headersSent) {
332
- res.writeHead(500, { 'Content-Type': 'application/json' });
333
- res.end(JSON.stringify({ error: 'Internal Server Error' }));
334
- }
335
- }
336
- }
337
-
338
- /**
339
- * Handles server action requests
340
- */
341
- async function handleServerAction(req, res) {
342
- try {
343
- // Parse request body
344
- const body: any = await parseBody(req);
345
- const { actionId, args } = body;
346
-
347
- if (!actionId) {
348
- res.writeHead(400, { 'Content-Type': 'application/json' });
349
- res.end(JSON.stringify({ success: false, error: 'Missing actionId' }));
350
- return;
351
- }
352
-
353
- // Deserialize arguments
354
- const deserializedArgs = deserializeArgs(args || []);
355
-
356
- // Execute the action
357
- const result = await executeAction(actionId, deserializedArgs, {
358
- request: new Request(`http://${req.headers.host}${req.url}`, {
359
- method: req.method,
360
- headers: req.headers as any
361
- })
362
- });
363
-
364
- // Send response
365
- res.writeHead(200, {
366
- 'Content-Type': 'application/json',
367
- 'X-Flexi-Action': actionId
368
- });
369
- res.end(JSON.stringify(result));
370
-
371
- } catch (error: any) {
372
- console.error('Server Action Error:', error);
373
- res.writeHead(500, { 'Content-Type': 'application/json' });
374
- res.end(JSON.stringify({
375
- success: false,
376
- error: error.message || 'Action execution failed'
377
- }));
378
- }
379
- }
380
-
381
- /**
382
- * Creates an enhanced API response object
383
- */
384
- function createApiResponse(res) {
385
- return {
386
- _res: res,
387
- _status: 200,
388
- _headers: {},
389
-
390
- status(code) {
391
- this._status = code;
392
- return this;
393
- },
394
-
395
- setHeader(name, value) {
396
- this._headers[name] = value;
397
- return this;
398
- },
399
-
400
- json(data) {
401
- this._headers['Content-Type'] = 'application/json';
402
- this._send(JSON.stringify(data));
403
- },
404
-
405
- send(data) {
406
- if (typeof data === 'object') {
407
- this.json(data);
408
- } else {
409
- this._headers['Content-Type'] = this._headers['Content-Type'] || 'text/plain';
410
- this._send(String(data));
411
- }
412
- },
413
-
414
- html(data) {
415
- this._headers['Content-Type'] = 'text/html';
416
- this._send(data);
417
- },
418
-
419
- redirect(url, status = 302) {
420
- this._status = status;
421
- this._headers['Location'] = url;
422
- this._send('');
423
- },
424
-
425
- _send(body) {
426
- if (!this._res.headersSent) {
427
- this._res.writeHead(this._status, this._headers);
428
- this._res.end(body);
429
- }
430
- }
431
- };
432
- }
433
-
434
- /**
435
- * Handles page route requests with SSR
436
- */
437
- async function handlePageRoute(req, res, route, routes, config, loadModule, url) {
438
- try {
439
- // Run route-specific middleware if exists
440
- if (route.middleware) {
441
- try {
442
- const middlewareModule = await loadModule(route.middleware);
443
- const middlewareFn = middlewareModule.default || middlewareModule.middleware;
444
-
445
- if (typeof middlewareFn === 'function') {
446
- const result = await middlewareFn(req, res, { route, params: route.params });
447
-
448
- // If middleware returns a response, use it
449
- if (result?.redirect) {
450
- res.writeHead(result.statusCode || 307, { 'Location': result.redirect });
451
- res.end();
452
- return;
453
- }
454
-
455
- if (result?.rewrite) {
456
- // Rewrite to different path
457
- req.url = result.rewrite;
458
- }
459
-
460
- if (result === false || result?.stop) {
461
- // Middleware stopped the request
462
- return;
463
- }
464
- }
465
- } catch (middlewareError: any) {
466
- console.error('Route middleware error:', middlewareError.message);
467
- }
468
- }
469
-
470
- // Load page module
471
- const pageModule = await loadModule(route.filePath);
472
- const Component = pageModule.default;
473
-
474
- if (!Component) {
475
- throw new Error(`No default export in ${route.filePath}`);
476
- }
477
-
478
- // Create request context
479
- const query = Object.fromEntries(url.searchParams);
480
- const context = createRequestContext(req, res, route.params, query);
481
-
482
- // Get page props
483
- let props = { params: route.params, query };
484
-
485
- // Handle getServerSideProps
486
- if (pageModule.getServerSideProps) {
487
- const result = await pageModule.getServerSideProps({
488
- params: route.params,
489
- query,
490
- req,
491
- res
492
- });
493
-
494
- if (result.redirect) {
495
- res.writeHead(result.redirect.statusCode || 302, {
496
- Location: result.redirect.destination
497
- });
498
- res.end();
499
- return;
500
- }
501
-
502
- if (result.notFound) {
503
- res.writeHead(404, { 'Content-Type': 'text/html' });
504
- res.end(renderError(404, 'Page not found'));
505
- return;
506
- }
507
-
508
- props = { ...props, ...result.props };
509
- }
510
-
511
- // Handle getStaticProps (for ISR)
512
- if (pageModule.getStaticProps) {
513
- const result = await pageModule.getStaticProps({ params: route.params });
514
-
515
- if (result.notFound) {
516
- res.writeHead(404, { 'Content-Type': 'text/html' });
517
- res.end(renderError(404, 'Page not found'));
518
- return;
519
- }
520
-
521
- props = { ...props, ...result.props };
522
- }
523
-
524
- // Load layouts (only if layouts directory exists and has layouts)
525
- const layouts = [];
526
-
527
- try {
528
- const layoutConfigs = findRouteLayouts(route, routes.layouts);
529
- for (const layoutConfig of layoutConfigs) {
530
- if (layoutConfig.filePath) {
531
- const layoutModule = await loadModule(layoutConfig.filePath);
532
- if (layoutModule.default) {
533
- layouts.push({
534
- Component: layoutModule.default,
535
- props: {}
536
- });
537
- }
538
- }
539
- }
540
- } catch (layoutError) {
541
- // Layouts are optional, continue without them
542
- console.warn('Layout loading skipped:', layoutError.message);
543
- }
544
-
545
- // Load loading component if exists
546
- let LoadingComponent = null;
547
- if (route.loading) {
548
- const loadingModule = await loadModule(route.loading);
549
- LoadingComponent = loadingModule.default;
550
- }
551
-
552
- // Load error component if exists
553
- let ErrorComponent = null;
554
- if (route.error) {
555
- const errorModule = await loadModule(route.error);
556
- ErrorComponent = errorModule.default;
557
- }
558
-
559
- // Run before render hook
560
- props = await pluginManager.runWaterfallHook(
561
- PluginHooks.BEFORE_RENDER,
562
- props,
563
- { route, Component }
564
- );
565
-
566
- // Check if this is a client component (needs hydration)
567
- const isClientComponent = route.isClientComponent ||
568
- (pageModule.__isClient) ||
569
- (typeof pageModule.default === 'function' && pageModule.default.toString().includes('useState'));
570
-
571
- // Render the page
572
- let html = await renderPage({
573
- Component,
574
- props,
575
- layouts,
576
- loading: LoadingComponent,
577
- error: ErrorComponent,
578
- islands: getRegisteredIslands(),
579
- title: pageModule.title || pageModule.metadata?.title || 'FlexiReact App',
580
- meta: pageModule.metadata || {},
581
- styles: config.styles || [],
582
- scripts: config.scripts || [],
583
- favicon: config.favicon || null,
584
- needsHydration: isClientComponent,
585
- componentPath: route.filePath,
586
- route: route.path || url.pathname,
587
- isSSG: !!pageModule.getStaticProps
588
- });
589
-
590
- // Add island hydration script
591
- const islands = getRegisteredIslands();
592
- if (islands.length > 0 && config.islands.enabled) {
593
- const hydrationScript = generateAdvancedHydrationScript(islands);
594
- html = html.replace('</body>', `${hydrationScript}</body>`);
595
- }
596
-
597
- // Add client hydration for 'use client' components
598
- if (isClientComponent) {
599
- const hydrationScript = generateClientHydrationScript(route.filePath, props);
600
- html = html.replace('</body>', `${hydrationScript}</body>`);
601
- }
602
-
603
- // Run after render hook
604
- html = await pluginManager.runWaterfallHook(
605
- PluginHooks.AFTER_RENDER,
606
- html,
607
- { route, Component, props }
608
- );
609
-
610
- res.writeHead(200, { 'Content-Type': 'text/html' });
611
- res.end(html);
612
-
613
- } catch (error) {
614
- console.error('Page Render Error:', error);
615
- throw error;
616
- }
617
- }
618
-
619
- /**
620
- * Serves a client component as JavaScript for hydration
621
- */
622
- async function serveClientComponent(res, pagesDir, componentName) {
623
- const { transformSync } = await import('esbuild');
624
-
625
- // Remove .tsx.js or .jsx.js suffix if present
626
- const cleanName = componentName.replace(/\.(tsx|jsx|ts|js)\.js$/, '').replace(/\.js$/, '');
627
-
628
- // Find the component file (support TypeScript)
629
- const possiblePaths = [
630
- path.join(pagesDir, `${cleanName}.tsx`),
631
- path.join(pagesDir, `${cleanName}.ts`),
632
- path.join(pagesDir, `${cleanName}.jsx`),
633
- path.join(pagesDir, `${cleanName}.js`),
634
- ];
635
-
636
- let componentPath = null;
637
- for (const p of possiblePaths) {
638
- if (fs.existsSync(p)) {
639
- componentPath = p;
640
- break;
641
- }
642
- }
643
-
644
- if (!componentPath) {
645
- res.writeHead(404, { 'Content-Type': 'text/plain' });
646
- res.end(`Component not found: ${cleanName}`);
647
- return;
648
- }
649
-
650
- // Determine loader based on extension
651
- const ext = path.extname(componentPath);
652
- const loader = ext === '.tsx' ? 'tsx' : ext === '.ts' ? 'ts' : 'jsx';
653
-
654
- try {
655
- let source = fs.readFileSync(componentPath, 'utf-8');
656
-
657
- // Remove 'use client' directive
658
- source = source.replace(/^['"]use (client|server|island)['"];?\s*/m, '');
659
-
660
- // Transform for browser
661
- const result = transformSync(source, {
662
- loader,
663
- format: 'esm',
664
- jsx: 'transform',
665
- jsxFactory: 'React.createElement',
666
- jsxFragment: 'React.Fragment',
667
- target: 'es2020',
668
- // Replace React imports with global
669
- banner: `
670
- const React = window.React;
671
- const useState = window.useState;
672
- const useEffect = window.useEffect;
673
- const useCallback = window.useCallback;
674
- const useMemo = window.useMemo;
675
- const useRef = window.useRef;
676
- `
677
- });
678
-
679
- // Remove all React imports since we're using globals
680
- let code = result.code;
681
- // Remove: import React from 'react'
682
- code = code.replace(/import\s+React\s+from\s+['"]react['"];?\s*/g, '');
683
- // Remove: import { useState } from 'react'
684
- code = code.replace(/import\s+\{[^}]+\}\s+from\s+['"]react['"];?\s*/g, '');
685
- // Remove: import React, { useState } from 'react'
686
- code = code.replace(/import\s+React\s*,\s*\{[^}]+\}\s+from\s+['"]react['"];?\s*/g, '');
687
-
688
- res.writeHead(200, {
689
- 'Content-Type': 'application/javascript',
690
- 'Cache-Control': 'no-cache'
691
- });
692
- res.end(code);
693
-
694
- } catch (error) {
695
- console.error('Error serving client component:', error);
696
- res.writeHead(500, { 'Content-Type': 'text/plain' });
697
- res.end('Error compiling component');
698
- }
699
- }
700
-
701
- /**
702
- * Generates client hydration script for 'use client' components
703
- */
704
- function generateClientHydrationScript(componentPath, props) {
705
- // Create a relative path for the client bundle (handle .tsx, .ts, .jsx, .js)
706
- const ext = path.extname(componentPath);
707
- const componentName = path.basename(componentPath, ext);
708
-
709
- return `
710
- <script type="module">
711
- // FlexiReact Client Hydration
712
- (async function() {
713
- try {
714
- const React = await import('https://esm.sh/react@18.3.1');
715
- const ReactDOM = await import('https://esm.sh/react-dom@18.3.1/client');
716
-
717
- // Make React available globally for the component
718
- window.React = React.default || React;
719
- window.useState = React.useState;
720
- window.useEffect = React.useEffect;
721
- window.useCallback = React.useCallback;
722
- window.useMemo = React.useMemo;
723
- window.useRef = React.useRef;
724
-
725
- // Fetch the component code
726
- const response = await fetch('/_flexi/component/${componentName}.js');
727
- const code = await response.text();
728
-
729
- // Create and import the module
730
- const blob = new Blob([code], { type: 'application/javascript' });
731
- const moduleUrl = URL.createObjectURL(blob);
732
- const module = await import(moduleUrl);
733
-
734
- const Component = module.default;
735
- const props = ${JSON.stringify(props)};
736
-
737
- // Hydrate the root
738
- const root = document.getElementById('root');
739
- ReactDOM.hydrateRoot(root, window.React.createElement(Component, props));
740
-
741
- console.log('⚡ FlexiReact: Component hydrated successfully');
742
- } catch (error) {
743
- console.error('⚡ FlexiReact: Hydration failed', error);
744
- }
745
- })();
746
- </script>`;
747
- }
748
-
749
- /**
750
- * Parses request body
751
- */
752
- async function parseBody(req) {
753
- return new Promise((resolve) => {
754
- const contentType = req.headers['content-type'] || '';
755
- let body = '';
756
-
757
- req.on('data', chunk => {
758
- body += chunk.toString();
759
- });
760
-
761
- req.on('end', () => {
762
- try {
763
- if (contentType.includes('application/json') && body) {
764
- resolve(JSON.parse(body));
765
- } else if (contentType.includes('application/x-www-form-urlencoded') && body) {
766
- resolve(Object.fromEntries(new URLSearchParams(body)));
767
- } else {
768
- resolve(body || null);
769
- }
770
- } catch {
771
- resolve(body);
772
- }
773
- });
774
-
775
- req.on('error', () => resolve(null));
776
- });
777
- }
778
-
779
- export default createServer;