@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.
Files changed (55) hide show
  1. package/dist/cli/index.js +1514 -0
  2. package/dist/cli/index.js.map +1 -0
  3. package/dist/core/client/index.js +373 -0
  4. package/dist/core/client/index.js.map +1 -0
  5. package/dist/core/index.js +6415 -0
  6. package/dist/core/index.js.map +1 -0
  7. package/dist/core/server/index.js +3094 -0
  8. package/dist/core/server/index.js.map +1 -0
  9. package/package.json +80 -80
  10. package/bin/flexireact.js +0 -23
  11. package/cli/generators.ts +0 -616
  12. package/cli/index.ts +0 -1182
  13. package/core/actions/index.ts +0 -364
  14. package/core/api.ts +0 -143
  15. package/core/build/index.ts +0 -425
  16. package/core/cli/logger.ts +0 -353
  17. package/core/client/Link.tsx +0 -345
  18. package/core/client/hydration.ts +0 -147
  19. package/core/client/index.ts +0 -12
  20. package/core/client/islands.ts +0 -143
  21. package/core/client/navigation.ts +0 -212
  22. package/core/client/runtime.ts +0 -52
  23. package/core/config.ts +0 -116
  24. package/core/context.ts +0 -83
  25. package/core/dev.ts +0 -47
  26. package/core/devtools/index.ts +0 -644
  27. package/core/edge/cache.ts +0 -344
  28. package/core/edge/fetch-polyfill.ts +0 -247
  29. package/core/edge/handler.ts +0 -248
  30. package/core/edge/index.ts +0 -81
  31. package/core/edge/ppr.ts +0 -264
  32. package/core/edge/runtime.ts +0 -161
  33. package/core/font/index.ts +0 -306
  34. package/core/helpers.ts +0 -494
  35. package/core/image/index.ts +0 -413
  36. package/core/index.ts +0 -218
  37. package/core/islands/index.ts +0 -293
  38. package/core/loader.ts +0 -111
  39. package/core/logger.ts +0 -242
  40. package/core/metadata/index.ts +0 -622
  41. package/core/middleware/index.ts +0 -416
  42. package/core/plugins/index.ts +0 -373
  43. package/core/render/index.ts +0 -1243
  44. package/core/render.ts +0 -136
  45. package/core/router/index.ts +0 -551
  46. package/core/router.ts +0 -141
  47. package/core/rsc/index.ts +0 -199
  48. package/core/server/index.ts +0 -779
  49. package/core/server.ts +0 -203
  50. package/core/ssg/index.ts +0 -346
  51. package/core/start-dev.ts +0 -6
  52. package/core/start-prod.ts +0 -6
  53. package/core/tsconfig.json +0 -30
  54. package/core/types.ts +0 -239
  55. 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
- }