@flexireact/core 4.0.0 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -770
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +441 -701
- package/dist/cli/index.js.map +1 -1
- package/dist/core/build/index.d.ts +99 -0
- package/dist/core/build/index.js +2 -2
- package/dist/core/build/index.js.map +1 -1
- package/dist/core/client/index.d.ts +164 -0
- package/dist/core/client/index.js.map +1 -1
- package/dist/core/config.d.ts +165 -0
- package/dist/core/config.js +62 -32
- package/dist/core/config.js.map +1 -1
- package/dist/core/index.d.ts +1978 -0
- package/dist/core/index.js +309 -202
- package/dist/core/index.js.map +1 -1
- package/dist/core/server/index.d.ts +16 -0
- package/dist/core/server/index.js +305 -198
- package/dist/core/server/index.js.map +1 -1
- package/dist/core/start-dev.d.ts +2 -0
- package/dist/core/start-dev.js +305 -198
- package/dist/core/start-dev.js.map +1 -1
- package/dist/core/start-prod.d.ts +2 -0
- package/dist/core/start-prod.js +305 -198
- package/dist/core/start-prod.js.map +1 -1
- package/package.json +23 -11
package/dist/core/start-dev.js
CHANGED
|
@@ -8,46 +8,59 @@ import { fileURLToPath, pathToFileURL as pathToFileURL4 } from "url";
|
|
|
8
8
|
import fs from "fs";
|
|
9
9
|
import path from "path";
|
|
10
10
|
import { pathToFileURL } from "url";
|
|
11
|
-
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import pc from "picocolors";
|
|
13
|
+
var BuildConfigSchema = z.object({
|
|
14
|
+
target: z.string().default("es2022"),
|
|
15
|
+
minify: z.boolean().default(true),
|
|
16
|
+
sourcemap: z.boolean().default(true),
|
|
17
|
+
splitting: z.boolean().default(true)
|
|
18
|
+
}).default({});
|
|
19
|
+
var ServerConfigSchema = z.object({
|
|
20
|
+
port: z.number().min(1).max(65535).default(3e3),
|
|
21
|
+
host: z.string().default("localhost")
|
|
22
|
+
}).default({});
|
|
23
|
+
var SSGConfigSchema = z.object({
|
|
24
|
+
enabled: z.boolean().default(false),
|
|
25
|
+
paths: z.array(z.string()).default([])
|
|
26
|
+
}).default({});
|
|
27
|
+
var IslandsConfigSchema = z.object({
|
|
28
|
+
enabled: z.boolean().default(true),
|
|
29
|
+
directive: z.string().default("use island")
|
|
30
|
+
}).default({});
|
|
31
|
+
var RSCConfigSchema = z.object({
|
|
32
|
+
enabled: z.boolean().default(true)
|
|
33
|
+
}).default({});
|
|
34
|
+
var PluginSchema = z.object({
|
|
35
|
+
name: z.string(),
|
|
36
|
+
setup: z.function().optional()
|
|
37
|
+
}).passthrough();
|
|
38
|
+
var FlexiReactConfigSchema = z.object({
|
|
12
39
|
// Directories
|
|
13
|
-
pagesDir: "pages",
|
|
14
|
-
layoutsDir: "layouts",
|
|
15
|
-
publicDir: "public",
|
|
16
|
-
outDir: ".flexi",
|
|
40
|
+
pagesDir: z.string().default("pages"),
|
|
41
|
+
layoutsDir: z.string().default("layouts"),
|
|
42
|
+
publicDir: z.string().default("public"),
|
|
43
|
+
outDir: z.string().default(".flexi"),
|
|
17
44
|
// Build options
|
|
18
|
-
build:
|
|
19
|
-
target: "es2022",
|
|
20
|
-
minify: true,
|
|
21
|
-
sourcemap: true,
|
|
22
|
-
splitting: true
|
|
23
|
-
},
|
|
45
|
+
build: BuildConfigSchema,
|
|
24
46
|
// Server options
|
|
25
|
-
server:
|
|
26
|
-
port: 3e3,
|
|
27
|
-
host: "localhost"
|
|
28
|
-
},
|
|
47
|
+
server: ServerConfigSchema,
|
|
29
48
|
// SSG options
|
|
30
|
-
ssg:
|
|
31
|
-
enabled: false,
|
|
32
|
-
paths: []
|
|
33
|
-
},
|
|
49
|
+
ssg: SSGConfigSchema,
|
|
34
50
|
// Islands (partial hydration)
|
|
35
|
-
islands:
|
|
36
|
-
enabled: true
|
|
37
|
-
},
|
|
51
|
+
islands: IslandsConfigSchema,
|
|
38
52
|
// RSC options
|
|
39
|
-
rsc:
|
|
40
|
-
enabled: true
|
|
41
|
-
},
|
|
53
|
+
rsc: RSCConfigSchema,
|
|
42
54
|
// Plugins
|
|
43
|
-
plugins: [],
|
|
55
|
+
plugins: z.array(PluginSchema).default([]),
|
|
44
56
|
// Styles (CSS files to include)
|
|
45
|
-
styles: [],
|
|
57
|
+
styles: z.array(z.string()).default([]),
|
|
46
58
|
// Scripts (JS files to include)
|
|
47
|
-
scripts: [],
|
|
59
|
+
scripts: z.array(z.string()).default([]),
|
|
48
60
|
// Favicon path
|
|
49
|
-
favicon: null
|
|
50
|
-
};
|
|
61
|
+
favicon: z.string().nullable().default(null)
|
|
62
|
+
});
|
|
63
|
+
var defaultConfig = FlexiReactConfigSchema.parse({});
|
|
51
64
|
async function loadConfig(projectRoot) {
|
|
52
65
|
const configPathTs = path.join(projectRoot, "flexireact.config.ts");
|
|
53
66
|
const configPathJs = path.join(projectRoot, "flexireact.config.js");
|
|
@@ -59,10 +72,22 @@ async function loadConfig(projectRoot) {
|
|
|
59
72
|
const module = await import(`${configUrl}?t=${Date.now()}`);
|
|
60
73
|
userConfig = module.default || module;
|
|
61
74
|
} catch (error) {
|
|
62
|
-
console.warn(
|
|
75
|
+
console.warn(pc.yellow(`\u26A0 Failed to load config: ${error.message}`));
|
|
63
76
|
}
|
|
64
77
|
}
|
|
65
|
-
|
|
78
|
+
const merged = deepMerge(defaultConfig, userConfig);
|
|
79
|
+
try {
|
|
80
|
+
return FlexiReactConfigSchema.parse(merged);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
if (err instanceof z.ZodError) {
|
|
83
|
+
console.error(pc.red("\u2716 Configuration validation failed:"));
|
|
84
|
+
for (const issue of err.issues) {
|
|
85
|
+
console.error(pc.dim(` - ${issue.path.join(".")}: ${issue.message}`));
|
|
86
|
+
}
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
66
91
|
}
|
|
67
92
|
function deepMerge(target, source) {
|
|
68
93
|
const result = { ...target };
|
|
@@ -148,7 +173,7 @@ function buildRouteTree(pagesDir, layoutsDir, appDir = null, routesDir = null) {
|
|
|
148
173
|
appRoutes: [],
|
|
149
174
|
// Next.js style app router routes
|
|
150
175
|
flexiRoutes: []
|
|
151
|
-
// FlexiReact
|
|
176
|
+
// FlexiReact v4 routes/ directory
|
|
152
177
|
};
|
|
153
178
|
const routesDirPath = routesDir || path2.join(projectRoot, "routes");
|
|
154
179
|
if (fs3.existsSync(routesDirPath)) {
|
|
@@ -499,32 +524,72 @@ async function renderPage(options) {
|
|
|
499
524
|
favicon = null,
|
|
500
525
|
isSSG = false,
|
|
501
526
|
route = "/",
|
|
502
|
-
needsHydration = false
|
|
527
|
+
needsHydration = false,
|
|
528
|
+
componentPath = ""
|
|
503
529
|
} = options;
|
|
504
530
|
const renderStart = Date.now();
|
|
505
531
|
try {
|
|
506
|
-
let element
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
532
|
+
let element;
|
|
533
|
+
let content;
|
|
534
|
+
let isClientOnly = false;
|
|
535
|
+
try {
|
|
536
|
+
element = React.createElement(Component, props);
|
|
537
|
+
if (error) {
|
|
538
|
+
element = React.createElement(ErrorBoundaryWrapper, {
|
|
539
|
+
fallback: error,
|
|
540
|
+
children: element
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
if (loading) {
|
|
544
|
+
element = React.createElement(React.Suspense, {
|
|
545
|
+
fallback: React.createElement(loading),
|
|
546
|
+
children: element
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
for (const layout of [...layouts].reverse()) {
|
|
550
|
+
if (layout.Component) {
|
|
551
|
+
const LayoutComponent = layout.Component;
|
|
552
|
+
element = React.createElement(LayoutComponent, {
|
|
553
|
+
...layout.props
|
|
554
|
+
}, element);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
content = renderToString(element);
|
|
558
|
+
} catch (renderErr) {
|
|
559
|
+
const isHookError = renderErr.message?.includes("useState") || renderErr.message?.includes("useEffect") || renderErr.message?.includes("useContext") || renderErr.message?.includes("useReducer") || renderErr.message?.includes("useRef") || renderErr.message?.includes("Invalid hook call");
|
|
560
|
+
if (isHookError || needsHydration) {
|
|
561
|
+
isClientOnly = true;
|
|
562
|
+
const placeholderHtml = `
|
|
563
|
+
<div id="flexi-root" data-client-component="true" data-component-path="${escapeHtml(componentPath)}">
|
|
564
|
+
<div class="flexi-loading" style="display:flex;align-items:center;justify-content:center;min-height:200px;color:#666;">
|
|
565
|
+
<div style="text-align:center;">
|
|
566
|
+
<div style="width:40px;height:40px;border:3px solid #e0e0e0;border-top-color:#3b82f6;border-radius:50%;animation:spin 1s linear infinite;margin:0 auto 12px;"></div>
|
|
567
|
+
<div>Loading...</div>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
<style>@keyframes spin{to{transform:rotate(360deg)}}</style>
|
|
571
|
+
</div>
|
|
572
|
+
`;
|
|
573
|
+
let wrappedContent = placeholderHtml;
|
|
574
|
+
for (const layout of [...layouts].reverse()) {
|
|
575
|
+
if (layout.Component) {
|
|
576
|
+
try {
|
|
577
|
+
const layoutElement = React.createElement(layout.Component, {
|
|
578
|
+
...layout.props,
|
|
579
|
+
children: React.createElement("div", {
|
|
580
|
+
dangerouslySetInnerHTML: { __html: wrappedContent }
|
|
581
|
+
})
|
|
582
|
+
});
|
|
583
|
+
wrappedContent = renderToString(layoutElement);
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
content = wrappedContent;
|
|
589
|
+
} else {
|
|
590
|
+
throw renderErr;
|
|
525
591
|
}
|
|
526
592
|
}
|
|
527
|
-
const content = renderToString(element);
|
|
528
593
|
const renderTime = Date.now() - renderStart;
|
|
529
594
|
const islandScripts = generateIslandScripts(islands);
|
|
530
595
|
return buildHtmlDocument({
|
|
@@ -538,7 +603,8 @@ async function renderPage(options) {
|
|
|
538
603
|
isSSG,
|
|
539
604
|
renderTime,
|
|
540
605
|
route,
|
|
541
|
-
isClientComponent: needsHydration
|
|
606
|
+
isClientComponent: needsHydration || isClientOnly,
|
|
607
|
+
componentPath
|
|
542
608
|
});
|
|
543
609
|
} catch (err) {
|
|
544
610
|
console.error("Render Error:", err);
|
|
@@ -589,7 +655,7 @@ function generateDevToolbar(options = {}) {
|
|
|
589
655
|
const timeColor = renderTime < 50 ? "#00FF9C" : renderTime < 200 ? "#fbbf24" : "#ef4444";
|
|
590
656
|
const timeLabel = renderTime < 50 ? "Fast" : renderTime < 200 ? "OK" : "Slow";
|
|
591
657
|
return `
|
|
592
|
-
<!-- FlexiReact
|
|
658
|
+
<!-- FlexiReact v4.1.0 Dev Toolbar -->
|
|
593
659
|
<div id="flexi-dev-toolbar" class="flexi-dev-collapsed">
|
|
594
660
|
<style>
|
|
595
661
|
#flexi-dev-toolbar {
|
|
@@ -897,7 +963,7 @@ function generateDevToolbar(options = {}) {
|
|
|
897
963
|
<div class="flexi-dev-header-logo">F</div>
|
|
898
964
|
<div class="flexi-dev-header-info">
|
|
899
965
|
<div class="flexi-dev-header-title">FlexiReact</div>
|
|
900
|
-
<div class="flexi-dev-header-subtitle">
|
|
966
|
+
<div class="flexi-dev-header-subtitle">v4.1.0 \u2022 Development</div>
|
|
901
967
|
</div>
|
|
902
968
|
<button class="flexi-dev-close" onclick="this.closest('#flexi-dev-toolbar').classList.remove('flexi-dev-open')">\u2715</button>
|
|
903
969
|
</div>
|
|
@@ -950,9 +1016,9 @@ function generateDevToolbar(options = {}) {
|
|
|
950
1016
|
</div>
|
|
951
1017
|
|
|
952
1018
|
<script>
|
|
953
|
-
// FlexiReact
|
|
1019
|
+
// FlexiReact v4 DevTools
|
|
954
1020
|
window.__FLEXI_DEV__ = {
|
|
955
|
-
version: '
|
|
1021
|
+
version: '4.1.0',
|
|
956
1022
|
renderTime: ${renderTime},
|
|
957
1023
|
pageType: '${pageType}',
|
|
958
1024
|
route: '${route}',
|
|
@@ -987,7 +1053,7 @@ function generateDevToolbar(options = {}) {
|
|
|
987
1053
|
|
|
988
1054
|
// Console branding
|
|
989
1055
|
console.log(
|
|
990
|
-
'%c \u26A1 FlexiReact
|
|
1056
|
+
'%c \u26A1 FlexiReact v4.1.0 %c ${pageType} %c ${renderTime}ms ',
|
|
991
1057
|
'background: #00FF9C; color: #000; font-weight: bold; padding: 2px 6px; border-radius: 4px 0 0 4px;',
|
|
992
1058
|
'background: #1e1e1e; color: #fafafa; padding: 2px 6px;',
|
|
993
1059
|
'background: ${timeColor}20; color: ${timeColor}; padding: 2px 6px; border-radius: 0 4px 4px 0;'
|
|
@@ -1088,7 +1154,7 @@ function renderError(statusCode, message, stack = null) {
|
|
|
1088
1154
|
401: { title: "Unauthorized", icon: "key", color: "#8b5cf6", desc: "Please log in to access this page." }
|
|
1089
1155
|
};
|
|
1090
1156
|
const errorInfo = errorMessages[statusCode] || { title: "Error", icon: "alert", color: "#ef4444", desc: message };
|
|
1091
|
-
const errorFramesHtml = showStack && errorDetails?.frames?.length > 0 ? errorDetails.frames.slice(0, 5).map((frame, i) => `
|
|
1157
|
+
const errorFramesHtml = showStack && errorDetails?.frames?.length && errorDetails.frames.length > 0 ? errorDetails.frames.slice(0, 5).map((frame, i) => `
|
|
1092
1158
|
<div class="error-frame ${i === 0 ? "error-frame-first" : ""}">
|
|
1093
1159
|
<div class="error-frame-fn">${escapeHtml(frame.fn)}</div>
|
|
1094
1160
|
<div class="error-frame-loc">${escapeHtml(frame.file)}:${frame.line}:${frame.col}</div>
|
|
@@ -1388,7 +1454,7 @@ function renderError(statusCode, message, stack = null) {
|
|
|
1388
1454
|
${isDev ? `
|
|
1389
1455
|
<div class="dev-badge">
|
|
1390
1456
|
<div class="dev-badge-dot"></div>
|
|
1391
|
-
FlexiReact
|
|
1457
|
+
FlexiReact v4.1.0
|
|
1392
1458
|
</div>
|
|
1393
1459
|
` : ""}
|
|
1394
1460
|
</body>
|
|
@@ -1826,10 +1892,7 @@ var colors = {
|
|
|
1826
1892
|
reset: "\x1B[0m",
|
|
1827
1893
|
bold: "\x1B[1m",
|
|
1828
1894
|
dim: "\x1B[2m",
|
|
1829
|
-
italic: "\x1B[3m",
|
|
1830
|
-
underline: "\x1B[4m",
|
|
1831
1895
|
// Text colors
|
|
1832
|
-
black: "\x1B[30m",
|
|
1833
1896
|
red: "\x1B[31m",
|
|
1834
1897
|
green: "\x1B[32m",
|
|
1835
1898
|
yellow: "\x1B[33m",
|
|
@@ -1837,22 +1900,7 @@ var colors = {
|
|
|
1837
1900
|
magenta: "\x1B[35m",
|
|
1838
1901
|
cyan: "\x1B[36m",
|
|
1839
1902
|
white: "\x1B[37m",
|
|
1840
|
-
gray: "\x1B[90m"
|
|
1841
|
-
// Bright colors
|
|
1842
|
-
brightRed: "\x1B[91m",
|
|
1843
|
-
brightGreen: "\x1B[92m",
|
|
1844
|
-
brightYellow: "\x1B[93m",
|
|
1845
|
-
brightBlue: "\x1B[94m",
|
|
1846
|
-
brightMagenta: "\x1B[95m",
|
|
1847
|
-
brightCyan: "\x1B[96m",
|
|
1848
|
-
brightWhite: "\x1B[97m",
|
|
1849
|
-
// Background colors
|
|
1850
|
-
bgRed: "\x1B[41m",
|
|
1851
|
-
bgGreen: "\x1B[42m",
|
|
1852
|
-
bgYellow: "\x1B[43m",
|
|
1853
|
-
bgBlue: "\x1B[44m",
|
|
1854
|
-
bgMagenta: "\x1B[45m",
|
|
1855
|
-
bgCyan: "\x1B[46m"
|
|
1903
|
+
gray: "\x1B[90m"
|
|
1856
1904
|
};
|
|
1857
1905
|
var c = colors;
|
|
1858
1906
|
function getStatusColor(status) {
|
|
@@ -1863,16 +1911,7 @@ function getStatusColor(status) {
|
|
|
1863
1911
|
return c.white;
|
|
1864
1912
|
}
|
|
1865
1913
|
function getMethodColor(method) {
|
|
1866
|
-
|
|
1867
|
-
GET: c.brightGreen,
|
|
1868
|
-
POST: c.brightBlue,
|
|
1869
|
-
PUT: c.brightYellow,
|
|
1870
|
-
PATCH: c.brightMagenta,
|
|
1871
|
-
DELETE: c.brightRed,
|
|
1872
|
-
OPTIONS: c.gray,
|
|
1873
|
-
HEAD: c.gray
|
|
1874
|
-
};
|
|
1875
|
-
return methodColors[method] || c.white;
|
|
1914
|
+
return c.white;
|
|
1876
1915
|
}
|
|
1877
1916
|
function formatTime(ms) {
|
|
1878
1917
|
if (ms < 1) return `${c.gray}<1ms${c.reset}`;
|
|
@@ -1881,137 +1920,107 @@ function formatTime(ms) {
|
|
|
1881
1920
|
return `${c.red}${ms}ms${c.reset}`;
|
|
1882
1921
|
}
|
|
1883
1922
|
var LOGO = `
|
|
1884
|
-
${c.
|
|
1885
|
-
${c.green} \u2502${c.reset} ${c.green}\u2502${c.reset}
|
|
1886
|
-
${c.green} \u2502${c.reset} ${c.brightGreen}\u26A1${c.reset} ${c.bold}${c.white}F L E X I R E A C T${c.reset} ${c.dim}v1.0.0${c.reset} ${c.green}\u2502${c.reset}
|
|
1887
|
-
${c.green} \u2502${c.reset} ${c.green}\u2502${c.reset}
|
|
1888
|
-
${c.green} \u2502${c.reset} ${c.dim}The Modern React Framework${c.reset} ${c.green}\u2502${c.reset}
|
|
1889
|
-
${c.green} \u2502${c.reset} ${c.green}\u2502${c.reset}
|
|
1890
|
-
${c.green} \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F${c.reset}
|
|
1923
|
+
${c.white}\u25B2${c.reset} ${c.bold}FlexiReact${c.reset} ${c.dim}4.1.0${c.reset}
|
|
1891
1924
|
`;
|
|
1892
|
-
var MINI_LOGO = `${c.brightGreen}\u26A1${c.reset} ${c.bold}FlexiReact${c.reset}`;
|
|
1893
|
-
var READY_MSG = ` ${c.green}\u25B2${c.reset} ${c.bold}Ready${c.reset} in`;
|
|
1894
1925
|
var logger = {
|
|
1895
1926
|
// Show startup logo
|
|
1896
1927
|
logo() {
|
|
1897
1928
|
console.log(LOGO);
|
|
1929
|
+
console.log(`${c.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
|
|
1930
|
+
console.log("");
|
|
1898
1931
|
},
|
|
1899
|
-
// Server started -
|
|
1932
|
+
// Server started - Minimalist style
|
|
1900
1933
|
serverStart(config, startTime = Date.now()) {
|
|
1901
|
-
const { port, host, mode, pagesDir
|
|
1934
|
+
const { port, host, mode, pagesDir } = config;
|
|
1902
1935
|
const elapsed = Date.now() - startTime;
|
|
1936
|
+
console.log(` ${c.green}\u2714${c.reset} ${c.bold}Ready${c.reset} in ${elapsed}ms`);
|
|
1903
1937
|
console.log("");
|
|
1904
|
-
console.log(`
|
|
1905
|
-
console.log("");
|
|
1906
|
-
console.log(`
|
|
1907
|
-
console.log(` ${c.dim}\u251C${c.reset} ${c.bold}Environment:${c.reset} ${mode === "development" ? `${c.yellow}development${c.reset}` : `${c.green}production${c.reset}`}`);
|
|
1908
|
-
if (islands) {
|
|
1909
|
-
console.log(` ${c.dim}\u251C${c.reset} ${c.bold}Islands:${c.reset} ${c.green}enabled${c.reset}`);
|
|
1910
|
-
}
|
|
1911
|
-
if (rsc) {
|
|
1912
|
-
console.log(` ${c.dim}\u251C${c.reset} ${c.bold}RSC:${c.reset} ${c.green}enabled${c.reset}`);
|
|
1913
|
-
}
|
|
1914
|
-
console.log(` ${c.dim}\u2514${c.reset} ${c.bold}Pages:${c.reset} ${c.dim}${pagesDir}${c.reset}`);
|
|
1938
|
+
console.log(` ${c.bold}Local:${c.reset} ${c.cyan}http://${host}:${port}${c.reset}`);
|
|
1939
|
+
console.log(` ${c.bold}Mode:${c.reset} ${mode === "development" ? c.yellow : c.green}${mode}${c.reset}`);
|
|
1940
|
+
console.log(` ${c.bold}Pages:${c.reset} ${c.dim}${pagesDir}${c.reset}`);
|
|
1915
1941
|
console.log("");
|
|
1916
1942
|
},
|
|
1917
|
-
// HTTP request log - Compact single line
|
|
1943
|
+
// HTTP request log - Compact single line
|
|
1918
1944
|
request(method, path8, status, time, extra = {}) {
|
|
1919
1945
|
const methodColor = getMethodColor(method);
|
|
1920
1946
|
const statusColor = getStatusColor(status);
|
|
1921
1947
|
const timeStr = formatTime(time);
|
|
1922
|
-
let badge =
|
|
1923
|
-
if (extra.type === "
|
|
1924
|
-
badge = `${c.
|
|
1925
|
-
} else if (extra.type === "dynamic" || extra.type === "ssr") {
|
|
1926
|
-
badge = `${c.magenta}\u0192${c.reset}`;
|
|
1948
|
+
let badge = `${c.dim}\u25CB${c.reset}`;
|
|
1949
|
+
if (extra.type === "dynamic" || extra.type === "ssr") {
|
|
1950
|
+
badge = `${c.white}\u0192${c.reset}`;
|
|
1927
1951
|
} else if (extra.type === "api") {
|
|
1928
|
-
badge = `${c.
|
|
1929
|
-
} else if (extra.type === "asset") {
|
|
1930
|
-
badge = `${c.dim}\u25E6${c.reset}`;
|
|
1931
|
-
} else {
|
|
1932
|
-
badge = `${c.magenta}\u0192${c.reset}`;
|
|
1952
|
+
badge = `${c.cyan}\u03BB${c.reset}`;
|
|
1933
1953
|
}
|
|
1934
1954
|
const statusStr = `${statusColor}${status}${c.reset}`;
|
|
1935
1955
|
const methodStr = `${methodColor}${method}${c.reset}`;
|
|
1936
|
-
console.log(`
|
|
1956
|
+
console.log(` ${badge} ${methodStr} ${path8} ${statusStr} ${c.dim}${timeStr}${c.reset}`);
|
|
1937
1957
|
},
|
|
1938
1958
|
// Info message
|
|
1939
1959
|
info(msg) {
|
|
1940
|
-
console.log(`
|
|
1960
|
+
console.log(` ${c.cyan}\u2139${c.reset} ${msg}`);
|
|
1941
1961
|
},
|
|
1942
1962
|
// Success message
|
|
1943
1963
|
success(msg) {
|
|
1944
|
-
console.log(`
|
|
1964
|
+
console.log(` ${c.green}\u2714${c.reset} ${msg}`);
|
|
1945
1965
|
},
|
|
1946
1966
|
// Warning message
|
|
1947
1967
|
warn(msg) {
|
|
1948
|
-
console.log(`
|
|
1968
|
+
console.log(` ${c.yellow}\u26A0${c.reset} ${c.yellow}${msg}${c.reset}`);
|
|
1949
1969
|
},
|
|
1950
1970
|
// Error message
|
|
1951
1971
|
error(msg, err = null) {
|
|
1952
|
-
console.log(`
|
|
1972
|
+
console.log(` ${c.red}\u2716${c.reset} ${c.red}${msg}${c.reset}`);
|
|
1953
1973
|
if (err && err.stack) {
|
|
1974
|
+
console.log("");
|
|
1954
1975
|
const stack = err.stack.split("\n").slice(1, 4).join("\n");
|
|
1955
1976
|
console.log(`${c.dim}${stack}${c.reset}`);
|
|
1977
|
+
console.log("");
|
|
1956
1978
|
}
|
|
1957
1979
|
},
|
|
1958
1980
|
// Compilation message
|
|
1959
1981
|
compile(file, time) {
|
|
1960
|
-
console.log(`
|
|
1982
|
+
console.log(` ${c.white}\u25CF${c.reset} Compiling ${c.dim}${file}${c.reset} ${c.dim}(${time}ms)${c.reset}`);
|
|
1961
1983
|
},
|
|
1962
1984
|
// Hot reload
|
|
1963
1985
|
hmr(file) {
|
|
1964
|
-
console.log(`
|
|
1986
|
+
console.log(` ${c.green}\u21BB${c.reset} Fast Refresh ${c.dim}${file}${c.reset}`);
|
|
1965
1987
|
},
|
|
1966
1988
|
// Plugin loaded
|
|
1967
1989
|
plugin(name) {
|
|
1968
|
-
console.log(`
|
|
1990
|
+
console.log(` ${c.cyan}\u25C6${c.reset} Plugin ${c.dim}${name}${c.reset}`);
|
|
1969
1991
|
},
|
|
1970
1992
|
// Route info
|
|
1971
1993
|
route(path8, type) {
|
|
1972
|
-
const
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
api: c.blue
|
|
1976
|
-
};
|
|
1977
|
-
const color = typeColors[type] || c.white;
|
|
1978
|
-
console.log(` ${c.dim}\u251C\u2500${c.reset} ${path8} ${color}[${type}]${c.reset}`);
|
|
1994
|
+
const typeLabel = type === "api" ? "\u03BB" : type === "dynamic" ? "\u0192" : "\u25CB";
|
|
1995
|
+
const color = type === "api" ? c.cyan : type === "dynamic" ? c.white : c.dim;
|
|
1996
|
+
console.log(` ${color}${typeLabel}${c.reset} ${path8}`);
|
|
1979
1997
|
},
|
|
1980
1998
|
// Divider
|
|
1981
1999
|
divider() {
|
|
1982
|
-
console.log(`${c.dim}
|
|
2000
|
+
console.log(`${c.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
|
|
1983
2001
|
},
|
|
1984
2002
|
// Blank line
|
|
1985
2003
|
blank() {
|
|
1986
2004
|
console.log("");
|
|
1987
2005
|
},
|
|
1988
|
-
// Port in use error
|
|
2006
|
+
// Port in use error
|
|
1989
2007
|
portInUse(port) {
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
${c.cyan}npx kill-port ${port}${c.reset}
|
|
1997
|
-
|
|
1998
|
-
${c.yellow}2.${c.reset} Use a different port in ${c.cyan}flexireact.config.js${c.reset}:
|
|
1999
|
-
${c.dim}server: { port: 3001 }${c.reset}
|
|
2000
|
-
|
|
2001
|
-
${c.yellow}3.${c.reset} Set PORT environment variable:
|
|
2002
|
-
${c.cyan}PORT=3001 npm run dev${c.reset}
|
|
2003
|
-
`);
|
|
2008
|
+
this.error(`Port ${port} is already in use.`);
|
|
2009
|
+
this.blank();
|
|
2010
|
+
console.log(` ${c.dim}Try:${c.reset}`);
|
|
2011
|
+
console.log(` 1. Kill the process on port ${port}`);
|
|
2012
|
+
console.log(` 2. Use a different port via PORT env var`);
|
|
2013
|
+
this.blank();
|
|
2004
2014
|
},
|
|
2005
2015
|
// Build info
|
|
2006
2016
|
build(stats) {
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
`);
|
|
2017
|
+
this.blank();
|
|
2018
|
+
console.log(` ${c.green}\u2714${c.reset} Build completed`);
|
|
2019
|
+
this.blank();
|
|
2020
|
+
console.log(` ${c.dim}Route${c.reset} ${c.dim}Size${c.reset}`);
|
|
2021
|
+
console.log(` ${c.dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
|
|
2022
|
+
console.log(` ${c.dim}Total time:${c.reset} ${c.white}${stats.time}ms${c.reset}`);
|
|
2023
|
+
this.blank();
|
|
2015
2024
|
}
|
|
2016
2025
|
};
|
|
2017
2026
|
|
|
@@ -2302,22 +2311,69 @@ async function executeAction(actionId, args, context) {
|
|
|
2302
2311
|
globalThis.__FLEXI_ACTION_CONTEXT__ = null;
|
|
2303
2312
|
}
|
|
2304
2313
|
}
|
|
2314
|
+
var ALLOWED_SERIALIZED_TYPES = /* @__PURE__ */ new Set(["FormData", "Date", "File"]);
|
|
2315
|
+
var MAX_PAYLOAD_DEPTH = 10;
|
|
2316
|
+
var MAX_STRING_LENGTH = 1e6;
|
|
2317
|
+
function validateInput(obj, depth = 0) {
|
|
2318
|
+
if (depth > MAX_PAYLOAD_DEPTH) {
|
|
2319
|
+
throw new Error("Payload too deeply nested");
|
|
2320
|
+
}
|
|
2321
|
+
if (obj === null || obj === void 0) return true;
|
|
2322
|
+
if (typeof obj === "string") {
|
|
2323
|
+
if (obj.length > MAX_STRING_LENGTH) {
|
|
2324
|
+
throw new Error("String value too long");
|
|
2325
|
+
}
|
|
2326
|
+
return true;
|
|
2327
|
+
}
|
|
2328
|
+
if (typeof obj !== "object") return true;
|
|
2329
|
+
if ("__proto__" in obj || "constructor" in obj || "prototype" in obj) {
|
|
2330
|
+
throw new Error("Invalid payload: prototype pollution attempt detected");
|
|
2331
|
+
}
|
|
2332
|
+
if ("$$type" in obj) {
|
|
2333
|
+
if (!ALLOWED_SERIALIZED_TYPES.has(obj.$$type)) {
|
|
2334
|
+
throw new Error(`Invalid serialized type: ${obj.$$type}`);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
if (Array.isArray(obj)) {
|
|
2338
|
+
for (const item of obj) {
|
|
2339
|
+
validateInput(item, depth + 1);
|
|
2340
|
+
}
|
|
2341
|
+
} else {
|
|
2342
|
+
for (const value of Object.values(obj)) {
|
|
2343
|
+
validateInput(value, depth + 1);
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
return true;
|
|
2347
|
+
}
|
|
2305
2348
|
function deserializeArgs(args) {
|
|
2349
|
+
validateInput(args);
|
|
2306
2350
|
return args.map((arg) => {
|
|
2307
2351
|
if (arg && typeof arg === "object") {
|
|
2308
2352
|
if (arg.$$type === "FormData") {
|
|
2309
2353
|
const formData = new FormData();
|
|
2310
|
-
for (const [key, value] of Object.entries(arg.data)) {
|
|
2354
|
+
for (const [key, value] of Object.entries(arg.data || {})) {
|
|
2355
|
+
if (typeof key !== "string" || key.startsWith("__")) continue;
|
|
2311
2356
|
if (Array.isArray(value)) {
|
|
2312
|
-
value.forEach((v) =>
|
|
2313
|
-
|
|
2314
|
-
|
|
2357
|
+
value.forEach((v) => {
|
|
2358
|
+
if (typeof v === "string" || typeof v === "number") {
|
|
2359
|
+
formData.append(key, String(v));
|
|
2360
|
+
}
|
|
2361
|
+
});
|
|
2362
|
+
} else if (typeof value === "string" || typeof value === "number") {
|
|
2363
|
+
formData.append(key, String(value));
|
|
2315
2364
|
}
|
|
2316
2365
|
}
|
|
2317
2366
|
return formData;
|
|
2318
2367
|
}
|
|
2319
2368
|
if (arg.$$type === "Date") {
|
|
2320
|
-
|
|
2369
|
+
const date = new Date(arg.value);
|
|
2370
|
+
if (isNaN(date.getTime())) {
|
|
2371
|
+
throw new Error("Invalid date value");
|
|
2372
|
+
}
|
|
2373
|
+
return date;
|
|
2374
|
+
}
|
|
2375
|
+
if (arg.$$type === "File") {
|
|
2376
|
+
return { name: String(arg.name || ""), type: String(arg.type || ""), size: Number(arg.size || 0) };
|
|
2321
2377
|
}
|
|
2322
2378
|
}
|
|
2323
2379
|
return arg;
|
|
@@ -2591,7 +2647,6 @@ async function createServer(options = {}) {
|
|
|
2591
2647
|
const serverStartTime = Date.now();
|
|
2592
2648
|
const projectRoot = options.projectRoot || process.cwd();
|
|
2593
2649
|
const isDev = options.mode === "development";
|
|
2594
|
-
logger.logo();
|
|
2595
2650
|
const rawConfig = await loadConfig(projectRoot);
|
|
2596
2651
|
const config = resolvePaths(rawConfig, projectRoot);
|
|
2597
2652
|
await loadPlugins(projectRoot, config);
|
|
@@ -2602,7 +2657,7 @@ async function createServer(options = {}) {
|
|
|
2602
2657
|
const loadModule = createModuleLoader(isDev);
|
|
2603
2658
|
const server = http.createServer(async (req, res) => {
|
|
2604
2659
|
const startTime = Date.now();
|
|
2605
|
-
const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
|
|
2660
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
2606
2661
|
const pathname = url.pathname;
|
|
2607
2662
|
try {
|
|
2608
2663
|
await pluginManager.runHook(PluginHooks.REQUEST, req, res);
|
|
@@ -2610,7 +2665,7 @@ async function createServer(options = {}) {
|
|
|
2610
2665
|
if (!middlewareResult.continue) {
|
|
2611
2666
|
return;
|
|
2612
2667
|
}
|
|
2613
|
-
const effectivePath = middlewareResult.rewritten ? new URL(req.url, `http://${req.headers.host}`).pathname : pathname;
|
|
2668
|
+
const effectivePath = middlewareResult.rewritten ? new URL(req.url || "/", `http://${req.headers.host}`).pathname : pathname;
|
|
2614
2669
|
if (await serveStaticFile(res, config.publicDir, effectivePath)) {
|
|
2615
2670
|
return;
|
|
2616
2671
|
}
|
|
@@ -2624,6 +2679,26 @@ async function createServer(options = {}) {
|
|
|
2624
2679
|
const componentName = effectivePath.slice(18).replace(".js", "");
|
|
2625
2680
|
return await serveClientComponent(res, config.pagesDir, componentName);
|
|
2626
2681
|
}
|
|
2682
|
+
if (effectivePath.startsWith("/_flexi/client/")) {
|
|
2683
|
+
const componentName = effectivePath.slice(15).replace(".js", "");
|
|
2684
|
+
const searchDirs = [
|
|
2685
|
+
config.pagesDir,
|
|
2686
|
+
path7.join(process.cwd(), "app", "components"),
|
|
2687
|
+
path7.join(process.cwd(), "routes"),
|
|
2688
|
+
path7.join(process.cwd(), "routes", "(public)")
|
|
2689
|
+
];
|
|
2690
|
+
for (const dir of searchDirs) {
|
|
2691
|
+
try {
|
|
2692
|
+
await serveClientComponent(res, dir, componentName);
|
|
2693
|
+
return;
|
|
2694
|
+
} catch {
|
|
2695
|
+
continue;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
2699
|
+
res.end("Component not found: " + componentName);
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2627
2702
|
if (effectivePath === "/_flexi/action" && req.method === "POST") {
|
|
2628
2703
|
return await handleServerAction(req, res);
|
|
2629
2704
|
}
|
|
@@ -2674,7 +2749,7 @@ async function createServer(options = {}) {
|
|
|
2674
2749
|
const duration = Date.now() - startTime;
|
|
2675
2750
|
if (isDev) {
|
|
2676
2751
|
const routeType = pathname.startsWith("/api/") ? "api" : pathname.startsWith("/_flexi/") ? "asset" : pathname.match(/\.(js|css|png|jpg|svg|ico)$/) ? "asset" : "dynamic";
|
|
2677
|
-
logger.request(req.method, pathname, res.statusCode, duration, { type: routeType });
|
|
2752
|
+
logger.request(req.method || "GET", pathname, res.statusCode, duration, { type: routeType });
|
|
2678
2753
|
}
|
|
2679
2754
|
await pluginManager.runHook(PluginHooks.RESPONSE, req, res, duration);
|
|
2680
2755
|
}
|
|
@@ -2735,9 +2810,9 @@ async function serveStaticFile(res, baseDir, pathname) {
|
|
|
2735
2810
|
async function handleApiRoute(req, res, route, loadModule) {
|
|
2736
2811
|
try {
|
|
2737
2812
|
const module = await loadModule(route.filePath);
|
|
2738
|
-
const method = req.method.toLowerCase();
|
|
2813
|
+
const method = (req.method || "GET").toLowerCase();
|
|
2739
2814
|
const body = await parseBody(req);
|
|
2740
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
2815
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
2741
2816
|
const query = Object.fromEntries(url.searchParams);
|
|
2742
2817
|
const enhancedReq = {
|
|
2743
2818
|
...req,
|
|
@@ -2763,11 +2838,28 @@ async function handleApiRoute(req, res, route, loadModule) {
|
|
|
2763
2838
|
}
|
|
2764
2839
|
async function handleServerAction(req, res) {
|
|
2765
2840
|
try {
|
|
2841
|
+
const contentType = req.headers["content-type"];
|
|
2842
|
+
if (!contentType?.includes("application/json")) {
|
|
2843
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2844
|
+
res.end(JSON.stringify({ success: false, error: "Invalid content type" }));
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
const contentLength = parseInt(req.headers["content-length"] || "0", 10);
|
|
2848
|
+
if (contentLength > 10 * 1024 * 1024) {
|
|
2849
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
2850
|
+
res.end(JSON.stringify({ success: false, error: "Payload too large" }));
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2766
2853
|
const body = await parseBody(req);
|
|
2767
2854
|
const { actionId, args } = body;
|
|
2768
|
-
if (!actionId) {
|
|
2855
|
+
if (!actionId || typeof actionId !== "string" || !/^[a-zA-Z0-9_]+$/.test(actionId)) {
|
|
2769
2856
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2770
|
-
res.end(JSON.stringify({ success: false, error: "
|
|
2857
|
+
res.end(JSON.stringify({ success: false, error: "Invalid actionId format" }));
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2860
|
+
if (args !== void 0 && !Array.isArray(args)) {
|
|
2861
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2862
|
+
res.end(JSON.stringify({ success: false, error: "Invalid args format" }));
|
|
2771
2863
|
return;
|
|
2772
2864
|
}
|
|
2773
2865
|
const deserializedArgs = deserializeArgs(args || []);
|
|
@@ -2784,10 +2876,11 @@ async function handleServerAction(req, res) {
|
|
|
2784
2876
|
res.end(JSON.stringify(result));
|
|
2785
2877
|
} catch (error) {
|
|
2786
2878
|
console.error("Server Action Error:", error);
|
|
2879
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
2787
2880
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
2788
2881
|
res.end(JSON.stringify({
|
|
2789
2882
|
success: false,
|
|
2790
|
-
error: error.message
|
|
2883
|
+
error: isDev ? error.message : "Action execution failed"
|
|
2791
2884
|
}));
|
|
2792
2885
|
}
|
|
2793
2886
|
}
|
|
@@ -3007,6 +3100,8 @@ async function serveClientComponent(res, pagesDir, componentName) {
|
|
|
3007
3100
|
const useCallback = window.useCallback;
|
|
3008
3101
|
const useMemo = window.useMemo;
|
|
3009
3102
|
const useRef = window.useRef;
|
|
3103
|
+
const useContext = window.useContext;
|
|
3104
|
+
const useReducer = window.useReducer;
|
|
3010
3105
|
`
|
|
3011
3106
|
});
|
|
3012
3107
|
let code = result.code;
|
|
@@ -3027,41 +3122,53 @@ async function serveClientComponent(res, pagesDir, componentName) {
|
|
|
3027
3122
|
function generateClientHydrationScript(componentPath, props) {
|
|
3028
3123
|
const ext = path7.extname(componentPath);
|
|
3029
3124
|
const componentName = path7.basename(componentPath, ext);
|
|
3125
|
+
const propsJson = JSON.stringify(props);
|
|
3030
3126
|
return `
|
|
3031
3127
|
<script type="module">
|
|
3032
|
-
// FlexiReact Client Hydration
|
|
3128
|
+
// FlexiReact Client Hydration v4.1
|
|
3129
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef, useContext, useReducer } from 'https://esm.sh/react@19.0.0';
|
|
3130
|
+
import { createRoot, hydrateRoot } from 'https://esm.sh/react-dom@19.0.0/client';
|
|
3131
|
+
|
|
3132
|
+
// Make React hooks available globally
|
|
3133
|
+
window.React = React;
|
|
3134
|
+
window.useState = useState;
|
|
3135
|
+
window.useEffect = useEffect;
|
|
3136
|
+
window.useCallback = useCallback;
|
|
3137
|
+
window.useMemo = useMemo;
|
|
3138
|
+
window.useRef = useRef;
|
|
3139
|
+
window.useContext = useContext;
|
|
3140
|
+
window.useReducer = useReducer;
|
|
3141
|
+
|
|
3033
3142
|
(async function() {
|
|
3034
3143
|
try {
|
|
3035
|
-
|
|
3036
|
-
const
|
|
3144
|
+
// Fetch and execute the component
|
|
3145
|
+
const response = await fetch('/_flexi/client/${componentName}.js');
|
|
3146
|
+
if (!response.ok) throw new Error('Failed to load component: ' + response.status);
|
|
3037
3147
|
|
|
3038
|
-
// Make React available globally for the component
|
|
3039
|
-
window.React = React.default || React;
|
|
3040
|
-
window.useState = React.useState;
|
|
3041
|
-
window.useEffect = React.useEffect;
|
|
3042
|
-
window.useCallback = React.useCallback;
|
|
3043
|
-
window.useMemo = React.useMemo;
|
|
3044
|
-
window.useRef = React.useRef;
|
|
3045
|
-
|
|
3046
|
-
// Fetch the component code
|
|
3047
|
-
const response = await fetch('/_flexi/component/${componentName}.js');
|
|
3048
3148
|
const code = await response.text();
|
|
3049
|
-
|
|
3050
|
-
// Create and import the module
|
|
3051
3149
|
const blob = new Blob([code], { type: 'application/javascript' });
|
|
3052
3150
|
const moduleUrl = URL.createObjectURL(blob);
|
|
3053
3151
|
const module = await import(moduleUrl);
|
|
3054
3152
|
|
|
3055
3153
|
const Component = module.default;
|
|
3056
|
-
|
|
3154
|
+
if (!Component) throw new Error('No default export found');
|
|
3057
3155
|
|
|
3058
|
-
|
|
3059
|
-
const
|
|
3060
|
-
ReactDOM.hydrateRoot(root, window.React.createElement(Component, props));
|
|
3156
|
+
const props = ${propsJson};
|
|
3157
|
+
const container = document.getElementById('flexi-root') || document.getElementById('root');
|
|
3061
3158
|
|
|
3062
|
-
|
|
3159
|
+
if (container) {
|
|
3160
|
+
// Use createRoot for client components (not hydrate since we rendered placeholder)
|
|
3161
|
+
const root = createRoot(container);
|
|
3162
|
+
root.render(React.createElement(Component, props));
|
|
3163
|
+
console.log('%c\u26A1 FlexiReact', 'color: #00FF9C; font-weight: bold', 'Component mounted');
|
|
3164
|
+
}
|
|
3063
3165
|
} catch (error) {
|
|
3064
|
-
console.error('\u26A1 FlexiReact: Hydration failed', error);
|
|
3166
|
+
console.error('%c\u26A1 FlexiReact', 'color: #ef4444; font-weight: bold', 'Hydration failed:', error);
|
|
3167
|
+
// Show error in UI
|
|
3168
|
+
const container = document.getElementById('flexi-root');
|
|
3169
|
+
if (container) {
|
|
3170
|
+
container.innerHTML = '<div style="color:#ef4444;padding:20px;text-align:center;">Failed to load component: ' + error.message + '</div>';
|
|
3171
|
+
}
|
|
3065
3172
|
}
|
|
3066
3173
|
})();
|
|
3067
3174
|
</script>`;
|