@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,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;