@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,147 +0,0 @@
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
- // Extend Window interface for __FLEXI_DATA__
10
- declare global {
11
- interface Window {
12
- __FLEXI_DATA__?: {
13
- islands?: any[];
14
- props?: Record<string, any>;
15
- };
16
- }
17
- }
18
-
19
- /**
20
- * Hydrates a specific island component
21
- */
22
- export function hydrateIsland(islandId, Component, props) {
23
- const element = document.querySelector(`[data-island="${islandId}"]`);
24
-
25
- if (!element) {
26
- console.warn(`Island element not found: ${islandId}`);
27
- return;
28
- }
29
-
30
- if (element.hasAttribute('data-hydrated')) {
31
- return; // Already hydrated
32
- }
33
-
34
- try {
35
- hydrateRoot(element, React.createElement(Component, props));
36
- element.setAttribute('data-hydrated', 'true');
37
-
38
- // Dispatch custom event
39
- element.dispatchEvent(new CustomEvent('flexi:hydrated', {
40
- bubbles: true,
41
- detail: { islandId, props }
42
- }));
43
- } catch (error) {
44
- console.error(`Failed to hydrate island ${islandId}:`, error);
45
-
46
- // Fallback: try full render instead of hydration
47
- try {
48
- createRoot(element).render(React.createElement(Component, props));
49
- element.setAttribute('data-hydrated', 'true');
50
- } catch (fallbackError) {
51
- console.error(`Fallback render also failed for ${islandId}:`, fallbackError);
52
- }
53
- }
54
- }
55
-
56
- /**
57
- * Hydrates the entire application
58
- */
59
- export function hydrateApp(App, props = {}) {
60
- const root = document.getElementById('root');
61
-
62
- if (!root) {
63
- console.error('Root element not found');
64
- return;
65
- }
66
-
67
- // Get server-rendered props
68
- const serverProps = window.__FLEXI_DATA__?.props || {};
69
- const mergedProps = { ...serverProps, ...props };
70
-
71
- try {
72
- hydrateRoot(root, React.createElement(App, mergedProps));
73
- } catch (error) {
74
- console.error('Hydration failed, falling back to full render:', error);
75
- createRoot(root).render(React.createElement(App, mergedProps));
76
- }
77
- }
78
-
79
- /**
80
- * Hydrates all islands on the page
81
- */
82
- export async function hydrateAllIslands(islandModules) {
83
- const islands = document.querySelectorAll('[data-island]');
84
-
85
- for (const element of islands) {
86
- if (element.hasAttribute('data-hydrated')) continue;
87
-
88
- const islandId = element.getAttribute('data-island');
89
- const islandName = element.getAttribute('data-island-name');
90
- const propsJson = element.getAttribute('data-island-props');
91
-
92
- try {
93
- const props = propsJson ? JSON.parse(propsJson) : {};
94
- const module = islandModules[islandName];
95
-
96
- if (module) {
97
- hydrateIsland(islandId, module.default || module, props);
98
- }
99
- } catch (error) {
100
- console.error(`Failed to hydrate island ${islandName}:`, error);
101
- }
102
- }
103
- }
104
-
105
- /**
106
- * Progressive hydration based on visibility
107
- */
108
- export function setupProgressiveHydration(islandModules) {
109
- const observer = new IntersectionObserver(
110
- (entries) => {
111
- entries.forEach(async (entry) => {
112
- if (!entry.isIntersecting) return;
113
-
114
- const element = entry.target;
115
- if (element.hasAttribute('data-hydrated')) return;
116
-
117
- const islandName = element.getAttribute('data-island-name');
118
- const islandId = element.getAttribute('data-island');
119
- const propsJson = element.getAttribute('data-island-props');
120
-
121
- try {
122
- const props = propsJson ? JSON.parse(propsJson) : {};
123
- const module = await islandModules[islandName]();
124
-
125
- hydrateIsland(islandId, module.default || module, props);
126
- observer.unobserve(element);
127
- } catch (error) {
128
- console.error(`Failed to hydrate island ${islandName}:`, error);
129
- }
130
- });
131
- },
132
- { rootMargin: '50px' }
133
- );
134
-
135
- document.querySelectorAll('[data-island]:not([data-hydrated])').forEach(el => {
136
- observer.observe(el);
137
- });
138
-
139
- return observer;
140
- }
141
-
142
- export default {
143
- hydrateIsland,
144
- hydrateApp,
145
- hydrateAllIslands,
146
- setupProgressiveHydration
147
- };
@@ -1,12 +0,0 @@
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 as NavLink } from './navigation.js';
8
- export { useIsland, IslandBoundary } from './islands.js';
9
-
10
- // Enhanced Link component with prefetching
11
- export { Link, useRouter } from './Link.js';
12
- export type { LinkProps } from './Link.js';
@@ -1,143 +0,0 @@
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
- interface LazyIslandOptions {
44
- fallback?: React.ReactNode;
45
- name?: string;
46
- }
47
-
48
- export function createClientIsland(loader: () => Promise<any>, options: LazyIslandOptions = {}) {
49
- const { fallback = null, name = 'lazy-island' } = options;
50
-
51
- return function LazyIsland(props) {
52
- const [Component, setComponent] = React.useState(null);
53
- const [error, setError] = React.useState(null);
54
-
55
- React.useEffect(() => {
56
- loader()
57
- .then(mod => setComponent(() => mod.default || mod))
58
- .catch(err => setError(err));
59
- }, []);
60
-
61
- if (error) {
62
- return React.createElement('div', {
63
- className: 'island-error',
64
- children: `Failed to load ${name}`
65
- });
66
- }
67
-
68
- if (!Component) {
69
- return fallback || React.createElement('div', {
70
- className: 'island-loading',
71
- children: 'Loading...'
72
- });
73
- }
74
-
75
- return React.createElement(Component, props);
76
- };
77
- }
78
-
79
- /**
80
- * Island with interaction trigger
81
- * Only hydrates when user interacts
82
- */
83
- export function InteractiveIsland({ children, trigger = 'click', fallback }) {
84
- const [shouldHydrate, setShouldHydrate] = React.useState(false);
85
- const ref = React.useRef(null);
86
-
87
- React.useEffect(() => {
88
- const element = ref.current;
89
- if (!element) return;
90
-
91
- const handleInteraction = () => {
92
- setShouldHydrate(true);
93
- };
94
-
95
- element.addEventListener(trigger, handleInteraction, { once: true });
96
-
97
- return () => {
98
- element.removeEventListener(trigger, handleInteraction);
99
- };
100
- }, [trigger]);
101
-
102
- if (!shouldHydrate) {
103
- return React.createElement('div', {
104
- ref,
105
- 'data-interactive-island': 'true',
106
- children: fallback || children
107
- });
108
- }
109
-
110
- return children;
111
- }
112
-
113
- /**
114
- * Media query island
115
- * Only hydrates when media query matches
116
- */
117
- export function MediaIsland({ children, query, fallback }) {
118
- const [matches, setMatches] = React.useState(false);
119
-
120
- React.useEffect(() => {
121
- const mediaQuery = window.matchMedia(query);
122
- setMatches(mediaQuery.matches);
123
-
124
- const handler = (e) => setMatches(e.matches);
125
- mediaQuery.addEventListener('change', handler);
126
-
127
- return () => mediaQuery.removeEventListener('change', handler);
128
- }, [query]);
129
-
130
- if (!matches) {
131
- return fallback || null;
132
- }
133
-
134
- return children;
135
- }
136
-
137
- export default {
138
- useIsland,
139
- IslandBoundary,
140
- createClientIsland,
141
- InteractiveIsland,
142
- MediaIsland
143
- };
@@ -1,212 +0,0 @@
1
- /**
2
- * FlexiReact Client Navigation
3
- * Client-side navigation with prefetching
4
- */
5
-
6
- import React from 'react';
7
-
8
- // Navigation state
9
- const navigationState: {
10
- listeners: Set<(url: string) => void>;
11
- prefetched: Set<string>;
12
- } = {
13
- listeners: new Set(),
14
- prefetched: new Set()
15
- };
16
-
17
- /**
18
- * Navigates to a new URL
19
- */
20
- interface NavigateOptions {
21
- replace?: boolean;
22
- scroll?: boolean;
23
- }
24
-
25
- export function navigate(url: string, options: NavigateOptions = {}) {
26
- const { replace = false, scroll = true } = options;
27
-
28
- if (replace) {
29
- window.history.replaceState({}, '', url);
30
- } else {
31
- window.history.pushState({}, '', url);
32
- }
33
-
34
- // Dispatch navigation event
35
- window.dispatchEvent(new CustomEvent('flexi:navigate', {
36
- detail: { url, replace, scroll }
37
- }));
38
-
39
- // Scroll to top if needed
40
- if (scroll) {
41
- window.scrollTo(0, 0);
42
- }
43
-
44
- // Notify listeners
45
- navigationState.listeners.forEach(listener => listener(url));
46
-
47
- // Fetch and render new page
48
- return fetchAndRender(url);
49
- }
50
-
51
- /**
52
- * Prefetches a URL for faster navigation
53
- */
54
- export function prefetch(url) {
55
- if (navigationState.prefetched.has(url)) {
56
- return Promise.resolve();
57
- }
58
-
59
- navigationState.prefetched.add(url);
60
-
61
- // Create a link element for prefetching
62
- const link = document.createElement('link');
63
- link.rel = 'prefetch';
64
- link.href = url;
65
- document.head.appendChild(link);
66
-
67
- return Promise.resolve();
68
- }
69
-
70
- /**
71
- * Fetches and renders a new page
72
- */
73
- async function fetchAndRender(url) {
74
- try {
75
- const response = await fetch(url, {
76
- headers: {
77
- 'X-Flexi-Navigation': 'true'
78
- }
79
- });
80
-
81
- if (!response.ok) {
82
- throw new Error(`Navigation failed: ${response.status}`);
83
- }
84
-
85
- const html = await response.text();
86
-
87
- // Parse the HTML
88
- const parser = new DOMParser();
89
- const doc = parser.parseFromString(html, 'text/html');
90
-
91
- // Update the page content
92
- const newRoot = doc.getElementById('root');
93
- const currentRoot = document.getElementById('root');
94
-
95
- if (newRoot && currentRoot) {
96
- currentRoot.innerHTML = newRoot.innerHTML;
97
- }
98
-
99
- // Update the title
100
- document.title = doc.title;
101
-
102
- // Update meta tags
103
- updateMetaTags(doc);
104
-
105
- // Re-hydrate islands
106
- window.dispatchEvent(new CustomEvent('flexi:pageload'));
107
-
108
- } catch (error) {
109
- console.error('Navigation error:', error);
110
- // Fallback to full page navigation
111
- window.location.href = url;
112
- }
113
- }
114
-
115
- /**
116
- * Updates meta tags from new document
117
- */
118
- function updateMetaTags(doc) {
119
- // Remove old meta tags
120
- document.querySelectorAll('meta[data-flexi]').forEach(el => el.remove());
121
-
122
- // Add new meta tags
123
- doc.querySelectorAll('meta').forEach(meta => {
124
- if (meta.name || meta.property) {
125
- const newMeta = meta.cloneNode(true);
126
- newMeta.setAttribute('data-flexi', 'true');
127
- document.head.appendChild(newMeta);
128
- }
129
- });
130
- }
131
-
132
- /**
133
- * Link component for client-side navigation
134
- */
135
- export function Link({ href, children, prefetch: shouldPrefetch = true, replace = false, className, ...props }) {
136
- const handleClick = (e) => {
137
- // Allow normal navigation for external links or modified clicks
138
- if (
139
- e.ctrlKey ||
140
- e.metaKey ||
141
- e.shiftKey ||
142
- e.button !== 0 ||
143
- href.startsWith('http') ||
144
- href.startsWith('//')
145
- ) {
146
- return;
147
- }
148
-
149
- e.preventDefault();
150
- navigate(href, { replace });
151
- };
152
-
153
- const handleMouseEnter = () => {
154
- if (shouldPrefetch) {
155
- prefetch(href);
156
- }
157
- };
158
-
159
- return React.createElement('a', {
160
- href,
161
- onClick: handleClick,
162
- onMouseEnter: handleMouseEnter,
163
- className,
164
- ...props
165
- }, children);
166
- }
167
-
168
- /**
169
- * Hook to listen for navigation events
170
- */
171
- export function useNavigation() {
172
- const [pathname, setPathname] = React.useState(
173
- typeof window !== 'undefined' ? window.location.pathname : '/'
174
- );
175
-
176
- React.useEffect(() => {
177
- const handleNavigation = (url) => {
178
- setPathname(new URL(url, window.location.origin).pathname);
179
- };
180
-
181
- navigationState.listeners.add(handleNavigation);
182
-
183
- const handlePopState = () => {
184
- setPathname(window.location.pathname);
185
- navigationState.listeners.forEach(listener => listener(window.location.pathname));
186
- };
187
-
188
- window.addEventListener('popstate', handlePopState);
189
-
190
- return () => {
191
- navigationState.listeners.delete(handleNavigation);
192
- window.removeEventListener('popstate', handlePopState);
193
- };
194
- }, []);
195
-
196
- return { pathname, navigate, prefetch };
197
- }
198
-
199
- // Setup popstate listener
200
- if (typeof window !== 'undefined') {
201
- window.addEventListener('popstate', () => {
202
- const url = window.location.pathname + window.location.search;
203
- navigationState.listeners.forEach(listener => listener(url));
204
- });
205
- }
206
-
207
- export default {
208
- navigate,
209
- prefetch,
210
- Link,
211
- useNavigation
212
- };
@@ -1,52 +0,0 @@
1
- /**
2
- * FlexiReact Client Runtime
3
- * Main entry point for client-side JavaScript
4
- */
5
-
6
- import { hydrateAllIslands, setupProgressiveHydration } from './hydration.js';
7
- import { navigate, prefetch } from './navigation.js';
8
-
9
- // Extend Window interface
10
- declare global {
11
- interface Window {
12
- FlexiReact: {
13
- navigate: typeof navigate;
14
- prefetch: typeof prefetch;
15
- hydrateAllIslands: typeof hydrateAllIslands;
16
- setupProgressiveHydration: typeof setupProgressiveHydration;
17
- };
18
- __FLEXI_DATA__?: {
19
- islands?: any[];
20
- props?: Record<string, any>;
21
- };
22
- }
23
- }
24
-
25
- // Expose to global scope
26
- window.FlexiReact = {
27
- navigate,
28
- prefetch,
29
- hydrateAllIslands,
30
- setupProgressiveHydration
31
- };
32
-
33
- // Auto-setup on page load
34
- document.addEventListener('DOMContentLoaded', () => {
35
- // Setup progressive hydration for islands
36
- if (window.__FLEXI_DATA__?.islands) {
37
- setupProgressiveHydration(window.__FLEXI_DATA__.islands);
38
- }
39
-
40
- // Dispatch ready event
41
- window.dispatchEvent(new CustomEvent('flexi:ready'));
42
- });
43
-
44
- // Handle page transitions
45
- window.addEventListener('flexi:pageload', () => {
46
- // Re-setup hydration after navigation
47
- if (window.__FLEXI_DATA__?.islands) {
48
- setupProgressiveHydration(window.__FLEXI_DATA__.islands);
49
- }
50
- });
51
-
52
- console.log('⚡ FlexiReact v2 client runtime loaded');
package/core/config.ts DELETED
@@ -1,116 +0,0 @@
1
- /**
2
- * FlexiReact Configuration System
3
- * Handles loading and merging of configuration from flexireact.config.js
4
- */
5
-
6
- import fs from 'fs';
7
- import path from 'path';
8
- import { pathToFileURL } from 'url';
9
-
10
- // Default configuration
11
- export const defaultConfig = {
12
- // Directories
13
- pagesDir: 'pages',
14
- layoutsDir: 'layouts',
15
- publicDir: 'public',
16
- outDir: '.flexi',
17
-
18
- // Build options
19
- build: {
20
- target: 'es2022',
21
- minify: true,
22
- sourcemap: true,
23
- splitting: true
24
- },
25
-
26
- // Server options
27
- server: {
28
- port: 3000,
29
- host: 'localhost'
30
- },
31
-
32
- // SSG options
33
- ssg: {
34
- enabled: false,
35
- paths: []
36
- },
37
-
38
- // Islands (partial hydration)
39
- islands: {
40
- enabled: true
41
- },
42
-
43
- // RSC options
44
- rsc: {
45
- enabled: true
46
- },
47
-
48
- // Plugins
49
- plugins: [],
50
-
51
- // Styles (CSS files to include)
52
- styles: [],
53
-
54
- // Scripts (JS files to include)
55
- scripts: [],
56
-
57
- // Favicon path
58
- favicon: null
59
- };
60
-
61
- /**
62
- * Loads configuration from the project root
63
- * @param {string} projectRoot - Path to project root
64
- * @returns {Object} Merged configuration
65
- */
66
- export async function loadConfig(projectRoot: string) {
67
- // Try .ts first, then .js
68
- const configPathTs = path.join(projectRoot, 'flexireact.config.ts');
69
- const configPathJs = path.join(projectRoot, 'flexireact.config.js');
70
- const configPath = fs.existsSync(configPathTs) ? configPathTs : configPathJs;
71
-
72
- let userConfig = {};
73
-
74
- if (fs.existsSync(configPath)) {
75
- try {
76
- const configUrl = pathToFileURL(configPath).href;
77
- const module = await import(`${configUrl}?t=${Date.now()}`);
78
- userConfig = module.default || module;
79
- } catch (error: any) {
80
- console.warn('Warning: Failed to load flexireact config:', error.message);
81
- }
82
- }
83
-
84
- // Deep merge configs
85
- return deepMerge(defaultConfig, userConfig);
86
- }
87
-
88
- /**
89
- * Deep merge two objects
90
- */
91
- function deepMerge(target, source) {
92
- const result = { ...target };
93
-
94
- for (const key in source) {
95
- if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
96
- result[key] = deepMerge(target[key] || {}, source[key]);
97
- } else {
98
- result[key] = source[key];
99
- }
100
- }
101
-
102
- return result;
103
- }
104
-
105
- /**
106
- * Resolves all paths in config relative to project root
107
- */
108
- export function resolvePaths(config, projectRoot) {
109
- return {
110
- ...config,
111
- pagesDir: path.resolve(projectRoot, config.pagesDir),
112
- layoutsDir: path.resolve(projectRoot, config.layoutsDir),
113
- publicDir: path.resolve(projectRoot, config.publicDir),
114
- outDir: path.resolve(projectRoot, config.outDir)
115
- };
116
- }