@flexireact/core 1.0.0

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.
@@ -0,0 +1,347 @@
1
+ /**
2
+ * FlexiReact CLI Logger - Premium Console Output
3
+ * Beautiful, modern, and professional CLI aesthetics
4
+ */
5
+
6
+ import pc from 'picocolors';
7
+
8
+ // ============================================================================
9
+ // Color Palette
10
+ // ============================================================================
11
+
12
+ const colors = {
13
+ primary: (text) => pc.green(text), // Neon emerald
14
+ secondary: (text) => pc.cyan(text), // Cyan
15
+ accent: (text) => pc.magenta(text), // Magenta accent
16
+ success: (text) => pc.green(text), // Success green
17
+ error: (text) => pc.red(text), // Error red
18
+ warning: (text) => pc.yellow(text), // Warning yellow
19
+ info: (text) => pc.blue(text), // Info blue
20
+ muted: (text) => pc.dim(text), // Muted/dim
21
+ bold: (text) => pc.bold(text), // Bold
22
+ white: (text) => pc.white(text), // White
23
+ };
24
+
25
+ // ============================================================================
26
+ // Icons & Symbols
27
+ // ============================================================================
28
+
29
+ const icons = {
30
+ success: pc.green('✓'),
31
+ error: pc.red('✗'),
32
+ warning: pc.yellow('⚠'),
33
+ info: pc.blue('ℹ'),
34
+ arrow: pc.dim('→'),
35
+ dot: pc.dim('•'),
36
+ star: pc.yellow('★'),
37
+ rocket: '🚀',
38
+ lightning: '⚡',
39
+ puzzle: '🧩',
40
+ island: '🏝️',
41
+ plug: '🔌',
42
+ package: '📦',
43
+ folder: '📁',
44
+ file: '📄',
45
+ globe: '🌐',
46
+ check: pc.green('✔'),
47
+ cross: pc.red('✘'),
48
+ spinner: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
49
+ };
50
+
51
+ // ============================================================================
52
+ // Box Drawing
53
+ // ============================================================================
54
+
55
+ const box = {
56
+ topLeft: '╭',
57
+ topRight: '╮',
58
+ bottomLeft: '╰',
59
+ bottomRight: '╯',
60
+ horizontal: '─',
61
+ vertical: '│',
62
+ horizontalDown: '┬',
63
+ horizontalUp: '┴',
64
+ verticalRight: '├',
65
+ verticalLeft: '┤',
66
+ };
67
+
68
+ function createBox(content, options = {}) {
69
+ const {
70
+ padding = 1,
71
+ borderColor = colors.primary,
72
+ width = 50
73
+ } = options;
74
+
75
+ const lines = content.split('\n');
76
+ const maxLen = Math.max(...lines.map(l => stripAnsi(l).length), width - 4);
77
+ const innerWidth = maxLen + (padding * 2);
78
+
79
+ const top = borderColor(box.topLeft + box.horizontal.repeat(innerWidth) + box.topRight);
80
+ const bottom = borderColor(box.bottomLeft + box.horizontal.repeat(innerWidth) + box.bottomRight);
81
+ const empty = borderColor(box.vertical) + ' '.repeat(innerWidth) + borderColor(box.vertical);
82
+
83
+ const contentLines = lines.map(line => {
84
+ const stripped = stripAnsi(line);
85
+ const pad = innerWidth - stripped.length - padding;
86
+ return borderColor(box.vertical) + ' '.repeat(padding) + line + ' '.repeat(Math.max(0, pad)) + borderColor(box.vertical);
87
+ });
88
+
89
+ const paddingLines = padding > 0 ? [empty] : [];
90
+
91
+ return [top, ...paddingLines, ...contentLines, ...paddingLines, bottom].join('\n');
92
+ }
93
+
94
+ function stripAnsi(str) {
95
+ return str.replace(/\x1B\[[0-9;]*m/g, '');
96
+ }
97
+
98
+ // ============================================================================
99
+ // ASCII Art Banners
100
+ // ============================================================================
101
+
102
+ const banners = {
103
+ // Main banner - Gradient style
104
+ main: `
105
+ ${pc.cyan(' ╭─────────────────────────────────────────╮')}
106
+ ${pc.cyan(' │')} ${pc.cyan('│')}
107
+ ${pc.cyan(' │')} ${pc.bold(pc.green('⚡'))} ${pc.bold(pc.white('F L E X I R E A C T'))} ${pc.cyan('│')}
108
+ ${pc.cyan(' │')} ${pc.cyan('│')}
109
+ ${pc.cyan(' │')} ${pc.dim('The Modern React Framework')} ${pc.cyan('│')}
110
+ ${pc.cyan(' │')} ${pc.cyan('│')}
111
+ ${pc.cyan(' ╰─────────────────────────────────────────╯')}
112
+ `,
113
+
114
+ // Compact banner
115
+ compact: `
116
+ ${pc.green('⚡')} ${pc.bold('FlexiReact')} ${pc.dim('v2.1.0')}
117
+ `,
118
+
119
+ // Build banner
120
+ build: `
121
+ ${pc.cyan(' ╭─────────────────────────────────────────╮')}
122
+ ${pc.cyan(' │')} ${pc.bold(pc.green('⚡'))} ${pc.bold('FlexiReact Build')} ${pc.cyan('│')}
123
+ ${pc.cyan(' ╰─────────────────────────────────────────╯')}
124
+ `,
125
+ };
126
+
127
+ // ============================================================================
128
+ // Server Status Panel
129
+ // ============================================================================
130
+
131
+ function serverPanel(config) {
132
+ const {
133
+ mode = 'development',
134
+ port = 3000,
135
+ host = 'localhost',
136
+ pagesDir = './pages',
137
+ islands = true,
138
+ rsc = true,
139
+ } = config;
140
+
141
+ const modeColor = mode === 'development' ? colors.warning : colors.success;
142
+ const url = `http://${host}:${port}`;
143
+
144
+ console.log('');
145
+ console.log(pc.dim(' ─────────────────────────────────────────'));
146
+ console.log(` ${icons.arrow} ${pc.dim('Mode:')} ${modeColor(mode)}`);
147
+ console.log(` ${icons.arrow} ${pc.dim('Local:')} ${pc.cyan(pc.underline(url))}`);
148
+ console.log(` ${icons.arrow} ${pc.dim('Pages:')} ${pc.white(pagesDir)}`);
149
+ console.log(` ${icons.arrow} ${pc.dim('Islands:')} ${islands ? pc.green('enabled') : pc.dim('disabled')}`);
150
+ console.log(` ${icons.arrow} ${pc.dim('RSC:')} ${rsc ? pc.green('enabled') : pc.dim('disabled')}`);
151
+ console.log(pc.dim(' ─────────────────────────────────────────'));
152
+ console.log('');
153
+ console.log(` ${icons.success} ${pc.green('Ready for requests...')}`);
154
+ console.log('');
155
+ }
156
+
157
+ // ============================================================================
158
+ // Plugin Loader
159
+ // ============================================================================
160
+
161
+ function pluginLoader(plugins = []) {
162
+ console.log('');
163
+ console.log(`${icons.package} ${pc.bold('Loading plugins...')}`);
164
+ console.log('');
165
+
166
+ if (plugins.length === 0) {
167
+ console.log(` ${pc.dim('No plugins configured')}`);
168
+ } else {
169
+ plugins.forEach((plugin, i) => {
170
+ console.log(` ${icons.check} ${pc.white(plugin.name)} ${pc.dim(`v${plugin.version || '1.0.0'}`)}`);
171
+ });
172
+ }
173
+
174
+ console.log('');
175
+ console.log(` ${pc.dim('Total plugins:')} ${pc.white(plugins.length)}`);
176
+ console.log('');
177
+ }
178
+
179
+ // ============================================================================
180
+ // HTTP Request Logger
181
+ // ============================================================================
182
+
183
+ function request(method, path, statusCode, duration, options = {}) {
184
+ const { type = 'dynamic' } = options;
185
+
186
+ // Method colors
187
+ const methodColors = {
188
+ GET: pc.green,
189
+ POST: pc.blue,
190
+ PUT: pc.yellow,
191
+ DELETE: pc.red,
192
+ PATCH: pc.magenta,
193
+ };
194
+
195
+ // Type badges
196
+ const typeBadges = {
197
+ ssr: `${pc.yellow('[SSR]')}`,
198
+ ssg: `${pc.green('[SSG]')}`,
199
+ rsc: `${pc.magenta('[RSC]')}`,
200
+ island: `${pc.cyan('[ISL]')}`,
201
+ api: `${pc.blue('[API]')}`,
202
+ asset: `${pc.dim('[AST]')}`,
203
+ dynamic: `${pc.yellow('[SSR]')}`,
204
+ };
205
+
206
+ // Status colors
207
+ const statusColor = statusCode >= 500 ? pc.red :
208
+ statusCode >= 400 ? pc.yellow :
209
+ statusCode >= 300 ? pc.cyan :
210
+ pc.green;
211
+
212
+ const methodColor = methodColors[method] || pc.white;
213
+ const badge = typeBadges[type] || typeBadges.dynamic;
214
+ const durationStr = duration < 100 ? pc.green(`${duration}ms`) :
215
+ duration < 500 ? pc.yellow(`${duration}ms`) :
216
+ pc.red(`${duration}ms`);
217
+
218
+ console.log(` ${methodColor(method.padEnd(6))} ${pc.white(path)}`);
219
+ console.log(` ${pc.dim('└─')} ${badge} ${statusColor(statusCode)} ${pc.dim('(')}${durationStr}${pc.dim(')')}`);
220
+ }
221
+
222
+ // ============================================================================
223
+ // Error & Warning Formatters
224
+ // ============================================================================
225
+
226
+ function error(title, message, stack = null, suggestions = []) {
227
+ console.log('');
228
+ console.log(pc.red(' ╭─────────────────────────────────────────────────────────╮'));
229
+ console.log(pc.red(' │') + ` ${pc.red(pc.bold('✗ ERROR'))} ` + pc.red('│'));
230
+ console.log(pc.red(' ├─────────────────────────────────────────────────────────┤'));
231
+ console.log(pc.red(' │') + ` ${pc.white(title.substring(0, 55).padEnd(55))} ` + pc.red('│'));
232
+ console.log(pc.red(' │') + ' '.repeat(57) + pc.red('│'));
233
+
234
+ // Message lines
235
+ const msgLines = message.split('\n').slice(0, 3);
236
+ msgLines.forEach(line => {
237
+ console.log(pc.red(' │') + ` ${pc.dim(line.substring(0, 55).padEnd(55))} ` + pc.red('│'));
238
+ });
239
+
240
+ if (suggestions.length > 0) {
241
+ console.log(pc.red(' │') + ' '.repeat(57) + pc.red('│'));
242
+ console.log(pc.red(' │') + ` ${pc.cyan('Suggestions:')} ` + pc.red('│'));
243
+ suggestions.slice(0, 2).forEach(s => {
244
+ console.log(pc.red(' │') + ` ${pc.dim('→')} ${pc.white(s.substring(0, 52).padEnd(52))} ` + pc.red('│'));
245
+ });
246
+ }
247
+
248
+ console.log(pc.red(' ╰─────────────────────────────────────────────────────────╯'));
249
+ console.log('');
250
+ }
251
+
252
+ function warning(title, message) {
253
+ console.log('');
254
+ console.log(pc.yellow(' ⚠ WARNING: ') + pc.white(title));
255
+ console.log(pc.dim(` ${message}`));
256
+ console.log('');
257
+ }
258
+
259
+ function info(message) {
260
+ console.log(` ${icons.info} ${message}`);
261
+ }
262
+
263
+ function success(message) {
264
+ console.log(` ${icons.success} ${pc.green(message)}`);
265
+ }
266
+
267
+ // ============================================================================
268
+ // Build Logger
269
+ // ============================================================================
270
+
271
+ function buildStart() {
272
+ console.log(banners.build);
273
+ console.log(` ${icons.rocket} ${pc.bold('Starting production build...')}`);
274
+ console.log('');
275
+ }
276
+
277
+ function buildStep(step, total, message) {
278
+ console.log(` ${pc.dim(`[${step}/${total}]`)} ${message}`);
279
+ }
280
+
281
+ function buildComplete(stats) {
282
+ const { duration, pages, size } = stats;
283
+
284
+ console.log('');
285
+ console.log(pc.green(' ╭─────────────────────────────────────────╮'));
286
+ console.log(pc.green(' │') + ` ${pc.green(pc.bold('✓ Build completed successfully!'))} ` + pc.green('│'));
287
+ console.log(pc.green(' ├─────────────────────────────────────────┤'));
288
+ console.log(pc.green(' │') + ` ${pc.dim('Duration:')} ${pc.white(duration + 'ms').padEnd(27)} ` + pc.green('│'));
289
+ console.log(pc.green(' │') + ` ${pc.dim('Pages:')} ${pc.white(pages + ' pages').padEnd(27)} ` + pc.green('│'));
290
+ console.log(pc.green(' │') + ` ${pc.dim('Size:')} ${pc.white(size).padEnd(27)} ` + pc.green('│'));
291
+ console.log(pc.green(' ╰─────────────────────────────────────────╯'));
292
+ console.log('');
293
+ }
294
+
295
+ // ============================================================================
296
+ // Dividers & Spacing
297
+ // ============================================================================
298
+
299
+ function divider() {
300
+ console.log(pc.dim(' ─────────────────────────────────────────'));
301
+ }
302
+
303
+ function blank() {
304
+ console.log('');
305
+ }
306
+
307
+ function clear() {
308
+ console.clear();
309
+ }
310
+
311
+ // ============================================================================
312
+ // Export
313
+ // ============================================================================
314
+
315
+ export const logger = {
316
+ // Banners
317
+ banner: () => console.log(banners.main),
318
+ bannerCompact: () => console.log(banners.compact),
319
+
320
+ // Panels
321
+ serverPanel,
322
+ pluginLoader,
323
+
324
+ // Logging
325
+ request,
326
+ error,
327
+ warning,
328
+ info,
329
+ success,
330
+
331
+ // Build
332
+ buildStart,
333
+ buildStep,
334
+ buildComplete,
335
+
336
+ // Utilities
337
+ divider,
338
+ blank,
339
+ clear,
340
+ createBox,
341
+
342
+ // Colors & Icons
343
+ colors,
344
+ icons,
345
+ };
346
+
347
+ export default logger;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * FlexiReact Client Hydration
3
+ * Handles selective hydration of islands and full app hydration
4
+ */
5
+
6
+ import React from 'react';
7
+ import { hydrateRoot, createRoot } from 'react-dom/client';
8
+
9
+ /**
10
+ * Hydrates a specific island component
11
+ */
12
+ export function hydrateIsland(islandId, Component, props) {
13
+ const element = document.querySelector(`[data-island="${islandId}"]`);
14
+
15
+ if (!element) {
16
+ console.warn(`Island element not found: ${islandId}`);
17
+ return;
18
+ }
19
+
20
+ if (element.hasAttribute('data-hydrated')) {
21
+ return; // Already hydrated
22
+ }
23
+
24
+ try {
25
+ hydrateRoot(element, React.createElement(Component, props));
26
+ element.setAttribute('data-hydrated', 'true');
27
+
28
+ // Dispatch custom event
29
+ element.dispatchEvent(new CustomEvent('flexi:hydrated', {
30
+ bubbles: true,
31
+ detail: { islandId, props }
32
+ }));
33
+ } catch (error) {
34
+ console.error(`Failed to hydrate island ${islandId}:`, error);
35
+
36
+ // Fallback: try full render instead of hydration
37
+ try {
38
+ createRoot(element).render(React.createElement(Component, props));
39
+ element.setAttribute('data-hydrated', 'true');
40
+ } catch (fallbackError) {
41
+ console.error(`Fallback render also failed for ${islandId}:`, fallbackError);
42
+ }
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Hydrates the entire application
48
+ */
49
+ export function hydrateApp(App, props = {}) {
50
+ const root = document.getElementById('root');
51
+
52
+ if (!root) {
53
+ console.error('Root element not found');
54
+ return;
55
+ }
56
+
57
+ // Get server-rendered props
58
+ const serverProps = window.__FLEXI_DATA__?.props || {};
59
+ const mergedProps = { ...serverProps, ...props };
60
+
61
+ try {
62
+ hydrateRoot(root, React.createElement(App, mergedProps));
63
+ } catch (error) {
64
+ console.error('Hydration failed, falling back to full render:', error);
65
+ createRoot(root).render(React.createElement(App, mergedProps));
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Hydrates all islands on the page
71
+ */
72
+ export async function hydrateAllIslands(islandModules) {
73
+ const islands = document.querySelectorAll('[data-island]');
74
+
75
+ for (const element of islands) {
76
+ if (element.hasAttribute('data-hydrated')) continue;
77
+
78
+ const islandId = element.getAttribute('data-island');
79
+ const islandName = element.getAttribute('data-island-name');
80
+ const propsJson = element.getAttribute('data-island-props');
81
+
82
+ try {
83
+ const props = propsJson ? JSON.parse(propsJson) : {};
84
+ const module = islandModules[islandName];
85
+
86
+ if (module) {
87
+ hydrateIsland(islandId, module.default || module, props);
88
+ }
89
+ } catch (error) {
90
+ console.error(`Failed to hydrate island ${islandName}:`, error);
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Progressive hydration based on visibility
97
+ */
98
+ export function setupProgressiveHydration(islandModules) {
99
+ const observer = new IntersectionObserver(
100
+ (entries) => {
101
+ entries.forEach(async (entry) => {
102
+ if (!entry.isIntersecting) return;
103
+
104
+ const element = entry.target;
105
+ if (element.hasAttribute('data-hydrated')) return;
106
+
107
+ const islandName = element.getAttribute('data-island-name');
108
+ const islandId = element.getAttribute('data-island');
109
+ const propsJson = element.getAttribute('data-island-props');
110
+
111
+ try {
112
+ const props = propsJson ? JSON.parse(propsJson) : {};
113
+ const module = await islandModules[islandName]();
114
+
115
+ hydrateIsland(islandId, module.default || module, props);
116
+ observer.unobserve(element);
117
+ } catch (error) {
118
+ console.error(`Failed to hydrate island ${islandName}:`, error);
119
+ }
120
+ });
121
+ },
122
+ { rootMargin: '50px' }
123
+ );
124
+
125
+ document.querySelectorAll('[data-island]:not([data-hydrated])').forEach(el => {
126
+ observer.observe(el);
127
+ });
128
+
129
+ return observer;
130
+ }
131
+
132
+ export default {
133
+ hydrateIsland,
134
+ hydrateApp,
135
+ hydrateAllIslands,
136
+ setupProgressiveHydration
137
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * FlexiReact Client Runtime
3
+ * Handles hydration, navigation, and client-side interactivity
4
+ */
5
+
6
+ export { hydrateIsland, hydrateApp } from './hydration.js';
7
+ export { navigate, prefetch, Link } from './navigation.js';
8
+ export { useIsland, IslandBoundary } from './islands.js';
@@ -0,0 +1,138 @@
1
+ /**
2
+ * FlexiReact Client Islands
3
+ * Client-side island utilities
4
+ */
5
+
6
+ import React from 'react';
7
+
8
+ /**
9
+ * Hook to check if component is hydrated
10
+ */
11
+ export function useIsland() {
12
+ const [isHydrated, setIsHydrated] = React.useState(false);
13
+
14
+ React.useEffect(() => {
15
+ setIsHydrated(true);
16
+ }, []);
17
+
18
+ return { isHydrated };
19
+ }
20
+
21
+ /**
22
+ * Island boundary component
23
+ * Wraps interactive components for partial hydration
24
+ */
25
+ export function IslandBoundary({ children, fallback = null, name = 'island' }) {
26
+ const { isHydrated } = useIsland();
27
+
28
+ // On server or before hydration, render children normally
29
+ // The server will wrap this in the island marker
30
+ if (!isHydrated && fallback) {
31
+ return fallback;
32
+ }
33
+
34
+ return React.createElement('div', {
35
+ 'data-island-boundary': name,
36
+ children
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Creates a lazy-loaded island
42
+ */
43
+ export function createClientIsland(loader, options = {}) {
44
+ const { fallback = null, name = 'lazy-island' } = options;
45
+
46
+ return function LazyIsland(props) {
47
+ const [Component, setComponent] = React.useState(null);
48
+ const [error, setError] = React.useState(null);
49
+
50
+ React.useEffect(() => {
51
+ loader()
52
+ .then(mod => setComponent(() => mod.default || mod))
53
+ .catch(err => setError(err));
54
+ }, []);
55
+
56
+ if (error) {
57
+ return React.createElement('div', {
58
+ className: 'island-error',
59
+ children: `Failed to load ${name}`
60
+ });
61
+ }
62
+
63
+ if (!Component) {
64
+ return fallback || React.createElement('div', {
65
+ className: 'island-loading',
66
+ children: 'Loading...'
67
+ });
68
+ }
69
+
70
+ return React.createElement(Component, props);
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Island with interaction trigger
76
+ * Only hydrates when user interacts
77
+ */
78
+ export function InteractiveIsland({ children, trigger = 'click', fallback }) {
79
+ const [shouldHydrate, setShouldHydrate] = React.useState(false);
80
+ const ref = React.useRef(null);
81
+
82
+ React.useEffect(() => {
83
+ const element = ref.current;
84
+ if (!element) return;
85
+
86
+ const handleInteraction = () => {
87
+ setShouldHydrate(true);
88
+ };
89
+
90
+ element.addEventListener(trigger, handleInteraction, { once: true });
91
+
92
+ return () => {
93
+ element.removeEventListener(trigger, handleInteraction);
94
+ };
95
+ }, [trigger]);
96
+
97
+ if (!shouldHydrate) {
98
+ return React.createElement('div', {
99
+ ref,
100
+ 'data-interactive-island': 'true',
101
+ children: fallback || children
102
+ });
103
+ }
104
+
105
+ return children;
106
+ }
107
+
108
+ /**
109
+ * Media query island
110
+ * Only hydrates when media query matches
111
+ */
112
+ export function MediaIsland({ children, query, fallback }) {
113
+ const [matches, setMatches] = React.useState(false);
114
+
115
+ React.useEffect(() => {
116
+ const mediaQuery = window.matchMedia(query);
117
+ setMatches(mediaQuery.matches);
118
+
119
+ const handler = (e) => setMatches(e.matches);
120
+ mediaQuery.addEventListener('change', handler);
121
+
122
+ return () => mediaQuery.removeEventListener('change', handler);
123
+ }, [query]);
124
+
125
+ if (!matches) {
126
+ return fallback || null;
127
+ }
128
+
129
+ return children;
130
+ }
131
+
132
+ export default {
133
+ useIsland,
134
+ IslandBoundary,
135
+ createClientIsland,
136
+ InteractiveIsland,
137
+ MediaIsland
138
+ };