@flexireact/core 3.0.1 → 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.
- package/dist/cli/index.js +1514 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/client/index.js +373 -0
- package/dist/core/client/index.js.map +1 -0
- package/dist/core/index.js +6415 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/server/index.js +3094 -0
- package/dist/core/server/index.js.map +1 -0
- package/package.json +80 -80
- package/bin/flexireact.js +0 -23
- package/cli/generators.ts +0 -616
- package/cli/index.ts +0 -1182
- package/core/actions/index.ts +0 -364
- package/core/api.ts +0 -143
- package/core/build/index.ts +0 -425
- package/core/cli/logger.ts +0 -353
- package/core/client/Link.tsx +0 -345
- package/core/client/hydration.ts +0 -147
- package/core/client/index.ts +0 -12
- package/core/client/islands.ts +0 -143
- package/core/client/navigation.ts +0 -212
- package/core/client/runtime.ts +0 -52
- package/core/config.ts +0 -116
- package/core/context.ts +0 -83
- package/core/dev.ts +0 -47
- package/core/devtools/index.ts +0 -644
- package/core/edge/cache.ts +0 -344
- package/core/edge/fetch-polyfill.ts +0 -247
- package/core/edge/handler.ts +0 -248
- package/core/edge/index.ts +0 -81
- package/core/edge/ppr.ts +0 -264
- package/core/edge/runtime.ts +0 -161
- package/core/font/index.ts +0 -306
- package/core/helpers.ts +0 -494
- package/core/image/index.ts +0 -413
- package/core/index.ts +0 -218
- package/core/islands/index.ts +0 -293
- package/core/loader.ts +0 -111
- package/core/logger.ts +0 -242
- package/core/metadata/index.ts +0 -622
- package/core/middleware/index.ts +0 -416
- package/core/plugins/index.ts +0 -373
- package/core/render/index.ts +0 -1243
- package/core/render.ts +0 -136
- package/core/router/index.ts +0 -551
- package/core/router.ts +0 -141
- package/core/rsc/index.ts +0 -199
- package/core/server/index.ts +0 -779
- package/core/server.ts +0 -203
- package/core/ssg/index.ts +0 -346
- package/core/start-dev.ts +0 -6
- package/core/start-prod.ts +0 -6
- package/core/tsconfig.json +0 -30
- package/core/types.ts +0 -239
- package/core/utils.ts +0 -176
package/core/islands/index.ts
DELETED
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FlexiReact Islands Architecture
|
|
3
|
-
*
|
|
4
|
-
* Islands allow partial hydration - only interactive components are hydrated on the client,
|
|
5
|
-
* while static content remains as HTML. This dramatically reduces JavaScript bundle size.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* - Add 'use island' at the top of a component file
|
|
9
|
-
* - The component will be rendered on server and hydrated on client
|
|
10
|
-
* - Non-island components are pure HTML (no JS)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import React from 'react';
|
|
14
|
-
import { renderToString } from 'react-dom/server';
|
|
15
|
-
import crypto from 'crypto';
|
|
16
|
-
|
|
17
|
-
// Island registry for tracking all islands in a render
|
|
18
|
-
const islandRegistry = new Map();
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Generates a unique island ID
|
|
22
|
-
*/
|
|
23
|
-
function generateIslandId(componentName) {
|
|
24
|
-
const hash = crypto.randomBytes(4).toString('hex');
|
|
25
|
-
return `island-${componentName}-${hash}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Island wrapper component for server-side rendering
|
|
30
|
-
*/
|
|
31
|
-
export function Island({ component: Component, props = {}, name, clientPath }) {
|
|
32
|
-
const islandId = generateIslandId(name);
|
|
33
|
-
|
|
34
|
-
// Register island for hydration
|
|
35
|
-
islandRegistry.set(islandId, {
|
|
36
|
-
id: islandId,
|
|
37
|
-
name,
|
|
38
|
-
clientPath,
|
|
39
|
-
props
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// Render the component
|
|
43
|
-
const content = renderToString(React.createElement(Component, props));
|
|
44
|
-
|
|
45
|
-
// Return wrapper with hydration marker
|
|
46
|
-
return React.createElement('div', {
|
|
47
|
-
'data-island': islandId,
|
|
48
|
-
'data-island-name': name,
|
|
49
|
-
'data-island-props': JSON.stringify(props),
|
|
50
|
-
dangerouslySetInnerHTML: { __html: content }
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Gets all registered islands and clears the registry
|
|
56
|
-
*/
|
|
57
|
-
export function getRegisteredIslands() {
|
|
58
|
-
const islands = Array.from(islandRegistry.values());
|
|
59
|
-
islandRegistry.clear();
|
|
60
|
-
return islands;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Creates an island component wrapper
|
|
65
|
-
*/
|
|
66
|
-
interface IslandOptions {
|
|
67
|
-
name?: string;
|
|
68
|
-
clientPath?: string;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function createIsland(Component: React.ComponentType<any>, options: IslandOptions = {}) {
|
|
72
|
-
const { name = (Component as any).name || 'Island', clientPath } = options;
|
|
73
|
-
|
|
74
|
-
function IslandWrapper(props) {
|
|
75
|
-
return Island({
|
|
76
|
-
component: Component,
|
|
77
|
-
props,
|
|
78
|
-
name,
|
|
79
|
-
clientPath: clientPath || `/_flexi/islands/${name}.js`
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
IslandWrapper.displayName = `Island(${name})`;
|
|
84
|
-
IslandWrapper.isIsland = true;
|
|
85
|
-
IslandWrapper.originalComponent = Component;
|
|
86
|
-
|
|
87
|
-
return IslandWrapper;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Generates the client-side hydration script
|
|
92
|
-
*/
|
|
93
|
-
export function generateHydrationScript(islands) {
|
|
94
|
-
if (!islands.length) return '';
|
|
95
|
-
|
|
96
|
-
const islandData = islands.map(island => ({
|
|
97
|
-
id: island.id,
|
|
98
|
-
name: island.name,
|
|
99
|
-
path: island.clientPath,
|
|
100
|
-
props: island.props
|
|
101
|
-
}));
|
|
102
|
-
|
|
103
|
-
return `
|
|
104
|
-
<script type="module">
|
|
105
|
-
const islands = ${JSON.stringify(islandData)};
|
|
106
|
-
|
|
107
|
-
async function hydrateIslands() {
|
|
108
|
-
const { hydrateRoot } = await import('/_flexi/react-dom-client.js');
|
|
109
|
-
const React = await import('/_flexi/react.js');
|
|
110
|
-
|
|
111
|
-
for (const island of islands) {
|
|
112
|
-
try {
|
|
113
|
-
const element = document.querySelector(\`[data-island="\${island.id}"]\`);
|
|
114
|
-
if (!element) continue;
|
|
115
|
-
|
|
116
|
-
const module = await import(island.path);
|
|
117
|
-
const Component = module.default;
|
|
118
|
-
|
|
119
|
-
// Hydrate the island
|
|
120
|
-
hydrateRoot(element, React.createElement(Component, island.props));
|
|
121
|
-
|
|
122
|
-
// Mark as hydrated
|
|
123
|
-
element.setAttribute('data-hydrated', 'true');
|
|
124
|
-
} catch (error) {
|
|
125
|
-
console.error(\`Failed to hydrate island \${island.name}:\`, error);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Hydrate when DOM is ready
|
|
131
|
-
if (document.readyState === 'loading') {
|
|
132
|
-
document.addEventListener('DOMContentLoaded', hydrateIslands);
|
|
133
|
-
} else {
|
|
134
|
-
hydrateIslands();
|
|
135
|
-
}
|
|
136
|
-
</script>`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Island loading strategies
|
|
141
|
-
*/
|
|
142
|
-
export const LoadStrategy = {
|
|
143
|
-
// Hydrate immediately when page loads
|
|
144
|
-
IMMEDIATE: 'immediate',
|
|
145
|
-
// Hydrate when island becomes visible
|
|
146
|
-
VISIBLE: 'visible',
|
|
147
|
-
// Hydrate when user interacts with the page
|
|
148
|
-
IDLE: 'idle',
|
|
149
|
-
// Hydrate on specific media query
|
|
150
|
-
MEDIA: 'media'
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Creates a lazy island that hydrates based on strategy
|
|
155
|
-
*/
|
|
156
|
-
interface LazyIslandOptions {
|
|
157
|
-
name?: string;
|
|
158
|
-
clientPath?: string;
|
|
159
|
-
strategy?: string;
|
|
160
|
-
media?: string | null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function createLazyIsland(Component: React.ComponentType<any>, options: LazyIslandOptions = {}) {
|
|
164
|
-
const {
|
|
165
|
-
name = (Component as any).name || 'LazyIsland',
|
|
166
|
-
clientPath,
|
|
167
|
-
strategy = LoadStrategy.VISIBLE,
|
|
168
|
-
media = null
|
|
169
|
-
} = options;
|
|
170
|
-
|
|
171
|
-
function LazyIslandWrapper(props) {
|
|
172
|
-
const islandId = generateIslandId(name);
|
|
173
|
-
const content = renderToString(React.createElement(Component, props));
|
|
174
|
-
|
|
175
|
-
// Register with loading strategy
|
|
176
|
-
islandRegistry.set(islandId, {
|
|
177
|
-
id: islandId,
|
|
178
|
-
name,
|
|
179
|
-
clientPath: clientPath || `/_flexi/islands/${name}.js`,
|
|
180
|
-
props,
|
|
181
|
-
strategy,
|
|
182
|
-
media
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
return React.createElement('div', {
|
|
186
|
-
'data-island': islandId,
|
|
187
|
-
'data-island-name': name,
|
|
188
|
-
'data-island-strategy': strategy,
|
|
189
|
-
'data-island-media': media,
|
|
190
|
-
'data-island-props': JSON.stringify(props),
|
|
191
|
-
dangerouslySetInnerHTML: { __html: content }
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
LazyIslandWrapper.displayName = `LazyIsland(${name})`;
|
|
196
|
-
LazyIslandWrapper.isIsland = true;
|
|
197
|
-
LazyIslandWrapper.isLazy = true;
|
|
198
|
-
|
|
199
|
-
return LazyIslandWrapper;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Generates advanced hydration script with loading strategies
|
|
204
|
-
*/
|
|
205
|
-
export function generateAdvancedHydrationScript(islands) {
|
|
206
|
-
if (!islands.length) return '';
|
|
207
|
-
|
|
208
|
-
const islandData = islands.map(island => ({
|
|
209
|
-
id: island.id,
|
|
210
|
-
name: island.name,
|
|
211
|
-
path: island.clientPath,
|
|
212
|
-
props: island.props,
|
|
213
|
-
strategy: island.strategy || LoadStrategy.IMMEDIATE,
|
|
214
|
-
media: island.media
|
|
215
|
-
}));
|
|
216
|
-
|
|
217
|
-
return `
|
|
218
|
-
<script type="module">
|
|
219
|
-
const islands = ${JSON.stringify(islandData)};
|
|
220
|
-
|
|
221
|
-
async function hydrateIsland(island) {
|
|
222
|
-
const element = document.querySelector(\`[data-island="\${island.id}"]\`);
|
|
223
|
-
if (!element || element.hasAttribute('data-hydrated')) return;
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
const { hydrateRoot } = await import('/_flexi/react-dom-client.js');
|
|
227
|
-
const React = await import('/_flexi/react.js');
|
|
228
|
-
const module = await import(island.path);
|
|
229
|
-
const Component = module.default;
|
|
230
|
-
|
|
231
|
-
hydrateRoot(element, React.createElement(Component, island.props));
|
|
232
|
-
element.setAttribute('data-hydrated', 'true');
|
|
233
|
-
} catch (error) {
|
|
234
|
-
console.error(\`Failed to hydrate island \${island.name}:\`, error);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Intersection Observer for visible strategy
|
|
239
|
-
const visibleObserver = new IntersectionObserver((entries) => {
|
|
240
|
-
entries.forEach(entry => {
|
|
241
|
-
if (entry.isIntersecting) {
|
|
242
|
-
const id = entry.target.getAttribute('data-island');
|
|
243
|
-
const island = islands.find(i => i.id === id);
|
|
244
|
-
if (island) {
|
|
245
|
-
hydrateIsland(island);
|
|
246
|
-
visibleObserver.unobserve(entry.target);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
}, { rootMargin: '50px' });
|
|
251
|
-
|
|
252
|
-
// Process islands based on strategy
|
|
253
|
-
function processIslands() {
|
|
254
|
-
for (const island of islands) {
|
|
255
|
-
const element = document.querySelector(\`[data-island="\${island.id}"]\`);
|
|
256
|
-
if (!element) continue;
|
|
257
|
-
|
|
258
|
-
switch (island.strategy) {
|
|
259
|
-
case 'immediate':
|
|
260
|
-
hydrateIsland(island);
|
|
261
|
-
break;
|
|
262
|
-
case 'visible':
|
|
263
|
-
visibleObserver.observe(element);
|
|
264
|
-
break;
|
|
265
|
-
case 'idle':
|
|
266
|
-
requestIdleCallback(() => hydrateIsland(island));
|
|
267
|
-
break;
|
|
268
|
-
case 'media':
|
|
269
|
-
if (island.media && window.matchMedia(island.media).matches) {
|
|
270
|
-
hydrateIsland(island);
|
|
271
|
-
}
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (document.readyState === 'loading') {
|
|
278
|
-
document.addEventListener('DOMContentLoaded', processIslands);
|
|
279
|
-
} else {
|
|
280
|
-
processIslands();
|
|
281
|
-
}
|
|
282
|
-
</script>`;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export default {
|
|
286
|
-
Island,
|
|
287
|
-
createIsland,
|
|
288
|
-
createLazyIsland,
|
|
289
|
-
getRegisteredIslands,
|
|
290
|
-
generateHydrationScript,
|
|
291
|
-
generateAdvancedHydrationScript,
|
|
292
|
-
LoadStrategy
|
|
293
|
-
};
|
package/core/loader.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { transformSync } from 'esbuild';
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
3
|
-
import { fileURLToPath, pathToFileURL } from 'url';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { createRequire } from 'module';
|
|
6
|
-
|
|
7
|
-
const require = createRequire(import.meta.url);
|
|
8
|
-
|
|
9
|
-
// Get the framework's React path to ensure single instance
|
|
10
|
-
const frameworkReactPath = require.resolve('react');
|
|
11
|
-
const frameworkReactDir = path.dirname(frameworkReactPath);
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Custom ESM loader for JSX files
|
|
15
|
-
* This allows Node.js to import .jsx files directly
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
export async function load(url, context, nextLoad) {
|
|
19
|
-
// Handle .jsx, .tsx, and .ts files
|
|
20
|
-
const isJsx = url.endsWith('.jsx') || url.includes('.jsx?');
|
|
21
|
-
const isTsx = url.endsWith('.tsx') || url.includes('.tsx?');
|
|
22
|
-
const isTs = url.endsWith('.ts') || url.includes('.ts?');
|
|
23
|
-
|
|
24
|
-
if (isJsx || isTsx || isTs) {
|
|
25
|
-
// Remove query string for file reading
|
|
26
|
-
const cleanUrl = url.split('?')[0];
|
|
27
|
-
const filePath = fileURLToPath(cleanUrl);
|
|
28
|
-
|
|
29
|
-
// Read the source file
|
|
30
|
-
let source = readFileSync(filePath, 'utf-8');
|
|
31
|
-
|
|
32
|
-
// Remove 'use client', 'use server', 'use island' directives
|
|
33
|
-
// These are handled at build time, not runtime
|
|
34
|
-
source = source.replace(/^['"]use (client|server|island)['"];?\s*/m, '');
|
|
35
|
-
|
|
36
|
-
// Determine the loader based on file extension
|
|
37
|
-
const loader = isTsx ? 'tsx' : isTs ? 'ts' : 'jsx';
|
|
38
|
-
|
|
39
|
-
// Transform to JS using esbuild
|
|
40
|
-
// Use classic JSX transform to avoid jsx-runtime issues
|
|
41
|
-
const result = transformSync(source, {
|
|
42
|
-
loader,
|
|
43
|
-
format: 'esm',
|
|
44
|
-
jsx: 'transform',
|
|
45
|
-
jsxFactory: 'React.createElement',
|
|
46
|
-
jsxFragment: 'React.Fragment',
|
|
47
|
-
target: 'node18'
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
format: 'module',
|
|
52
|
-
source: result.code,
|
|
53
|
-
shortCircuit: true
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Let Node.js handle other file types
|
|
58
|
-
return nextLoad(url, context);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Resolve hook to ensure single React instance and handle TS/TSX imports
|
|
63
|
-
*/
|
|
64
|
-
export async function resolve(specifier, context, nextResolve) {
|
|
65
|
-
// Redirect react imports to framework's React
|
|
66
|
-
if (specifier === 'react' || specifier === 'react-dom' || specifier.startsWith('react/') || specifier.startsWith('react-dom/')) {
|
|
67
|
-
try {
|
|
68
|
-
const resolved = require.resolve(specifier);
|
|
69
|
-
return {
|
|
70
|
-
url: pathToFileURL(resolved).href,
|
|
71
|
-
shortCircuit: true
|
|
72
|
-
};
|
|
73
|
-
} catch {
|
|
74
|
-
// Fall through to default resolution
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Handle TypeScript file imports (resolve .ts/.tsx extensions)
|
|
79
|
-
if (context.parentURL && !specifier.startsWith('node:') && !specifier.startsWith('data:')) {
|
|
80
|
-
// Try to resolve with .tsx, .ts, .jsx extensions
|
|
81
|
-
const extensions = ['.tsx', '.ts', '.jsx', '.js'];
|
|
82
|
-
const parentPath = fileURLToPath(context.parentURL);
|
|
83
|
-
const parentDir = path.dirname(parentPath);
|
|
84
|
-
|
|
85
|
-
for (const ext of extensions) {
|
|
86
|
-
try {
|
|
87
|
-
const possiblePath = path.resolve(parentDir, specifier + ext);
|
|
88
|
-
if (require('fs').existsSync(possiblePath)) {
|
|
89
|
-
return {
|
|
90
|
-
url: pathToFileURL(possiblePath).href,
|
|
91
|
-
shortCircuit: true
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Also try index files
|
|
96
|
-
const indexPath = path.resolve(parentDir, specifier, 'index' + ext);
|
|
97
|
-
if (require('fs').existsSync(indexPath)) {
|
|
98
|
-
return {
|
|
99
|
-
url: pathToFileURL(indexPath).href,
|
|
100
|
-
shortCircuit: true
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
} catch {
|
|
104
|
-
// Continue to next extension
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return nextResolve(specifier, context);
|
|
110
|
-
}
|
|
111
|
-
|
package/core/logger.ts
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FlexiReact Logger
|
|
3
|
-
* Premium console output inspired by Next.js, Vite, and Bun
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// ANSI color codes
|
|
7
|
-
const colors = {
|
|
8
|
-
reset: '\x1b[0m',
|
|
9
|
-
bold: '\x1b[1m',
|
|
10
|
-
dim: '\x1b[2m',
|
|
11
|
-
italic: '\x1b[3m',
|
|
12
|
-
underline: '\x1b[4m',
|
|
13
|
-
|
|
14
|
-
// Text colors
|
|
15
|
-
black: '\x1b[30m',
|
|
16
|
-
red: '\x1b[31m',
|
|
17
|
-
green: '\x1b[32m',
|
|
18
|
-
yellow: '\x1b[33m',
|
|
19
|
-
blue: '\x1b[34m',
|
|
20
|
-
magenta: '\x1b[35m',
|
|
21
|
-
cyan: '\x1b[36m',
|
|
22
|
-
white: '\x1b[37m',
|
|
23
|
-
gray: '\x1b[90m',
|
|
24
|
-
|
|
25
|
-
// Bright colors
|
|
26
|
-
brightRed: '\x1b[91m',
|
|
27
|
-
brightGreen: '\x1b[92m',
|
|
28
|
-
brightYellow: '\x1b[93m',
|
|
29
|
-
brightBlue: '\x1b[94m',
|
|
30
|
-
brightMagenta: '\x1b[95m',
|
|
31
|
-
brightCyan: '\x1b[96m',
|
|
32
|
-
brightWhite: '\x1b[97m',
|
|
33
|
-
|
|
34
|
-
// Background colors
|
|
35
|
-
bgRed: '\x1b[41m',
|
|
36
|
-
bgGreen: '\x1b[42m',
|
|
37
|
-
bgYellow: '\x1b[43m',
|
|
38
|
-
bgBlue: '\x1b[44m',
|
|
39
|
-
bgMagenta: '\x1b[45m',
|
|
40
|
-
bgCyan: '\x1b[46m',
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const c = colors;
|
|
44
|
-
|
|
45
|
-
// Get current time formatted
|
|
46
|
-
function getTime() {
|
|
47
|
-
const now = new Date();
|
|
48
|
-
return `${c.dim}${now.toLocaleTimeString('en-US', { hour12: false })}${c.reset}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Status code colors
|
|
52
|
-
function getStatusColor(status) {
|
|
53
|
-
if (status >= 500) return c.red;
|
|
54
|
-
if (status >= 400) return c.yellow;
|
|
55
|
-
if (status >= 300) return c.cyan;
|
|
56
|
-
if (status >= 200) return c.green;
|
|
57
|
-
return c.white;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Method colors
|
|
61
|
-
function getMethodColor(method) {
|
|
62
|
-
const methodColors = {
|
|
63
|
-
GET: c.brightGreen,
|
|
64
|
-
POST: c.brightBlue,
|
|
65
|
-
PUT: c.brightYellow,
|
|
66
|
-
PATCH: c.brightMagenta,
|
|
67
|
-
DELETE: c.brightRed,
|
|
68
|
-
OPTIONS: c.gray,
|
|
69
|
-
HEAD: c.gray,
|
|
70
|
-
};
|
|
71
|
-
return methodColors[method] || c.white;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Format time
|
|
75
|
-
function formatTime(ms) {
|
|
76
|
-
if (ms < 1) return `${c.gray}<1ms${c.reset}`;
|
|
77
|
-
if (ms < 100) return `${c.green}${ms}ms${c.reset}`;
|
|
78
|
-
if (ms < 500) return `${c.yellow}${ms}ms${c.reset}`;
|
|
79
|
-
return `${c.red}${ms}ms${c.reset}`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// FlexiReact ASCII Logo - Premium Design
|
|
83
|
-
const LOGO = `
|
|
84
|
-
${c.green} ╭─────────────────────────────────────────────╮${c.reset}
|
|
85
|
-
${c.green} │${c.reset} ${c.green}│${c.reset}
|
|
86
|
-
${c.green} │${c.reset} ${c.brightGreen}⚡${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}│${c.reset}
|
|
87
|
-
${c.green} │${c.reset} ${c.green}│${c.reset}
|
|
88
|
-
${c.green} │${c.reset} ${c.dim}The Modern React Framework${c.reset} ${c.green}│${c.reset}
|
|
89
|
-
${c.green} │${c.reset} ${c.green}│${c.reset}
|
|
90
|
-
${c.green} ╰─────────────────────────────────────────────╯${c.reset}
|
|
91
|
-
`;
|
|
92
|
-
|
|
93
|
-
const MINI_LOGO = `${c.brightGreen}⚡${c.reset} ${c.bold}FlexiReact${c.reset}`;
|
|
94
|
-
|
|
95
|
-
// Compact ready message like Next.js
|
|
96
|
-
const READY_MSG = ` ${c.green}▲${c.reset} ${c.bold}Ready${c.reset} in`;
|
|
97
|
-
|
|
98
|
-
export const logger = {
|
|
99
|
-
// Show startup logo
|
|
100
|
-
logo() {
|
|
101
|
-
console.log(LOGO);
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
// Server started - Next.js style
|
|
105
|
-
serverStart(config, startTime = Date.now()) {
|
|
106
|
-
const { port, host, mode, pagesDir, islands, rsc } = config;
|
|
107
|
-
const elapsed = Date.now() - startTime;
|
|
108
|
-
|
|
109
|
-
console.log('');
|
|
110
|
-
console.log(` ${c.green}▲${c.reset} ${c.bold}Ready${c.reset} in ${c.cyan}${elapsed}ms${c.reset}`);
|
|
111
|
-
console.log('');
|
|
112
|
-
console.log(` ${c.dim}┌${c.reset} ${c.bold}Local:${c.reset} ${c.cyan}http://${host}:${port}${c.reset}`);
|
|
113
|
-
console.log(` ${c.dim}├${c.reset} ${c.bold}Environment:${c.reset} ${mode === 'development' ? `${c.yellow}development${c.reset}` : `${c.green}production${c.reset}`}`);
|
|
114
|
-
if (islands) {
|
|
115
|
-
console.log(` ${c.dim}├${c.reset} ${c.bold}Islands:${c.reset} ${c.green}enabled${c.reset}`);
|
|
116
|
-
}
|
|
117
|
-
if (rsc) {
|
|
118
|
-
console.log(` ${c.dim}├${c.reset} ${c.bold}RSC:${c.reset} ${c.green}enabled${c.reset}`);
|
|
119
|
-
}
|
|
120
|
-
console.log(` ${c.dim}└${c.reset} ${c.bold}Pages:${c.reset} ${c.dim}${pagesDir}${c.reset}`);
|
|
121
|
-
console.log('');
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
// HTTP request log - Compact single line like Next.js
|
|
125
|
-
request(method: string, path: string, status: number, time: number, extra: { type?: string } = {}) {
|
|
126
|
-
const methodColor = getMethodColor(method);
|
|
127
|
-
const statusColor = getStatusColor(status);
|
|
128
|
-
const timeStr = formatTime(time);
|
|
129
|
-
|
|
130
|
-
// Route type badge
|
|
131
|
-
let badge = '';
|
|
132
|
-
if (extra.type === 'static' || extra.type === 'ssg') {
|
|
133
|
-
badge = `${c.dim}○${c.reset}`; // Static
|
|
134
|
-
} else if (extra.type === 'dynamic' || extra.type === 'ssr') {
|
|
135
|
-
badge = `${c.magenta}ƒ${c.reset}`; // Function/SSR
|
|
136
|
-
} else if (extra.type === 'api') {
|
|
137
|
-
badge = `${c.blue}λ${c.reset}`; // API
|
|
138
|
-
} else if (extra.type === 'asset') {
|
|
139
|
-
badge = `${c.dim}◦${c.reset}`; // Asset
|
|
140
|
-
} else {
|
|
141
|
-
badge = `${c.magenta}ƒ${c.reset}`;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const statusStr = `${statusColor}${status}${c.reset}`;
|
|
145
|
-
const methodStr = `${methodColor}${method}${c.reset}`;
|
|
146
|
-
|
|
147
|
-
// Single line format like Next.js
|
|
148
|
-
console.log(` ${badge} ${methodStr} ${path} ${statusStr} ${c.dim}in${c.reset} ${timeStr}`);
|
|
149
|
-
},
|
|
150
|
-
|
|
151
|
-
// Info message
|
|
152
|
-
info(msg) {
|
|
153
|
-
console.log(` ${c.cyan}ℹ${c.reset} ${msg}`);
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
// Success message
|
|
157
|
-
success(msg) {
|
|
158
|
-
console.log(` ${c.green}✓${c.reset} ${msg}`);
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
// Warning message
|
|
162
|
-
warn(msg) {
|
|
163
|
-
console.log(` ${c.yellow}⚠${c.reset} ${c.yellow}${msg}${c.reset}`);
|
|
164
|
-
},
|
|
165
|
-
|
|
166
|
-
// Error message
|
|
167
|
-
error(msg, err = null) {
|
|
168
|
-
console.log(` ${c.red}✗${c.reset} ${c.red}${msg}${c.reset}`);
|
|
169
|
-
if (err && err.stack) {
|
|
170
|
-
const stack = err.stack.split('\n').slice(1, 4).join('\n');
|
|
171
|
-
console.log(`${c.dim}${stack}${c.reset}`);
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
|
|
175
|
-
// Compilation message
|
|
176
|
-
compile(file, time) {
|
|
177
|
-
console.log(` ${c.magenta}◉${c.reset} Compiled ${c.cyan}${file}${c.reset} ${c.dim}(${time}ms)${c.reset}`);
|
|
178
|
-
},
|
|
179
|
-
|
|
180
|
-
// Hot reload
|
|
181
|
-
hmr(file) {
|
|
182
|
-
console.log(` ${c.yellow}↻${c.reset} HMR update: ${c.cyan}${file}${c.reset}`);
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
// Plugin loaded
|
|
186
|
-
plugin(name) {
|
|
187
|
-
console.log(` ${c.blue}⬡${c.reset} Plugin: ${c.cyan}${name}${c.reset}`);
|
|
188
|
-
},
|
|
189
|
-
|
|
190
|
-
// Route info
|
|
191
|
-
route(path, type) {
|
|
192
|
-
const typeColors = {
|
|
193
|
-
static: c.green,
|
|
194
|
-
dynamic: c.yellow,
|
|
195
|
-
api: c.blue,
|
|
196
|
-
};
|
|
197
|
-
const color = typeColors[type] || c.white;
|
|
198
|
-
console.log(` ${c.dim}├─${c.reset} ${path} ${color}[${type}]${c.reset}`);
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
// Divider
|
|
202
|
-
divider() {
|
|
203
|
-
console.log(`${c.dim} ─────────────────────────────────────────${c.reset}`);
|
|
204
|
-
},
|
|
205
|
-
|
|
206
|
-
// Blank line
|
|
207
|
-
blank() {
|
|
208
|
-
console.log('');
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
// Port in use error with solution
|
|
212
|
-
portInUse(port) {
|
|
213
|
-
console.log(`
|
|
214
|
-
${c.red} ✗ Port ${port} is already in use${c.reset}
|
|
215
|
-
|
|
216
|
-
${c.dim}Try one of these solutions:${c.reset}
|
|
217
|
-
|
|
218
|
-
${c.yellow}1.${c.reset} Kill the process using the port:
|
|
219
|
-
${c.cyan}npx kill-port ${port}${c.reset}
|
|
220
|
-
|
|
221
|
-
${c.yellow}2.${c.reset} Use a different port in ${c.cyan}flexireact.config.js${c.reset}:
|
|
222
|
-
${c.dim}server: { port: 3001 }${c.reset}
|
|
223
|
-
|
|
224
|
-
${c.yellow}3.${c.reset} Set PORT environment variable:
|
|
225
|
-
${c.cyan}PORT=3001 npm run dev${c.reset}
|
|
226
|
-
`);
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
// Build info
|
|
230
|
-
build(stats) {
|
|
231
|
-
console.log(`
|
|
232
|
-
${c.green}✓${c.reset} Build complete!
|
|
233
|
-
|
|
234
|
-
${c.dim}├─${c.reset} Pages: ${c.cyan}${stats.pages}${c.reset}
|
|
235
|
-
${c.dim}├─${c.reset} API: ${c.cyan}${stats.api}${c.reset}
|
|
236
|
-
${c.dim}├─${c.reset} Assets: ${c.cyan}${stats.assets}${c.reset}
|
|
237
|
-
${c.dim}└─${c.reset} Time: ${c.green}${stats.time}ms${c.reset}
|
|
238
|
-
`);
|
|
239
|
-
},
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
export default logger;
|